-
+
-
(
-
- 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;
- // TODO: 구글 캘린더 연동 단계 재활성화 시 아래 completeOnboarding 블록을 제거하고
- // history.push("CalendarConnect", { language: answers.language, predictionAccuracy: answers.predictionAccuracy, wakeUpTime: answers.wakeUpTime, bedTime: answers.bedTime }) 로 복구
- completeOnboarding(
- {
- data: {
- language: ONBOARDING_LANGUAGE_MAP[answers.language],
- predictionAccuracy: answers.predictionAccuracy,
- wakeUpTime: answers.wakeUpTime,
- bedTime: answers.bedTime,
- },
- },
- {
- onSuccess: () => {
- setOnboardingCompleted(true);
- router.replace(ROUTES.HOME, {
- locale: answers.language,
- });
- },
- onError: () => {
- setIsErrorToastOpen(true);
- },
- },
- );
- }}
- />
- )}
- CalendarConnect={({ history }) => (
- history.back()}
- onStart={() => {
- if (
- !answers.language ||
- !answers.predictionAccuracy ||
- !answers.wakeUpTime ||
- !answers.bedTime
- )
- return;
- completeOnboarding(
- {
- data: {
- language: ONBOARDING_LANGUAGE_MAP[answers.language],
- predictionAccuracy: answers.predictionAccuracy,
- wakeUpTime: answers.wakeUpTime,
- bedTime: answers.bedTime,
- },
- },
- {
- onSuccess: () => {
- setOnboardingCompleted(true);
- router.replace(ROUTES.HOME, {
- locale: answers.language,
- });
- },
- onError: () => {
- setIsErrorToastOpen(true);
- },
- },
- );
- }}
- />
- )}
- />
+
+
(
+
+ setAnswers((prev) => ({ ...prev, language }))
+ }
+ onNext={() => {
+ if (!answers.language) return;
+ history.push("TimePrediction", {
+ language: answers.language,
+ });
+ }}
+ />
+ )}
+ TimePrediction={({ history, index, context }) => (
+
+ setAnswers((prev) => ({ ...prev, predictionAccuracy }))
+ }
+ onPrev={() =>
+ index > 0
+ ? history.back()
+ : history.replace("Language", {
+ language: context.language,
+ })
+ }
+ onNext={() => {
+ if (!answers.language || !answers.predictionAccuracy)
+ return;
+ history.push("LifePattern", {
+ language: answers.language,
+ predictionAccuracy: answers.predictionAccuracy,
+ });
+ }}
+ />
+ )}
+ LifePattern={({ history, index, context }) => (
+
+ setAnswers((prev) => ({ ...prev, wakeUpTime }))
+ }
+ onSelectBedTime={(bedTime) =>
+ setAnswers((prev) => ({ ...prev, bedTime }))
+ }
+ onPrev={() =>
+ index > 0
+ ? history.back()
+ : history.replace("TimePrediction", context)
+ }
+ 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, index, context }) => (
+
+
+ setErrorToastMessage(t("calendarConnectFailed"))
+ }
+ onPrev={() =>
+ index > 0
+ ? history.back()
+ : history.replace("LifePattern", context)
+ }
+ onStart={() => {
+ completeOnboarding(
+ {
+ data: {
+ language: ONBOARDING_LANGUAGE_MAP[context.language],
+ predictionAccuracy: context.predictionAccuracy,
+ wakeUpTime: context.wakeUpTime,
+ bedTime: context.bedTime,
+ },
+ },
+ {
+ onSuccess: () => {
+ removeOnboardingAnswers();
+ setOnboardingCompleted(true);
+ router.replace(ROUTES.HOME, {
+ locale: context.language,
+ });
+ },
+ onError: () => {
+ setErrorToastMessage(t("onboardingSubmitFailed"));
+ },
+ },
+ );
+ }}
+ />
+
+ )}
+ />
+
diff --git a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer.tsx b/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer.tsx
deleted file mode 100644
index 821d5cb6..00000000
--- a/apps/timo-web/app/[locale]/onboarding/_containers/OnboardingGoogleButtonContainer.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-"use client";
-
-import { useTranslations } from "next-intl";
-
-import { OnboardingGoogleButton } from "@/app/[locale]/onboarding/_components/OnboardingGoogleButton";
-
-interface OnboardingGoogleButtonContainerProps {
- variant: "login" | "connectCalendar";
- isSelected?: boolean;
- onClick?: () => void;
-}
-
-export const OnboardingGoogleButtonContainer = ({
- variant,
- isSelected,
- onClick,
-}: OnboardingGoogleButtonContainerProps) => {
- const t = useTranslations("Onboarding");
-
- return (
-
- );
-};
diff --git a/apps/timo-web/app/[locale]/onboarding/_utils/onboarding-answers-storage.ts b/apps/timo-web/app/[locale]/onboarding/_utils/onboarding-answers-storage.ts
new file mode 100644
index 00000000..1e5a4613
--- /dev/null
+++ b/apps/timo-web/app/[locale]/onboarding/_utils/onboarding-answers-storage.ts
@@ -0,0 +1,54 @@
+import type { OnboardingFunnelSteps } from "@/app/[locale]/onboarding/_types/onboarding-funnel";
+
+const ONBOARDING_ANSWERS_KEY = "timo:onboarding-answers";
+
+type OnboardingAnswers = OnboardingFunnelSteps["CalendarConnect"];
+
+const isOnboardingAnswers = (value: unknown): value is OnboardingAnswers => {
+ if (typeof value !== "object" || value === null) return false;
+
+ const { language, predictionAccuracy, wakeUpTime, bedTime } = value as Record<
+ string,
+ unknown
+ >;
+ return (
+ (language === "ko" || language === "en") &&
+ (predictionAccuracy === 1 ||
+ predictionAccuracy === 2 ||
+ predictionAccuracy === 3 ||
+ predictionAccuracy === 4) &&
+ typeof wakeUpTime === "string" &&
+ typeof bedTime === "string"
+ );
+};
+
+export const getOnboardingAnswers = (): OnboardingAnswers | null => {
+ try {
+ const raw = window.sessionStorage.getItem(ONBOARDING_ANSWERS_KEY);
+ if (raw === null) return null;
+
+ const parsed: unknown = JSON.parse(raw);
+ return isOnboardingAnswers(parsed) ? parsed : null;
+ } catch {
+ return null;
+ }
+};
+
+export const setOnboardingAnswers = (answers: OnboardingAnswers): void => {
+ try {
+ window.sessionStorage.setItem(
+ ONBOARDING_ANSWERS_KEY,
+ JSON.stringify(answers),
+ );
+ } catch {
+ // 저장소 접근 불가 시 무시
+ }
+};
+
+export const removeOnboardingAnswers = (): void => {
+ try {
+ window.sessionStorage.removeItem(ONBOARDING_ANSWERS_KEY);
+ } catch {
+ // 저장소 접근 불가 시 무시
+ }
+};
diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingGoogleButton.tsx b/apps/timo-web/components/auth/AuthButton.tsx
similarity index 57%
rename from apps/timo-web/app/[locale]/onboarding/_components/OnboardingGoogleButton.tsx
rename to apps/timo-web/components/auth/AuthButton.tsx
index 12322e21..ce282b7b 100644
--- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingGoogleButton.tsx
+++ b/apps/timo-web/components/auth/AuthButton.tsx
@@ -1,21 +1,30 @@
-import googleLogo from "@repo/timo-design-system/assets/images/google-logo.svg";
import { cn } from "@repo/timo-design-system/utils";
import Image from "next/image";
-type OnboardingGoogleButtonVariant = "login" | "connectCalendar";
+export type AuthButtonVariant =
+ | "googleLogin"
+ | "googleCalendar"
+ | "appleCalendar";
-interface OnboardingGoogleButtonProps {
- variant: OnboardingGoogleButtonVariant;
+const AUTH_BUTTON_LOGO_SRC: Record
= {
+ googleLogin: "/images/google-logo.png",
+ googleCalendar: "/images/google-calendar.png",
+ appleCalendar: "/images/apple-calendar.png",
+};
+
+interface AuthButtonProps {
+ variant: AuthButtonVariant;
label: string;
isSelected?: boolean;
onClick?: () => void;
}
-export const OnboardingGoogleButton = ({
+export const AuthButton = ({
+ variant,
label,
isSelected = false,
onClick,
-}: OnboardingGoogleButtonProps) => {
+}: AuthButtonProps) => {
return (
diff --git a/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx b/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx
index 5a5d1573..80679715 100644
--- a/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx
+++ b/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx
@@ -17,6 +17,7 @@ export const TodayCalendarEventCard = ({
alt="Google Calendar"
width={14}
height={14}
+ unoptimized
/>