From d6e6c28dd92a04b45ef7bf09453785617f139f34 Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 01:56:54 +0900 Subject: [PATCH 01/10] =?UTF-8?q?chore(web):=20@use-funnel/browser=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 온보딩 퍼널 구현을 위해 @use-funnel/browser 패키지를 추가했습니다 --- apps/timo-web/package.json | 1 + pnpm-lock.yaml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/apps/timo-web/package.json b/apps/timo-web/package.json index 92b8707a..5c3c47eb 100644 --- a/apps/timo-web/package.json +++ b/apps/timo-web/package.json @@ -18,6 +18,7 @@ "@sentry/nextjs": "^10.61.0", "@tanstack/react-query": "^5.101.1", "@tanstack/react-query-devtools": "^5.101.1", + "@use-funnel/browser": "^0.0.17", "axios": "^1.18.1", "next": "16.2.0", "next-intl": "^4.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7e9c27e..528b6854 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.101.1 version: 5.101.1(@tanstack/react-query@5.101.1(react@19.2.0))(react@19.2.0) + '@use-funnel/browser': + specifier: ^0.0.17 + version: 0.0.17(react@19.2.0) axios: specifier: ^1.18.1 version: 1.18.1 @@ -2091,6 +2094,16 @@ packages: resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@use-funnel/browser@0.0.17': + resolution: {integrity: sha512-saexLJSpTwm02If0I6oY6s1Z7cPwkEGqHvBg+IVokDywcrkhZA9MpxDHT27GSrNlhJHRxis/3g/xIQ48UYfp2Q==} + peerDependencies: + react: '>=16.8' + + '@use-funnel/core@0.1.0': + resolution: {integrity: sha512-KEjBKTbOh5wd6CQZ1jYWhI6KuGrI9Pjn9CkmUgAYzonsyZE0lsiZ2/1RwsaWcxVO5RJBgfwCM8dRpibwm7An9w==} + peerDependencies: + react: '>=16.8' + '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} @@ -6228,6 +6241,15 @@ snapshots: '@typescript-eslint/types': 8.50.0 eslint-visitor-keys: 4.2.1 + '@use-funnel/browser@0.0.17(react@19.2.0)': + dependencies: + '@use-funnel/core': 0.1.0(react@19.2.0) + react: 19.2.0 + + '@use-funnel/core@0.1.0(react@19.2.0)': + dependencies: + react: 19.2.0 + '@vitest/expect@2.0.5': dependencies: '@vitest/spy': 2.0.5 From a0c0d4adedec28b796693dffe43844a4aca9e92f Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 01:57:19 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat(ui):=20alert=C2=B7moon=C2=B7sun=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=86=8C=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 온보딩 화면에서 사용할 alert, moon, sun SVG 아이콘을 추가했습니다 --- .../src/icons/source/alert.svg | 3 +++ .../src/icons/source/moon.svg | 10 ++++++++++ .../src/icons/source/sun.svg | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 packages/timo-design-system/src/icons/source/alert.svg create mode 100644 packages/timo-design-system/src/icons/source/moon.svg create mode 100644 packages/timo-design-system/src/icons/source/sun.svg diff --git a/packages/timo-design-system/src/icons/source/alert.svg b/packages/timo-design-system/src/icons/source/alert.svg new file mode 100644 index 00000000..35fd0a99 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/alert.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/moon.svg b/packages/timo-design-system/src/icons/source/moon.svg new file mode 100644 index 00000000..507d46f3 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/moon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/timo-design-system/src/icons/source/sun.svg b/packages/timo-design-system/src/icons/source/sun.svg new file mode 100644 index 00000000..08218ff5 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/sun.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + From 63871ffa2bc042cf0cc9f532140752d2659b6ca1 Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 01:57:37 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat(web):=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20i18n=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 페이지 한/영 메시지를 추가했습니다 - 언어 선택, 시간 예측, 생활 패턴, 캘린더 연결 단계 메시지를 추가했습니다 --- apps/timo-web/messages/en.json | 46 ++++++++++++++++++++++++++++++++++ apps/timo-web/messages/ko.json | 43 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index 473c9527..e905a463 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -41,6 +41,12 @@ "todoLimitLine1": "You can add up to 20 incomplete to-dos.", "todoLimitLine2": "To add a new to-do, please complete an existing one." }, + "Login": { + "headline": "Manage your day with clear timeboxes.", + "description": "Please log in to your timo account.", + "connectLabel": "Log in with Google", + "termsNotice": "By continuing, you agree to the timo Terms of Service and Privacy Policy." + }, "Onboarding": { "button": { "next": "Next", @@ -50,6 +56,46 @@ "onboardingGoogleButton": { "login": "Continue with your Google account", "connectCalendar": "Connect Google Calendar" + }, + "language": { + "title": "Please select a language to use.", + "description": "timo will be displayed in your selected language.\nYou can change it later in settings." + }, + "timePrediction": { + "title": "Do you estimate your time well?", + "description": "timo provides personalized recommendations.", + "options": { + "4": { + "label": "Very accurate.", + "sublabel": "The estimated time matches the actual time." + }, + "3": { + "label": "Generally accurate.", + "sublabel": "Sometimes incorrect but generally accurate." + }, + "2": { + "label": "Often incorrect.", + "sublabel": "Always goes differently than expected." + }, + "1": { + "label": "I'm not sure.", + "sublabel": "I've never thought about it." + } + } + }, + "lifePattern": { + "title": "Set your wake-up and bedtime.", + "description": "The information you provide will only be used for time pattern analysis.", + "wakeUpTime": "Wake-up time", + "bedTime": "Bedtime", + "bedTimeError": "Please set your bedtime later than your wake-up time." + }, + "calendarConnect": { + "title": "Connect your Google Calendar.", + "description": "You can import your existing schedules into timo and manage them in one place.", + "connectLabel": "Google Calendar integration", + "consentNotice": "Clicking will display the Google authentication and calendar access permission screen.", + "permissionNotice": "timo only requests permission to view your schedules." } } } diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index 076fe886..4edd6596 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -41,6 +41,12 @@ "todoLimitLine1": "완료되지 않은 투두는 최대 20개까지 추가할 수 있어요.", "todoLimitLine2": "새로운 투두를 추가하려면 기존 투두를 완료해주세요." }, + "Login": { + "headline": "흩어진 시간을, 실행 가능한 흐름으로.", + "description": "timo 계정에 로그인하세요.", + "connectLabel": "Google 로그인", + "termsNotice": "계속하면 timo 이용약관개인정보 처리방침에 동의하게 됩니다." + }, "Onboarding": { "button": { "next": "다음", @@ -50,6 +56,43 @@ "onboardingGoogleButton": { "login": "Google 계정으로 계속하기", "connectCalendar": "Google 캘린더 연결하기" + }, + "language": { + "title": "사용할 언어를 선택해 주세요.", + "description": "선택한 언어로 timo의 모든 텍스트가 표시돼요.\n나중에 설정에서 변경할 수 있어요." + }, + "timePrediction": { + "title": "평소 작업 시간을\n잘 예측하는 편인가요?", + "description": "timo가 맞춤 추천을 해드려요.", + "options": { + "4": { + "label": "매우 정확해요.", + "sublabel": "예상 시간과 실제 시간이 일치해요." + }, + "3": { + "label": "대체로 맞는 편이에요.", + "sublabel": "가끔 틀리지만 대체로 맞는 편이에요." + }, + "2": { + "label": "자주 틀리는 편이에요.", + "sublabel": "예상과 항상 다르게 흘러가요." + }, + "1": { "label": "잘 모르겠어요.", "sublabel": "생각해본 적이 없어요." } + } + }, + "lifePattern": { + "title": "평소 생활 패턴을 알려주세요.", + "description": "입력한 정보는 시간 패턴 분석에만 사용돼요.", + "wakeUpTime": "기상 시간", + "bedTime": "취침 시간", + "bedTimeError": "취침 시간은 기상 시간보다 늦게 설정해 주세요." + }, + "calendarConnect": { + "title": "Google 캘린더를 연결하면\n일정을 함께 관리할 수 있어요.", + "description": "기존 일정을 timo에 불러와 한 곳에서 관리할 수 있어요.", + "connectLabel": "Google 캘린더 연동", + "consentNotice": "클릭 시 Google 인증 및 캘린더 접근 권한 동의 화면이 표시됩니다.", + "permissionNotice": "timo는 일정 조회 권한만 요청합니다." } } } From bd2ae23dd6d8d49ce0cdd936178e269bcb42aa1d Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 01:57:57 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat(web):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LoginContainer 컨테이너를 구현했습니다 - 로그인 페이지를 추가했습니다 --- .../login/_containers/LoginContainer.tsx | 70 +++++++++++++++++++ apps/timo-web/app/[locale]/login/page.tsx | 5 ++ 2 files changed, 75 insertions(+) create mode 100644 apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx create mode 100644 apps/timo-web/app/[locale]/login/page.tsx diff --git a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx new file mode 100644 index 00000000..dd85ceec --- /dev/null +++ b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx @@ -0,0 +1,70 @@ +"use client"; + +import timoTextLogo from "@repo/timo-design-system/assets/images/logo/timo-text-logo.svg"; +import Image from "next/image"; +import { useTranslations } from "next-intl"; + +import { OnboardingGoogleButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer"; + +export const LoginContainer = () => { + const t = useTranslations("Login"); + + return ( +
+ {/* TODO: PR#131(feat/web/130-status-screen-lottie) 병합 후 온보딩 Lottie 그래픽으로 교체 */} +
+ +
+
+
+ Timo +
+ + Less Chaos More Focus + + + {t("headline")} + +
+
+

+ {t("description")} +

+
+ +
+
+

+ {t("connectLabel")} +

+ { + // TODO: 백엔드 OAuth 로그인 URL 확정 후 리다이렉트 + }} + /> +
+ +

+ {t.rich("termsNotice", { + // TODO: 실제 이용약관 페이지 URL 나오면 href 교체 + terms: (chunks) => ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + + {chunks} + + ), + // TODO: 실제 개인정보 처리방침 페이지 URL 나오면 href 교체 + privacy: (chunks) => ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + + {chunks} + + ), + })} +

+
+
+
+ ); +}; diff --git a/apps/timo-web/app/[locale]/login/page.tsx b/apps/timo-web/app/[locale]/login/page.tsx new file mode 100644 index 00000000..4abaaf3e --- /dev/null +++ b/apps/timo-web/app/[locale]/login/page.tsx @@ -0,0 +1,5 @@ +import { LoginContainer } from "@/app/[locale]/login/_containers/LoginContainer"; + +export default function LoginPage() { + return ; +} From 467246e8706dfa0536528ba58fbfcdda463251c3 Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 01:58:31 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat(web):=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=20=ED=8D=BC=EB=84=90=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88?= =?UTF-8?q?=C2=B7=ED=83=80=EC=9E=85=C2=B7=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 온보딩 퍼널 타입(onboarding-funnel.ts)을 정의했습니다 - 언어 선택, 시간 예측, 생활 패턴, 캘린더 연결 단계 컨테이너를 구현했습니다 - OnboardingFunnelContainer로 퍼널 흐름을 조합했습니다 - 온보딩 페이지에 OnboardingFunnelContainer를 연동했습니다 --- .../CalendarConnectStepContainer.tsx | 65 +++++++++ .../_containers/LanguageStepContainer.tsx | 61 +++++++++ .../_containers/LifePatternStepContainer.tsx | 98 ++++++++++++++ .../_containers/OnboardingFunnelContainer.tsx | 124 ++++++++++++++++++ .../TimePredictionStepContainer.tsx | 65 +++++++++ .../onboarding/_types/onboarding-funnel.ts | 26 ++++ .../timo-web/app/[locale]/onboarding/page.tsx | 4 +- 7 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 apps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx create mode 100644 apps/timo-web/app/[locale]/onboarding/_containers/LanguageStepContainer.tsx create mode 100644 apps/timo-web/app/[locale]/onboarding/_containers/LifePatternStepContainer.tsx create mode 100644 apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx create mode 100644 apps/timo-web/app/[locale]/onboarding/_containers/TimePredictionStepContainer.tsx create mode 100644 apps/timo-web/app/[locale]/onboarding/_types/onboarding-funnel.ts diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx new file mode 100644 index 00000000..45401d42 --- /dev/null +++ b/apps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { useState } from "react"; + +import { OnboardingButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingButtonContainer"; +import { OnboardingGoogleButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer"; + +interface CalendarConnectStepContainerProps { + onPrev: () => void; + onStart: () => void; +} + +export const CalendarConnectStepContainer = ({ + onPrev, + onStart, +}: CalendarConnectStepContainerProps) => { + const t = useTranslations("Onboarding"); + const [isCalendarConnected, setIsCalendarConnected] = useState(false); + + return ( + <> +
+
+

+ {t("calendarConnect.title")} +

+

+ {t("calendarConnect.description")} +

+
+ +
+
+

+ {t("calendarConnect.connectLabel")} +

+ { + // TODO: 실제 구글 캘린더 OAuth 연동 (백엔드 API 확정 후) + setIsCalendarConnected(true); + }} + /> +
+ +
+

+ {t("calendarConnect.consentNotice")} +

+

+ {t("calendarConnect.permissionNotice")} +

+
+
+
+ +
+ + +
+ + ); +}; diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/LanguageStepContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/LanguageStepContainer.tsx new file mode 100644 index 00000000..d6983ad9 --- /dev/null +++ b/apps/timo-web/app/[locale]/onboarding/_containers/LanguageStepContainer.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import { OnboardingSelectCard } from "@/app/[locale]/onboarding/_components/OnboardingSelectCard"; +import { OnboardingButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingButtonContainer"; + +interface LanguageStepContainerProps { + language?: "ko" | "en"; + onSelect: (language: "ko" | "en" | undefined) => void; + onNext: () => void; +} + +export const LanguageStepContainer = ({ + language, + onSelect, + onNext, +}: LanguageStepContainerProps) => { + const t = useTranslations("Onboarding"); + + return ( + <> +
+
+

+ {t("language.title")} +

+

+ {t("language.description")} +

+
+ +
+ onSelect(language === "en" ? undefined : "en")} + /> + onSelect(language === "ko" ? undefined : "ko")} + /> +
+
+ +
+ +
+ + ); +}; diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/LifePatternStepContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/LifePatternStepContainer.tsx new file mode 100644 index 00000000..1fdd46b8 --- /dev/null +++ b/apps/timo-web/app/[locale]/onboarding/_containers/LifePatternStepContainer.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { AlertIcon, MoonIcon, SunIcon } from "@repo/timo-design-system/icons"; +import { useTranslations } from "next-intl"; + +import { OnboardingTimeDropdown } from "@/app/[locale]/onboarding/_components/OnboardingTimeDropdown"; +import { OnboardingButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingButtonContainer"; + +interface LifePatternStepContainerProps { + wakeUpTime?: string; + bedTime?: string; + onSelectWakeUpTime: (time: string) => void; + onSelectBedTime: (time: string) => void; + onPrev: () => void; + onNext: () => void; +} + +export const LifePatternStepContainer = ({ + wakeUpTime, + bedTime, + onSelectWakeUpTime, + onSelectBedTime, + onPrev, + onNext, +}: LifePatternStepContainerProps) => { + const t = useTranslations("Onboarding"); + + const isBedTimeInvalid = Boolean( + wakeUpTime && bedTime && bedTime <= wakeUpTime, + ); + const canProceed = Boolean(wakeUpTime && bedTime && !isBedTimeInvalid); + + return ( + <> +
+
+

+ {t("lifePattern.title")} +

+

+ {t("lifePattern.description")} +

+
+ +
+
+
+
+ + + {t("lifePattern.wakeUpTime")} + +
+ +
+ +
+
+ + + {t("lifePattern.bedTime")} + +
+ +
+
+ + {isBedTimeInvalid && ( +
+ + + {t("lifePattern.bedTimeError")} + +
+ )} +
+
+ +
+ + +
+ + ); +}; diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx new file mode 100644 index 00000000..ed6d5d3d --- /dev/null +++ b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useFunnel } from "@use-funnel/browser"; +import { useState } from "react"; + +import { OnboardingStepButton } from "@/app/[locale]/onboarding/_components/OnboardingStepButton"; +import { CalendarConnectStepContainer } from "@/app/[locale]/onboarding/_containers/CalendarConnectStepContainer"; +import { LanguageStepContainer } from "@/app/[locale]/onboarding/_containers/LanguageStepContainer"; +import { LifePatternStepContainer } from "@/app/[locale]/onboarding/_containers/LifePatternStepContainer"; +import { TimePredictionStepContainer } from "@/app/[locale]/onboarding/_containers/TimePredictionStepContainer"; +import { OnboardingFunnelSteps } from "@/app/[locale]/onboarding/_types/onboarding-funnel"; + +const STEP_NUMBER: Record = { + Language: 1, + TimePrediction: 2, + LifePattern: 3, + CalendarConnect: 4, +}; + +export const OnboardingFunnelContainer = () => { + const [answers, setAnswers] = useState< + Partial + >({}); + + const funnel = useFunnel({ + id: "onboarding", + initial: { step: "Language", context: {} }, + }); + + return ( +
+ {/* TODO: PR#131(feat/web/130-status-screen-lottie) 병합 후 온보딩 Lottie 그래픽으로 교체 */} +
+
+
+ + + ( + + setAnswers((prev) => ({ ...prev, language })) + } + onNext={() => { + if (!answers.language) return; + history.push("TimePrediction", { + language: answers.language, + }); + }} + /> + )} + TimePrediction={({ history }) => ( + + setAnswers((prev) => ({ ...prev, predictionAccuracy })) + } + onPrev={() => history.back()} + onNext={() => { + if (!answers.language || !answers.predictionAccuracy) return; + history.push("LifePattern", { + language: answers.language, + predictionAccuracy: answers.predictionAccuracy, + }); + }} + /> + )} + LifePattern={({ history }) => ( + + setAnswers((prev) => ({ ...prev, wakeUpTime })) + } + onSelectBedTime={(bedTime) => + setAnswers((prev) => ({ ...prev, bedTime })) + } + onPrev={() => history.back()} + onNext={() => { + if ( + !answers.language || + !answers.predictionAccuracy || + !answers.wakeUpTime || + !answers.bedTime + ) + return; + history.push("CalendarConnect", { + language: answers.language, + predictionAccuracy: answers.predictionAccuracy, + wakeUpTime: answers.wakeUpTime, + bedTime: answers.bedTime, + }); + }} + /> + )} + CalendarConnect={({ history }) => ( + history.back()} + onStart={() => { + if ( + !answers.language || + !answers.predictionAccuracy || + !answers.wakeUpTime || + !answers.bedTime + ) + return; + const payload: OnboardingFunnelSteps["CalendarConnect"] = { + language: answers.language, + predictionAccuracy: answers.predictionAccuracy, + wakeUpTime: answers.wakeUpTime, + bedTime: answers.bedTime, + }; + // TODO: 실제 제출 API 연동 + 완료 후 payload.language로 locale 리다이렉트 + console.log("[onboarding] submit payload", payload); + }} + /> + )} + /> +
+
+
+ ); +}; diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/TimePredictionStepContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/TimePredictionStepContainer.tsx new file mode 100644 index 00000000..81a6488f --- /dev/null +++ b/apps/timo-web/app/[locale]/onboarding/_containers/TimePredictionStepContainer.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import { OnboardingSelectCard } from "@/app/[locale]/onboarding/_components/OnboardingSelectCard"; +import { OnboardingButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingButtonContainer"; +import { PredictionAccuracy } from "@/app/[locale]/onboarding/_types/onboarding-funnel"; + +interface TimePredictionStepContainerProps { + predictionAccuracy?: PredictionAccuracy; + onSelect: (predictionAccuracy: PredictionAccuracy | undefined) => void; + onPrev: () => void; + onNext: () => void; +} + +const PREDICTION_ACCURACY_OPTIONS: PredictionAccuracy[] = [4, 3, 2, 1]; + +export const TimePredictionStepContainer = ({ + predictionAccuracy, + onSelect, + onPrev, + onNext, +}: TimePredictionStepContainerProps) => { + const t = useTranslations("Onboarding"); + + return ( + <> +
+
+

+ {t("timePrediction.title")} +

+

+ {t("timePrediction.description")} +

+
+ +
+ {PREDICTION_ACCURACY_OPTIONS.map((accuracy) => ( + + onSelect(predictionAccuracy === accuracy ? undefined : accuracy) + } + /> + ))} +
+
+ +
+ + +
+ + ); +}; diff --git a/apps/timo-web/app/[locale]/onboarding/_types/onboarding-funnel.ts b/apps/timo-web/app/[locale]/onboarding/_types/onboarding-funnel.ts new file mode 100644 index 00000000..65397303 --- /dev/null +++ b/apps/timo-web/app/[locale]/onboarding/_types/onboarding-funnel.ts @@ -0,0 +1,26 @@ +export type PredictionAccuracy = 1 | 2 | 3 | 4; + +// use-funnel의 CompareMergeContext가 keyof로 필드를 비교하는데, interface는 +// keyof가 예상대로 계산되지 않아 타입 추론이 깨짐 (반드시 type이어야 함) +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type OnboardingFunnelSteps = { + Language: { + language?: "ko" | "en"; + }; + TimePrediction: { + language: "ko" | "en"; + predictionAccuracy?: PredictionAccuracy; + }; + LifePattern: { + language: "ko" | "en"; + predictionAccuracy: PredictionAccuracy; + wakeUpTime?: string; + bedTime?: string; + }; + CalendarConnect: { + language: "ko" | "en"; + predictionAccuracy: PredictionAccuracy; + wakeUpTime: string; + bedTime: string; + }; +}; diff --git a/apps/timo-web/app/[locale]/onboarding/page.tsx b/apps/timo-web/app/[locale]/onboarding/page.tsx index e82bb921..f862cf18 100644 --- a/apps/timo-web/app/[locale]/onboarding/page.tsx +++ b/apps/timo-web/app/[locale]/onboarding/page.tsx @@ -1,3 +1,5 @@ +import { OnboardingFunnelContainer } from "@/app/[locale]/onboarding/_containers/OnboardingFunnelContainer"; + export default function OnboardingPage() { - return <>; + return ; } From b4d162e700d87c20cd6f880254cf65759d0715ef Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 01:59:07 +0900 Subject: [PATCH 06/10] =?UTF-8?q?fix(web):=20OnboardingStepButton=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EC=A4=91=EC=95=99=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pretendard 폰트 메트릭(line-height 1.5)으로 인한 시각적 상하 어긋남을 수정했습니다 - leading-none으로 line-height를 1로 줄이고 translate-y-px로 1px 보정했습니다 --- .../[locale]/onboarding/_components/OnboardingStepButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingStepButton.tsx b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingStepButton.tsx index d2be88f1..afda1b95 100644 --- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingStepButton.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingStepButton.tsx @@ -19,7 +19,7 @@ export const OnboardingStepButton = ({ step }: OnboardingStepButtonProps) => { > From d0142fbee980505acb60c46ca15132ab4eaef68f Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sat, 11 Jul 2026 03:42:33 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat(web):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=C2=B7=EC=98=A8=EB=B3=B4=EB=94=A9=20Lottie=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=ED=92=88=EC=A7=88=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인·온보딩 화면에 onboarding Lottie 애니메이션 추가 (반응형 lg/xl/2xl) - 바깥 래퍼를 div에서 main으로 변경 (시맨틱) - 로그인 태그라인 span → p 변경 (시맨틱) - OnboardingButtonProps, OnboardingSelectCardProps 불필요 export 제거 Co-Authored-By: Claude Sonnet 4.6 --- .../login/_containers/LoginContainer.tsx | 16 ++++++++++------ .../onboarding/_components/OnboardingButton.tsx | 2 +- .../_components/OnboardingSelectCard.tsx | 2 +- .../_containers/OnboardingFunnelContainer.tsx | 12 ++++++++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx index dd85ceec..b9b4c3f3 100644 --- a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx +++ b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx @@ -5,23 +5,27 @@ import Image from "next/image"; import { useTranslations } from "next-intl"; import { OnboardingGoogleButtonContainer } from "@/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer"; +import { LottiePlayer } from "@/components/lottie/LottiePlayer"; export const LoginContainer = () => { const t = useTranslations("Login"); return ( -
- {/* TODO: PR#131(feat/web/130-status-screen-lottie) 병합 후 온보딩 Lottie 그래픽으로 교체 */} -
+
+
Timo
- +

