diff --git a/.agents/skills/ui/timo-component/SKILL.md b/.agents/skills/ui/timo-component/SKILL.md index 0c9d81cc..8866aaa8 100644 --- a/.agents/skills/ui/timo-component/SKILL.md +++ b/.agents/skills/ui/timo-component/SKILL.md @@ -9,6 +9,7 @@ - `docs/conventions/code-style.md` → 컴포넌트·타입·A11y 규칙 - `docs/architecture/components.md` → 컴포넌트 계층 +- `docs/architecture/responsive.md` → 반응형 구현 규칙 (모바일 대응이 필요한 경우) ## 워크플로우 diff --git a/.agents/skills/ui/timo-page/SKILL.md b/.agents/skills/ui/timo-page/SKILL.md index e861862c..3f418d65 100644 --- a/.agents/skills/ui/timo-page/SKILL.md +++ b/.agents/skills/ui/timo-page/SKILL.md @@ -9,6 +9,7 @@ - `docs/architecture/components.md` → 컴포넌트 계층 - `docs/architecture/state.md` → 상태 관리 전략 +- `docs/architecture/responsive.md` → 반응형 구현 규칙 (모바일 대응이 필요한 경우) - `docs/conventions/naming.md` → 파일 네이밍 - `docs/conventions/code-style.md` → 코드 스타일 diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml index a1153e67..7782b72f 100644 --- a/.github/workflows/auto-assign.yml +++ b/.github/workflows/auto-assign.yml @@ -29,10 +29,8 @@ jobs: // kimminna가 PR 작성 시: 나머지 3명 중 2명 랜덤 reviewers = pickRandom(others, 2); } else { - // 나머지가 PR 작성 시: kimminna 고정 + 나머지 3명 중 작성자 제외 1명 랜덤 - const eligible = others.filter(c => c !== author); - const random = pickRandom(eligible, 1); - reviewers = ['kimminna', ...random]; + // 나머지가 PR 작성 시: 작성자를 제외한 나머지 2명 리뷰 + reviewers = others.filter(c => c !== author); } await github.rest.pulls.requestReviewers({ diff --git a/.github/workflows/discord-reviewer-reminder.yml b/.github/workflows/discord-reviewer-reminder.yml index fa1b21be..abd90782 100644 --- a/.github/workflows/discord-reviewer-reminder.yml +++ b/.github/workflows/discord-reviewer-reminder.yml @@ -34,6 +34,8 @@ jobs: }; const resolveName = (login) => NAME_MAP[login] || login; + const EXCLUDED_MENTION_LOGINS = ['kimminna']; + const webhookUrl = process.env.DISCORD_WEBHOOK_URL; if (!webhookUrl) { console.log('DISCORD_REMINDER_WEBHOOK_URL not set, skipping'); @@ -94,8 +96,13 @@ jobs: const hoursOpen = (now - oldestRequestTime) / (1000 * 60 * 60); if (hoursOpen < REMINDER_THRESHOLD_HOURS) continue; + const mentionedReviewers = reviewers.filter( + (reviewer) => !EXCLUDED_MENTION_LOGINS.includes(reviewer.login), + ); + if (mentionedReviewers.length === 0 && teams.length === 0) continue; + const reviewerNames = [ - ...reviewers.map((reviewer) => resolveName(reviewer.login)), + ...mentionedReviewers.map((reviewer) => resolveName(reviewer.login)), ...teams.map((team) => team.name || team.slug), ].join(', '); diff --git a/AGENTS.md b/AGENTS.md index e5f568b4..24b5d133 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,8 +69,8 @@ Claude Code / Codex 공통 진입점입니다. ## 문서 위치 -| 내용 | 경로 | -| ------------------------------------------ | -------------------- | -| 커밋·브랜치·이슈·코드 스타일·네이밍 컨벤션 | `docs/conventions/` | -| 기술 스택·컴포넌트 계층·상태 전략·스캐폴딩 | `docs/architecture/` | -| 디자인 토큰·피그마 MCP 연동·스토리북 | `docs/design/` | +| 내용 | 경로 | +| ------------------------------------------------------ | -------------------- | +| 커밋·브랜치·이슈·코드 스타일·네이밍 컨벤션 | `docs/conventions/` | +| 기술 스택·컴포넌트 계층·상태 전략·스캐폴딩·반응형 구현 | `docs/architecture/` | +| 디자인 토큰·피그마 MCP 연동·스토리북 | `docs/design/` | diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx index 0d8d4e73..8de25438 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx @@ -20,7 +20,14 @@ export const WithTimeSidebarContainer = ({ children, }: WithTimeSidebarContainerProps) => { const isOpen = useTimeSidebarStore((state) => state.isOpen); + const isMobileOpen = useTimeSidebarStore((state) => state.isMobileOpen); const toggleOpen = useTimeSidebarStore((state) => state.toggleOpen); + const closeMobileOpen = useTimeSidebarStore((state) => state.closeMobileOpen); + + const handleToggleCollapse = () => { + closeMobileOpen(); + toggleOpen(); + }; const pathname = usePathname(); @@ -39,7 +46,13 @@ export const WithTimeSidebarContainer = ({ {children} - + ); }; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx index 0ce550c2..6abd1a59 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx @@ -1,13 +1,13 @@ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { + ControlPauseActiveIcon, + ControlPlayActiveIcon, MemoDisableIcon, MemoOnIcon, PlayDisabledIcon, - PlayIcon, RepeatTodoDisableIcon, RepeatTodoOnIcon, - StopIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, @@ -142,9 +142,9 @@ export const HomeTodoCard = ({ {isCompleted ? ( ) : isRunning ? ( - + ) : isPlayHighlighted ? ( - + ) : ( )} @@ -179,7 +179,7 @@ export const HomeTodoCard = ({ checked={isSubtaskCompleted} onChange={(checked) => onToggleSubtaskCompleted?.(checked)} /> -

+

{subtaskTitle}

diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx index 55166f87..3e51f1dc 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx @@ -5,7 +5,8 @@ import { useTranslations } from "next-intl"; import { triggerScrollToToday } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-today-scroll"; import { useHomeViewMode } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-view-mode"; import { Header } from "@/components/layout/header/Header"; -import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext"; +import { NavigationSidebarToggle } from "@/components/layout/sidebar/navigation/NavigationSidebarToggle"; +import { useTimeSidebarStore } from "@/stores/time-sidebar/useTimeSidebarStore"; export const HomeHeaderContainer = () => { const t = useTranslations("Home"); @@ -14,7 +15,12 @@ export const HomeHeaderContainer = () => { const viewOptions = [basicLabel, weekLabel]; - const { isOpen, toggle } = useNavigationSidebar(); + const isTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.isMobileOpen, + ); + const toggleTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.toggleMobileOpen, + ); const { isWeekView, setViewMode, goToNextWeek, goToPrevWeek, goToToday } = useHomeViewMode(); @@ -35,7 +41,7 @@ export const HomeHeaderContainer = () => {
- + {isWeekView ? ( ) : ( @@ -44,11 +50,19 @@ export const HomeHeaderContainer = () => { } right={ - +
+ + +
} /> ); diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx index 41df59a2..d0696c35 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx @@ -109,7 +109,9 @@ export const HomeTodoContainer = () => { ref={scrollRef} className={cn( "flex h-full overflow-x-auto scroll-smooth", - isWeekView ? "w-full gap-2.5" : "snap-x snap-proximity gap-5", + isWeekView + ? "w-full snap-x snap-mandatory gap-2.5 md:snap-none" + : "snap-x snap-proximity gap-5", )} > {days.map((day) => { @@ -125,8 +127,8 @@ export const HomeTodoContainer = () => { className={cn( "flex h-full flex-col gap-2", isWeekView - ? "min-w-[150px] flex-1" - : "w-[230px] shrink-0 snap-start", + ? "w-full shrink-0 snap-start md:w-auto md:min-w-37.5 md:flex-1 md:shrink" + : "w-57.5 shrink-0 snap-start", )} > { if (isTimerActionPending) return; - const willRun = - todosByDate[dateKey]?.find((todo) => todo.todoId === todoId) - ?.timerStatus !== "RUNNING"; - - if (willRun) { - openTimerPanel(); - } - if ( activeTimer && activeTimer.todoId === todoId && activeTimer.date === dateKey ) { + const isResuming = activeTimer.status !== "RUNNING"; + changeStatus( { timerId: activeTimer.timerId, @@ -174,8 +168,10 @@ export const useHomeTodosByDate = ( }, }, { - onSuccess: () => - invalidateTimerProgress({ includeFocus: true, todoId }), + onSuccess: () => { + if (isResuming) openTimerPanel(); + invalidateTimerProgress({ includeFocus: true, todoId }); + }, }, ); return; @@ -190,8 +186,10 @@ export const useHomeTodosByDate = ( startTimer( { todoId, params: { date: dateKey } }, { - onSuccess: () => - invalidateTimerProgress({ includeFocus: true, todoId }), + onSuccess: () => { + openTimerPanel(); + invalidateTimerProgress({ includeFocus: true, todoId }); + }, onError: (error: ApiError) => { onPlayError(error.message); }, diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx index 920d018f..10e44231 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx @@ -1,12 +1,21 @@ import { + CalendarDisableIcon, + CalendarOnIcon, + ClockDisableIcon, + ClockOnIcon, + ControlPauseActiveIcon, + ControlPlayActiveIcon, + MemoDisableIcon, + MemoOnIcon, PlayDisabledIcon, - PlayIcon, - StopIcon, + RepeatDisableIcon, + RepeatOnIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, PlayButton, - TodoToolbar, + PriorityIcon, + TagIcon, type PriorityLevel, } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; @@ -18,15 +27,17 @@ const CARD_STYLE = { active: { card: "bg-white", title: "text-timo-gray-900", - subText: "text-timo-gray-700", + toolbarText: "text-timo-gray-900", }, done: { card: "bg-timo-gray-200", title: "text-timo-gray-700", - subText: "text-timo-gray-700", + toolbarText: "text-timo-gray-700", }, } as const; +const TOOLBAR_ICON_CLASS_NAME = "size-4.5 shrink-0 md:size-5.5"; + export interface SubTodo { id: number; text: string; @@ -35,7 +46,6 @@ export interface SubTodo { export interface TodayTodoCardToolbar { date: string; - dateValue: Date; time: string; priority?: PriorityLevel; tag?: string; @@ -80,8 +90,13 @@ export const TodayTodoCard = ({ onMouseLeave, }: TodayTodoCardProps) => { const t = useTranslations("Home.createModal"); + const tCommon = useTranslations("Common"); const style = CARD_STYLE[isDimmed ? "done" : "active"]; + const priorityLabel = toolbar.priority + ? tCommon(`priority.${toolbar.priority}`) + : undefined; + const handleCardKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); @@ -98,7 +113,7 @@ export const TodayTodoCard = ({ onClick={onCardClick} onKeyDown={onCardClick ? handleCardKeyDown : undefined} className={cn( - "border-timo-gray-500 flex min-w-80 flex-col gap-1 rounded-[4px] border px-5 py-4", + "border-timo-gray-500 flex w-full flex-col gap-1 overflow-hidden rounded-[4px] border px-3.5 py-3 md:min-w-80 md:px-5 md:py-4", style.card, onCardClick && "cursor-pointer", )} @@ -135,9 +150,9 @@ export const TodayTodoCard = ({ {isDone ? ( ) : isPlaying ? ( - + ) : isPlayHighlighted ? ( - + ) : ( )} @@ -160,12 +175,7 @@ export const TodayTodoCard = ({ onChange={() => onSubTodoCheck(sub.id)} /> - + {sub.text} @@ -173,33 +183,73 @@ export const TodayTodoCard = ({ )} -
-
- +
+
+ + {isDimmed ? ( + + ) : ( + + )} + + {toolbar.date} + + + + {isDimmed ? : } + + {toolbar.time} + + + + + + + + + + {isDimmed || !toolbar.hasMemo ? ( + + ) : ( + + )} + + + {isDimmed || !toolbar.hasRepeat ? ( + + ) : ( + + )} +
+ + {toolbar.time} +
); diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx index df1d0a4b..ff29f33b 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx @@ -1,12 +1,28 @@ "use client"; import { Header } from "@/components/layout/header/Header"; -import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext"; +import { NavigationSidebarToggle } from "@/components/layout/sidebar/navigation/NavigationSidebarToggle"; +import { useTimeSidebarStore } from "@/stores/time-sidebar/useTimeSidebarStore"; export const TodayHeaderContainer = () => { - const { isOpen, toggle } = useNavigationSidebar(); + const isTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.isMobileOpen, + ); + const toggleTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.toggleMobileOpen, + ); return ( -
} /> +
} + right={ + + } + /> ); }; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx index 94a4a295..7d544a7e 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx @@ -139,7 +139,6 @@ export const TodayTodoListContainer = () => { date: formatShortDateLabel( parseDateKey(todo.date) ?? new Date(), ), - dateValue: parseDateKey(todo.date) ?? new Date(), time: convertDurationToTimeText(durationSeconds), priority: todo.priority, tag: todo.tag ? getTagLabel(todo.tag) : undefined, diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts index 240eee50..b4b0fab0 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts @@ -142,18 +142,13 @@ export const useTodayTodoList = ( const dateKey = todos.find((todo) => todo.todoId === todoId)?.date; if (!dateKey) return; - const willRun = - todos.find((todo) => todo.todoId === todoId)?.timerStatus !== "RUNNING"; - - if (willRun) { - openTimerPanel(); - } - if ( activeTimer && activeTimer.todoId === todoId && activeTimer.date === dateKey ) { + const isResuming = activeTimer.status !== "RUNNING"; + changeStatus( { timerId: activeTimer.timerId, @@ -162,8 +157,10 @@ export const useTodayTodoList = ( }, }, { - onSuccess: () => - invalidateTimerProgress({ includeFocus: true, todoId }), + onSuccess: () => { + if (isResuming) openTimerPanel(); + invalidateTimerProgress({ includeFocus: true, todoId }); + }, }, ); return; @@ -178,8 +175,10 @@ export const useTodayTodoList = ( startTimer( { todoId, params: { date: dateKey } }, { - onSuccess: () => - invalidateTimerProgress({ includeFocus: true, todoId }), + onSuccess: () => { + openTimerPanel(); + invalidateTimerProgress({ includeFocus: true, todoId }); + }, onError: (error: ErrorType) => { onPlayError(error.response?.data.message); }, diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx index 4fffe122..1b06d043 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx @@ -6,8 +6,8 @@ export default function TodayPage() { return (
-
-
+
+
diff --git a/apps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsx b/apps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsx index 4d362aeb..d28168cb 100644 --- a/apps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsx @@ -1,20 +1,22 @@ "use client"; import { cn } from "@repo/timo-design-system/utils"; +import { useEffect } from "react"; import { NavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebar"; import { NavigationSidebarProvider, useNavigationSidebar, } from "@/components/layout/sidebar/navigation/NavigationSidebarContext"; +import { unlockTimerSound } from "@/utils/timer/timer-sound"; interface MainShellContainerProps { children: React.ReactNode; } const MAIN_MARGIN_CLASS_NAME = { - open: "ml-55", - collapsed: "ml-5", + open: "md:ml-55", + collapsed: "md:ml-5", } as const; const MainContent = ({ children }: { children: React.ReactNode }) => { @@ -23,7 +25,7 @@ const MainContent = ({ children }: { children: React.ReactNode }) => { return (
@@ -33,6 +35,22 @@ const MainContent = ({ children }: { children: React.ReactNode }) => { }; export const MainShellContainer = ({ children }: MainShellContainerProps) => { + useEffect(() => { + const unlock = (event: Event) => { + if (!event.isTrusted) return; + void unlockTimerSound().then((isUnlocked) => { + if (isUnlocked) removeListeners(); + }); + }; + const removeListeners = () => { + window.removeEventListener("click", unlock, true); + window.removeEventListener("keydown", unlock, true); + }; + window.addEventListener("click", unlock, true); + window.addEventListener("keydown", unlock, true); + return removeListeners; + }, []); + return ( diff --git a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusEmptyTaskItem.tsx b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusEmptyTaskItem.tsx index cdb087b4..47d79ca6 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusEmptyTaskItem.tsx +++ b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusEmptyTaskItem.tsx @@ -20,7 +20,7 @@ export const FocusEmptyTaskItem = ({ const t = useTranslations("Focus"); return ( -
+

{dayNumber}

{dayOfWeek}

diff --git a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx new file mode 100644 index 00000000..59a72882 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx @@ -0,0 +1,45 @@ +import type { ReactNode } from "react"; + +import { AnimatedToast } from "@/components/toast/AnimatedToast"; + +export interface FocusSessionLayoutProps { + header?: ReactNode; + isErrorToastOpen: boolean; + onCloseErrorToast: () => void; + errorToastMessage: string; + taskItem: ReactNode; + timer: ReactNode; + controls: ReactNode; +} + +export const FocusSessionLayout = ({ + header, + isErrorToastOpen, + onCloseErrorToast, + errorToastMessage, + taskItem, + timer, + controls, +}: FocusSessionLayoutProps) => { + return ( +
+ {header} + + +
+
{taskItem}
+ +
+
+ {timer} + {controls} +
+
+
+
+ ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx index 1002ba8f..17db9059 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx +++ b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx @@ -1,8 +1,8 @@ import { CalendarOnIcon, ClockOnIcon, - PlayIcon, - StopIcon, + ControlPauseActiveIcon, + ControlPlayActiveIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, PlayButton } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; @@ -38,8 +38,10 @@ export const FocusTaskItem = ({ onTogglePlay, onToggleSubtaskCompleted, }: FocusTaskItemProps) => { + const hasChecklist = subtasks.length > 0 || Boolean(memo); + return ( -
+

{dayNumber}

{dayOfWeek}

@@ -75,14 +77,21 @@ export const FocusTaskItem = ({ onClick={onTogglePlay} > {isRunning ? ( - + ) : ( - + )}
-
+
{subtasks.map((subtask) => (
{ - const { isOpen, toggle } = useNavigationSidebar(); - - return ( -
} /> - ); + return
} />; }; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx b/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx index ff5cf58e..54edca0f 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx @@ -4,6 +4,7 @@ import { useTranslations } from "next-intl"; import { useState } from "react"; import { FocusEmptyTaskItem } from "@/app/[locale]/(main)/focus/_components/FocusEmptyTaskItem"; +import { FocusSessionLayout } from "@/app/[locale]/(main)/focus/_components/FocusSessionLayout"; import { FocusTaskItem } from "@/app/[locale]/(main)/focus/_components/FocusTaskItem"; import { FocusHeaderContainer } from "@/app/[locale]/(main)/focus/_containers/FocusHeaderContainer"; import { useFocusSession } from "@/app/[locale]/(main)/focus/_hooks/use-focus-session"; @@ -13,10 +14,13 @@ import { } from "@/app/[locale]/(main)/focus/_utils/date"; import { Timer } from "@/components/timer/Timer"; import { TimerSessionControls } from "@/components/timer/TimerSessionControls"; -import { AnimatedToast } from "@/components/toast/AnimatedToast"; import { convertDurationToTimeText } from "@/utils/duration/convert-duration-to-time-text"; import { formatDurationLabel } from "@/utils/duration/format-duration-label"; +const TIMER_SIZE_CLASS_NAME = "h-60.5 w-60.5 md:h-90 md:w-90"; +const TIMER_TIME_CLASS_NAME = "typo-headline-b-40 md:typo-headline-b-50"; +const TIMER_PLANNED_CLASS_NAME = "typo-headline-m-20 md:typo-headline-m-26"; + export const FocusSessionContainer = () => { const tWeekday = useTranslations("Common.weekday"); const tToast = useTranslations("Toast"); @@ -29,16 +33,16 @@ export const FocusSessionContainer = () => { onFeedback: setFeedbackText, }); + const header = ; + if (!focusSessionState.focusView.hasTodo || !focusSessionState.todo) { return ( -
- setIsErrorToastOpen(false)} - message={tToast("focusActionFailed")} - /> -
- + setIsErrorToastOpen(false)} + errorToastMessage={tToast("focusActionFailed")} + taskItem={ { )} dateText={focusSessionState.dateText} /> -
- -
-
- - - {}} - plannedMinutes={0} - actualMinutes={0} - isTimeUp={false} - onExtend={() => {}} - onComplete={() => {}} - onStop={() => {}} - disabled - /> -
-
-
+ } + timer={ + + } + controls={ + {}} + plannedMinutes={0} + actualMinutes={0} + isTimeUp={false} + onExtend={() => {}} + onComplete={() => {}} + onStop={() => {}} + disabled + /> + } + /> ); } const todo = focusSessionState.todo; return ( -
- setIsErrorToastOpen(false)} - message={tToast("focusActionFailed")} - /> -
- + setIsErrorToastOpen(false)} + errorToastMessage={tToast("focusActionFailed")} + taskItem={ { focusSessionActions.onToggleSubtaskCompleted } /> -
- -
-
- - - -
-
-
+ } + timer={ + + } + controls={ + + } + /> ); }; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts b/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts index f9c6a679..0ec5cacd 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts +++ b/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts @@ -24,6 +24,7 @@ import { useTimerActions } from "@/hooks/timer/use-timer-actions"; import { useTimerOvertime } from "@/hooks/timer/use-timer-overtime"; import { useTimerQueryInvalidation } from "@/hooks/timer/use-timer-query-invalidation"; import { getTimerProgress } from "@/utils/timer/get-timer-progress"; +import { playTimerSound } from "@/utils/timer/timer-sound"; export interface UseFocusSessionOptions { onMutationError: () => void; @@ -138,6 +139,7 @@ export const useFocusSession = ({ useEffect(() => { if (isTimeUp && !wasTimeUpRef.current) { timerSessionControlsRef.current?.openEndModal(); + void playTimerSound(); if (timer && isRunning) { changeStatus({ timerId: timer.timerId, data: { action: "PAUSE" } }); } diff --git a/apps/timo-web/app/[locale]/(main)/layout.tsx b/apps/timo-web/app/[locale]/(main)/layout.tsx index fcc5efd6..fd881994 100644 --- a/apps/timo-web/app/[locale]/(main)/layout.tsx +++ b/apps/timo-web/app/[locale]/(main)/layout.tsx @@ -17,7 +17,7 @@ interface MainLayoutProps { export default function MainLayout({ children }: Readonly) { return ( -
+
{children} diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx index b9f3c1fa..31f6a9d8 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx @@ -1,7 +1,7 @@ "use client"; import timoTimerLogo from "@repo/timo-design-system/assets/images/logo/timo-timer.svg"; -import { ModalButton } from "@repo/timo-design-system/ui"; +import { Button } from "@repo/timo-design-system/ui"; import Image from "next/image"; import type { SettingsProfileLabels } from "@/app/[locale]/(main)/settings/_types/account/profile-type"; @@ -29,7 +29,7 @@ export const SettingsCalendarDisconnectModal = ({ onClose={onClose} onExited={onExited} ariaLabel={labels.calendarDisconnectConfirmTitle} - className="w-100 p-5.5" + className="m-1 w-100 p-4.5 md:m-0 md:p-5.5" >
@@ -41,19 +41,27 @@ export const SettingsCalendarDisconnectModal = ({ {labels.calendarDisconnectConfirmDescription}

- +
); diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx index a5e8f4a4..e4c69f19 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx @@ -1,4 +1,4 @@ -import { PillButton } from "@repo/timo-design-system/ui"; +import { Button } from "@repo/timo-design-system/ui"; import Image from "next/image"; import type { @@ -43,16 +43,16 @@ export const SettingsProfileView = ({ return (
-

+

{labels.title}

-
+

{labels.profileSection}

-
+
{profileImageUrl ? ( ) : ( -
+
)}

{name}

@@ -88,25 +88,30 @@ export const SettingsProfileView = ({

{labels.calendarSection}

-
+
- +
+ +
Google Calendar
- {isCalendarConnected ? labels.disconnect : labels.connect} - +
diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx index 5e0b1c8f..1e0bb318 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx @@ -1,4 +1,5 @@ import { WarningGrayIcon } from "@repo/timo-design-system/icons"; +import { Button } from "@repo/timo-design-system/ui"; import type { SettingsWithdrawalLabels } from "@/app/[locale]/(main)/settings/_types/withdrawal/withdrawal-type"; @@ -12,11 +13,13 @@ export const SettingsWithdrawalView = ({ onWithdraw, }: SettingsWithdrawalViewProps) => { return ( -
-

{labels.title}

-
+
+

+ {labels.title} +

+
-
+

{labels.guideTitle} @@ -26,7 +29,7 @@ export const SettingsWithdrawalView = ({

-
+

{labels.warningTitle}

@@ -42,13 +45,15 @@ export const SettingsWithdrawalView = ({
- +
); diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsx index a5fa5297..a5538299 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsx @@ -1,12 +1,8 @@ "use client"; import { Header } from "@/components/layout/header/Header"; -import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext"; +import { NavigationSidebarToggle } from "@/components/layout/sidebar/navigation/NavigationSidebarToggle"; export const SettingsHeaderContainer = () => { - const { isOpen, toggle } = useNavigationSidebar(); - - return ( -
} /> - ); + return
} />; }; diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx index 2f736c05..1a70fde6 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx @@ -25,24 +25,24 @@ export const SettingsNavContainer = () => { ]; return ( -