Less Chaos More Focus - +

{t("headline")} @@ -65,6 +69,6 @@ export const LoginContainer = () => {

-
+
); }; diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx index 7f4251ab..165c39e9 100644 --- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx @@ -5,7 +5,7 @@ import { } from "@repo/timo-design-system/icons"; import { cn } from "@repo/timo-design-system/utils"; -export interface OnboardingButtonProps { +interface OnboardingButtonProps { variant: "next" | "prev" | "start"; label: string; isActive?: boolean; diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx index 3f7c0a99..dad6ffe2 100644 --- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx @@ -1,6 +1,6 @@ import { cn } from "@repo/timo-design-system/utils"; -export interface OnboardingSelectCardProps { +interface OnboardingSelectCardProps { size: "sm" | "lg"; selected: boolean; label: string; diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx index ed6d5d3d..d37228d8 100644 --- a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx @@ -9,6 +9,7 @@ import { LanguageStepContainer } from "@/app/[locale]/onboarding/_containers/Lan import { LifePatternStepContainer } from "@/app/[locale]/onboarding/_containers/LifePatternStepContainer"; import { TimePredictionStepContainer } from "@/app/[locale]/onboarding/_containers/TimePredictionStepContainer"; import { OnboardingFunnelSteps } from "@/app/[locale]/onboarding/_types/onboarding-funnel"; +import { LottiePlayer } from "@/components/lottie/LottiePlayer"; const STEP_NUMBER: Record = { Language: 1, @@ -28,9 +29,12 @@ export const OnboardingFunnelContainer = () => { }); return ( -
- {/* TODO: PR#131(feat/web/130-status-screen-lottie) 병합 후 온보딩 Lottie 그래픽으로 교체 */} -
+
+
@@ -119,6 +123,6 @@ export const OnboardingFunnelContainer = () => { />
-
+ ); }; From 51bf3a41c296c28408c21ffc28281d03006092ee Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sun, 12 Jul 2026 16:09:03 +0900 Subject: [PATCH 08/10] =?UTF-8?q?style(web):=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=C2=B7=EC=85=80=EB=A0=89=ED=8A=B8=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=BB=AC=EB=9F=AC=20=ED=8A=B8=EB=9E=9C=EC=A7=80?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OnboardingButton과 OnboardingSelectCard에 transition-colors 클래스를 추가했습니다 Co-Authored-By: Claude Sonnet 4.6 --- .../app/[locale]/onboarding/_components/OnboardingButton.tsx | 2 +- .../[locale]/onboarding/_components/OnboardingSelectCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx index 165c39e9..fc491756 100644 --- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx @@ -53,7 +53,7 @@ export const OnboardingButton = ({ onClick={onClick} disabled={isDisabled} className={cn( - "flex items-center justify-center gap-2 rounded-[4px] px-4 py-2", + "flex items-center justify-center gap-2 rounded-[4px] px-4 py-2 transition-colors duration-200 ease-in-out", isActive ? "bg-timo-blue-300" : "bg-timo-gray-200", )} > diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx index dad6ffe2..3cff299a 100644 --- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx @@ -31,7 +31,7 @@ export const OnboardingSelectCard = ({ type="button" onClick={onClick} className={cn( - "flex rounded-[4px] border px-4 py-2 text-left", + "flex rounded-[4px] border px-4 py-2 text-left transition-colors duration-200 ease-in-out", selected ? "border-timo-blue-300 bg-timo-blue-50" : "border-timo-gray-500 bg-white", From 4d94be6fd460f7f39f648d39cbadc9cddc886398 Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sun, 12 Jul 2026 16:12:14 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor(web):=20main=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=EB=A5=BC=20section=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20console.log=20=EC=A0=9C=EA=B1=B0=20(#13?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoginContainer와 OnboardingFunnelContainer의 main 태그를 section으로 수정하고, OnboardingFunnelContainer의 디버그용 console.log를 제거했습니다 Co-Authored-By: Claude Sonnet 4.6 --- .../[locale]/login/_containers/LoginContainer.tsx | 4 ++-- .../_containers/OnboardingFunnelContainer.tsx | 13 +++---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx index b9b4c3f3..a50a3e8e 100644 --- a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx +++ b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx @@ -11,7 +11,7 @@ export const LoginContainer = () => { const t = useTranslations("Login"); return ( -
+
{

- + ); }; diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx index d37228d8..634d1301 100644 --- a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingFunnelContainer.tsx @@ -29,7 +29,7 @@ export const OnboardingFunnelContainer = () => { }); return ( -
+
{ !answers.bedTime ) return; - const payload: OnboardingFunnelSteps["CalendarConnect"] = { - language: answers.language, - predictionAccuracy: answers.predictionAccuracy, - wakeUpTime: answers.wakeUpTime, - bedTime: answers.bedTime, - }; - // TODO: 실제 제출 API 연동 + 완료 후 payload.language로 locale 리다이렉트 - console.log("[onboarding] submit payload", payload); + // TODO: 실제 제출 API 연동 + 완료 후 answers.language로 locale 리다이렉트 }} /> )} />
- + ); }; From 626f3c6e0ed5dc87b5d8d736d50d60ea2424ba5b Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Sun, 12 Jul 2026 16:32:15 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor(web):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20ari?= =?UTF-8?q?aLabel=20i18n=20=ED=82=A4=EB=A1=9C=20=EC=A0=84=ED=99=98=20(#134?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 하드코딩된 ariaLabel 문자열을 next-intl 번역 키로 교체했습니다. --- apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx | 2 +- apps/timo-web/messages/en.json | 1 + apps/timo-web/messages/ko.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx index a50a3e8e..d998201d 100644 --- a/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx +++ b/apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx @@ -15,7 +15,7 @@ export const LoginContainer = () => {
diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index 02829460..7602db49 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -66,6 +66,7 @@ "todoLimitLine2": "To add a new to-do, please complete an existing one." }, "Login": { + "animationLabel": "Login animation", "headline": "Manage your day with clear timeboxes.", "description": "Please log in to your timo account.", "connectLabel": "Log in with Google", diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index 39022bf0..6df0911b 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -66,6 +66,7 @@ "todoLimitLine2": "새로운 투두를 추가하려면 기존 투두를 완료해주세요." }, "Login": { + "animationLabel": "로그인 애니메이션", "headline": "흩어진 시간을, 실행 가능한 흐름으로.", "description": "timo 계정에 로그인하세요.", "connectLabel": "Google 로그인",