Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@tanstack/react-query": "^5.85.3",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"framer-motion": "^12.23.12",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.8.2"
Expand Down
10 changes: 10 additions & 0 deletions apps/client/src/assets/onBoarding/background/onBoardingBg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/icons/chippi_bell.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/icons/chippi_morning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions apps/client/src/assets/onBoarding/icons/chippi_night.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/icons/dotori.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/client/src/assets/onBoarding/icons/header_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/story/macNotice.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/story/story1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/story/story2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/client/src/assets/onBoarding/story/story3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions apps/client/src/constants/alarms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import avatar1 from '@assets/onBoarding/icons/chippi_morning.svg';
import avatar2 from '@assets/onBoarding/icons/chippi_night.svg';
import avatar3 from '@assets/onBoarding/icons/chippi_bell.svg';

export interface AlarmType {
img: string;
title: string;
time: string;
}

Comment on lines +5 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

상수(프리셋) 변이 방지: 필드를 readonly로 고정하세요

컴포넌트에서 AlarmsType[2].time = ...와 같이 변경 시도가 보입니다. 타입 레벨에서 변이를 차단하면 의도치 않은 전역 상태 오염을 예방할 수 있습니다.

-export interface AlarmType {
-  img: string;
-  title: string;
-  time: string;
-}
+export interface AlarmType {
+  readonly img: string;
+  readonly title: string;
+  readonly time: string;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface AlarmType {
img: string;
title: string;
time: string;
}
export interface AlarmType {
readonly img: string;
readonly title: string;
readonly time: string;
}
🤖 Prompt for AI Agents
In apps/client/src/constants/alarms.ts around lines 5 to 10, the AlarmType
interface allows mutation of its properties which enables code like
AlarmsType[2].time = ..., so change the interface fields to be readonly (e.g.,
mark img, title, time as readonly) to enforce immutability at the type level;
also ensure any exported presets use a readonly array type
(ReadonlyArray<AlarmType> or declare the constant with an immutable assertion)
so consumers cannot mutate elements.

export const AlarmsType: AlarmType[] = [
{ img: avatar1, title: '아침형 치삐', time: '오전 9시' },
{ img: avatar2, title: '저녁형 치삐', time: '오후 8시' },
{ img: avatar3, title: '사용자 설정', time: '' },
];
Comment on lines +11 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

프리셋은 ‘상태’가 아닙니다: 사용자 선택 값(시간)은 컴포넌트/스토어 상태로 관리하세요

모듈 상수(AlarmsType)를 직접 변경하면 전역 공유로 인해 리렌더/다중 인스턴스/스토리북 병렬 실행 시 예기치 않은 사이드이펙트가 납니다. 프리셋은 불변 상수로 두고, 선택/수정은 로컬 상태(useState)나 전역 스토어(Zustand/Redux 등)로 분리하세요.

다음 명령으로 상수 변이(write) 사용처를 확인해 보세요:


🏁 Script executed:

#!/bin/bash
# AlarmsType 요소의 time을 대입(=)하는 코드 탐지
rg -nP 'AlarmsType\s*\[\s*\d+\s*\]\s*\.time\s*=' -C2
rg -nP 'AlarmsType\s*\[\s*.*?\s*\]\s*\.time\s*=' -C2

Length of output: 2277


AlarmsType은 불변으로 유지 — 전역 상수 직접 변이 제거하고 사용자 선택은 상태로 관리하세요.

apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx에서 AlarmsType[2].time에 직접 할당이 발견됩니다 (라인 62: AlarmsType[2].time = formatted, 라인 67: AlarmsType[2].time = ''). 프리셋(AlarmsType)은 불변 상수로 두고, 사용자 선택/수정은 컴포넌트 useState 또는 전역 스토어(Zustand/Redux 등)로 이동해 저장·갱신하세요.

🤖 Prompt for AI Agents
In apps/client/src/constants/alarms.ts around lines 11-15, the AlarmsType array
is intended as a constant preset but is currently being mutated elsewhere; stop
direct mutation and treat this file as read-only. In
apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx (where you
currently do AlarmsType[2].time = formatted at line 62 and AlarmsType[2].time =
'' at line 67) introduce a local state (useState) or use your global store
(Zustand/Redux) to hold the user-selected time, initialize it from
AlarmsType[2].time, update that state when the user picks or clears a time, and
read from that state for rendering instead of writing back into AlarmsType;
ensure AlarmsType remains unchanged.

44 changes: 44 additions & 0 deletions apps/client/src/constants/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import story1 from '@assets/onBoarding/story/story1.svg';
import story2 from '@assets/onBoarding/story/story2.svg';
import story3 from '@assets/onBoarding/story/story3.svg';

export interface Story {
img: string;
text: React.ReactNode;
progress: number;
}
Comment on lines +5 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

React.ReactNode 직접 참조는 타입 오류 소지 → 타입 임포트로 전환.

모듈 스코프에서 React 네임스페이스를 직접 참조하면 TS 설정에 따라 “Cannot find namespace 'React'”가 날 수 있습니다. 안전하게 타입만 임포트해 사용하세요.

+import type { ReactNode } from 'react';
 
 export interface Story {
   img: string;
-  text: React.ReactNode;
+  text: ReactNode;
   progress: number;
+  alt: string;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface Story {
img: string;
text: React.ReactNode;
progress: number;
}
import type { ReactNode } from 'react';
export interface Story {
img: string;
text: ReactNode;
progress: number;
alt: string;
}
🤖 Prompt for AI Agents
In apps/client/src/constants/stories.tsx around lines 5 to 9, the interface
references React.ReactNode directly which can cause "Cannot find namespace
'React'" errors; change it to import the type and use it: add "import type {
ReactNode } from 'react';" at the top of the file and replace React.ReactNode
with ReactNode in the Story interface.


export const stories: Story[] = [
{
img: story1,
text: (
<>
깊고 신비한 숲에는 지식 나무가 있어요. <br />
지식 나무는 사람들의 잊힌 기록을 도토리 씨앗으로 바꾼답니다.
</>
),
progress: 30,
},
{
img: story2,
text: (
<>
당신이 정보를 읽고 활용하는 것을 양분삼아, <br />
지식 나무에는 맛있는 도토리 열매가 열려요.
</>
),
progress: 60,
},
{
img: story3,
text: (
<>
다람쥐 치삐는 정보를 활용하지 못해 아직 도토리 만개 숲에 도착하지 못하고
있어요.
<br />
도토리를 모아 치삐가 숲에 닿을 수 있도록 도와주세요!
</>
),
progress: 100,
}
];
13 changes: 12 additions & 1 deletion apps/client/src/pages/onBoarding/OnBoarding.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import onBoardingBg from '@assets/onBoarding/background/onBoardingBg.svg';
import Header from './components/header/Header';
import MainCard from './components/funnel/MainCard';
const OnBoarding = () => {
return <div>OnBoarding</div>;
return (
<div
className={`relative flex h-screen w-screen items-center justify-center bg-cover bg-center bg-no-repeat`}
style={{ backgroundImage: `url(${onBoardingBg})` }}
>
<Header />
<MainCard />
</div>
);
};

export default OnBoarding;
78 changes: 78 additions & 0 deletions apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { cva } from 'class-variance-authority';
import TimePicker from '../timePicker/TimePicker';
import { AlarmsType } from '@constants/alarms';
import { useState } from 'react';
interface AlarmBoxProps {
select: 1 | 2 | 3;
isDisabled: boolean;
onClick?: () => void;
}

const boxStyle = cva(
'flex h-[22.4rem] w-[18rem] flex-col items-center rounded-[1.2rem] px-[3.9rem] pb-[2.6rem] pt-[3.6rem] cursor-pointer transition',
{
variants: {
disabled: {
true: 'border-main400 bg-main100 border',
false: 'bg-white border border-transparent hover:border-main300',
},
},
defaultVariants: { disabled: false },
}
);
Comment on lines +11 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

disabled 변형에서도 커서/클릭이 유지됨

베이스 클래스에 cursor-pointer가 있어 disabled 상태에서도 포인터 커서가 유지됩니다. 비활성화 시 커서/포인터 이벤트를 비활성화하세요.

-const boxStyle = cva(
-  'flex h-[22.4rem] w-[18rem] flex-col items-center rounded-[1.2rem] px-[3.9rem] pb-[2.6rem] pt-[3.6rem] cursor-pointer transition',
+const boxStyle = cva(
+  'flex h-[22.4rem] w-[18rem] flex-col items-center rounded-[1.2rem] px-[3.9rem] pb-[2.6rem] pt-[3.6rem] transition',
   {
     variants: {
       disabled: {
-        true: 'border-main400 bg-main100 border',
-        false: 'bg-white border border-transparent hover:border-main300',
+        true: 'border-main400 bg-main100 border cursor-not-allowed pointer-events-none',
+        false: 'bg-white border border-transparent hover:border-main300 cursor-pointer',
       },
     },
     defaultVariants: { disabled: false },
   }
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const boxStyle = cva(
'flex h-[22.4rem] w-[18rem] flex-col items-center rounded-[1.2rem] px-[3.9rem] pb-[2.6rem] pt-[3.6rem] cursor-pointer transition',
{
variants: {
disabled: {
true: 'border-main400 bg-main100 border',
false: 'bg-white border border-transparent hover:border-main300',
},
},
defaultVariants: { disabled: false },
}
);
const boxStyle = cva(
'flex h-[22.4rem] w-[18rem] flex-col items-center rounded-[1.2rem] px-[3.9rem] pb-[2.6rem] pt-[3.6rem] transition',
{
variants: {
disabled: {
true: 'border-main400 bg-main100 border cursor-not-allowed pointer-events-none',
false: 'bg-white border border-transparent hover:border-main300 cursor-pointer',
},
},
defaultVariants: { disabled: false },
}
);
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx around lines
18 to 29, the base class includes cursor-pointer so the pointer cursor and
clickability remain when the component is disabled; update the cva variants so
cursor-pointer is applied only for the non-disabled variant and add disabled
styles like cursor-default and pointer-events-none to the disabled:true variant
(and remove hover styles for disabled) so the element looks and behaves
non-interactive when disabled.


const AlarmBox = ({ select, isDisabled, onClick }: AlarmBoxProps) => {
const [showPicker, setShowPicker] = useState(false);
const getTimePicker = ({ hour, minute, meridiem }: { hour: string; minute: string; meridiem: string }) => {
const formatted = `${meridiem} ${hour}:${minute}`;
AlarmsType[2].time = formatted;
setShowPicker(false);
// 이거 나중에 api 연결때 쓸려고 표시한거.. 그떄 지우겠듬여 console.log('저장된 사용자 알람:', AlarmsType[2].time);
}
return (
<div
className={boxStyle({ disabled: isDisabled })}
onClick={() => {
if (select === 3 && isDisabled) {
setShowPicker(true);
}
onClick?.();
}}
>
<img src={AlarmsType[select - 1].img} alt="chippi" />
<p
className={`sub3-sb ${
isDisabled ? 'text-main500' : 'text-font-black-1'
}`}
>
{AlarmsType[select - 1].title}
</p>

{select <= 2 && (
<p className="caption2-m text-font-gray-3">
{AlarmsType[select - 1].time}
</p>
)}

{select === 3 && isDisabled && (
<>
{AlarmsType[2].time && (
<p className="caption2-m text-font-gray-3">{AlarmsType[2].time}</p>
)}

{showPicker && (
<TimePicker
onSave={getTimePicker}
onCancel={() => {
AlarmsType[2].time = '';
}}
onClick={(e) => e.stopPropagation()}
/>
)}
</>
)}
</div>
);
};

export default AlarmBox;
137 changes: 137 additions & 0 deletions apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 step을 컴포넌트로 잘 분리해주셨는데 해당 step에 관련된 코드가 각 step안이 아니라 해당 MainCard에 있는 것 같아요. 관련 코드를 step안으로 넣어주고, 책임을 확실히 해주는 것이 어떨까요?

현재는 step에 해당하는 코드가 step 컴포넌트가 아니라 해당 MainCard안에 있어서 조금 어색한 느낌이 드는 것 같아요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Progress, Button } from '@pinback/design-system/ui';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import StoryStep from './step/StoryStep';
import AlarmStep from './step/AlarmStep';
import MacStep from './step/MacStep';
import FinalStep from './step/FinalStep';
import { cva } from 'class-variance-authority';
const stepProgress = [{ progress: 30 }, { progress: 60 }, { progress: 100 }];

const variants = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variants 네이밍을 조금 더 구체화해주세요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slideIn~Out 느낌으로 네이밍 구체화 했습니당

slideIn: (direction: number) => ({
x: direction > 0 ? 200 : -200,
opacity: 0,
}),
slideCenter: { x: 0, opacity: 1 },
slideOut: (direction: number) => ({
x: direction > 0 ? -200 : 200,
opacity: 0,
}),
};
const CardStyle = cva(
'bg-white-bg flex h-[54.8rem] w-[63.2rem] flex-col items-center justify-between rounded-[2.4rem] pt-[3.2rem]',
{
variants: {
overflow: {
true: 'overflow-visible',
false: 'overflow-hidden',
},
},
defaultVariants: { overflow: false },
}
);
const MainCard = () => {
const [step, setStep] = useState(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제 개인적인 생각으로는 stepnumber로 관리하게 되면 해당 step이 무엇인지 각 컴포넌트에서 파악하기가 쉽지 않다고 생각해요.

예를 들어

 <Button
    variant="primary"
    size="medium"
    isDisabled={step === 6}
    className="ml-auto w-[4.8rem]"
    onClick={nextStep}
>

이런 코드도 step이 6이라는 것을 해당 코드만 보고는 쉽게 알지 못한다고 생각해요.
따라서 stepstring 형식으로, 더 타입을 안정화하려면 union으로 'mac', 'story' 등으로 가드해두면 자동 완성/안정성도 좋아지고 가독성 측면에서 훨씬 좋은 코드가 될 것 같다고 생각해요.

재림님은 이 부분에 대해서 어떻게 생각하시나요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안그래도 구현할때, step 몇이 어떤단계인지 저도 헷갈렸었는데,, 너무 좋은 방향인 것 같아요!!
union으로 명시화해두겠습니당

const [direction, setDirection] = useState(0);
const [alarmSelected, setAlarmSelected] = useState<1 | 2 | 3>(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1,2,3 보다 더 의미 있는 string 값으로 가독성을 높이는 것은 어떨까요?

const [isMac, setIsMac] = useState(false);

useEffect(() => {
const ua = navigator.userAgent.toLowerCase();
if (ua.includes('mac os') || ua.includes('iphone') || ua.includes('ipad')) {
setIsMac(true);
}
}, []);
const renderStep = () => {
switch (step) {
case 0:
case 1:
case 2:
return <StoryStep step={step as 0 | 1 | 2} />;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StoryStep props interface에서

interface StoryStepProps {
  step: 0 | 1 | 2;
}

union으로 구체화 해주셔서 잘 타입이 추론 되는 것 같아요! as로 타입 단언을 사용하신 특별한 이유가 있을까요?


image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 스텝꼬일까봐
step이 0,1,2 중 하나인 걸 보장할때만 하도록 타입 단언해두긴 했습니다.! 너무 불필요한 단언이라면 뺴두겠습니다 :)

case 3:
return <AlarmStep selected={alarmSelected} setSelected={setAlarmSelected} />;
case 4:
if (isMac) return <MacStep />;
return <FinalStep />;
case 5:
if (isMac) return <FinalStep />;
return null;
default:
Comment on lines +56 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

비-Apple(Windows 등) 환경에서 step 5가 공백으로 렌더되고, ‘다음’을 두 번 눌러야 홈으로 이동하는 버그

step 5가 !isMac일 때 null을 반환하고, 리다이렉트는 step 5에서만 발생합니다. step 4에서 step 5로 이동(첫 클릭) 후 빈 화면이 표시되고, 한 번 더 눌러야 이동됩니다. 아래처럼 비Mac은 step 4에서 바로 리다이렉트하고, 안전망으로 step 5는 항상 FinalStep을 렌더하도록 수정하세요.

@@
-      case 5:
-        if (isMac) return <FinalStep />;
-        return null;
+      case 5:
+        return <FinalStep />;
@@
-  const nextStep = () => {
+  const nextStep = () => {
     if (step === 3) {
       // 이거 이후에 api 붙일 자리 표시임! console.log('선택된 알람:', AlarmsType[alarmSelected - 1].time);
     }
-    if (step < 5) {
-      setDirection(1);
-      setStep((prev) => prev + 1);
-    } else if (step === 5) {
-      window.location.href = '/';
-    }
+    const shouldRedirect = (!isMac && step === 4) || (isMac && step === 5);
+    if (shouldRedirect) {
+      window.location.href = '/';
+      return;
+    }
+    setDirection(1);
+    setStep((prev) => prev + 1);
   };

Also applies to: 65-75

🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx around lines
56-60 (and similarly 65-75), the switch currently returns null for case 5 on
non-Mac causing a blank render and requiring a second click to trigger the
redirect; change the flow so non-Mac users are redirected earlier and case 5
never returns null. Specifically: update case 4 to perform the non-Mac redirect
immediately (same redirect logic used for Mac) so clicking Next from step 4
moves straight to home, and change case 5 to always render FinalStep as a safety
net (do not return null). Apply the same fixes in the repeated block at lines
65-75.

return <FinalStep />;
}
};

const nextStep = () => {
if (step === 3) {
// 이거 이후에 api 붙일 자리 표시임! console.log('선택된 알람:', AlarmsType[alarmSelected - 1].time);
}
if (step < 5) {
setDirection(1);
setStep((prev) => prev + 1);
} else if (step === 5) {
window.location.href = '/';
}
};

const prevStep = () => {
if (step > 0) {
setDirection(-1);
setStep((prev) => prev - 1);
}
};
Comment on lines +65 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step string 관련 의논을 해보고 필요하다면 로직 관련은 커스텀 훅으로 분리하는 것도 좋을 것 같아요!


return (
<div className={CardStyle({ overflow: step === 3 && alarmSelected === 3 })}>
{step < 3 && (
<Progress
value={stepProgress[step].progress}
variant="profile"
className="w-[15.6rem]"
/>
)}

<div className="relative flex h-full w-full items-center justify-center">
<AnimatePresence custom={direction} mode="wait">
<motion.div
key={step}
custom={direction}
variants={variants}
initial="slideIn"
animate="slideCenter"
exit="slideOut"
transition={{ duration: 0.4 }}
className="flex h-full flex-col items-center"
>
{renderStep()}
</motion.div>
</AnimatePresence>
</div>

<div className="mb-[4.8rem] mt-[1.2rem] flex w-full justify-between px-[3.2rem]">
{step < 4 && (
<Button
variant="primary"
size="medium"
isDisabled={step === 0}
className="w-[4.8rem]"
onClick={prevStep}
>
이전
</Button>
)}
<Button
variant="primary"
size="medium"
isDisabled={step === 6}
className="ml-auto w-[4.8rem]"
onClick={nextStep}
>
다음
</Button>
</div>
Comment on lines +123 to +132
Copy link

@coderabbitai coderabbitai bot Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

다음 버튼 비활성화 조건 버그(step === 6) 수정

step은 0–5 범위로 운용되므로 현재 조건은 영원히 false입니다. 의미 없는 prop 전달이므로 제거하거나 전환 중(isAnimating) 비활성화로 교체하세요.

전환 중 비활성화로 교체 예:

-          isDisabled={step === 6}
+          isDisabled={isAnimating}

단순 제거 예:

-          isDisabled={step === 6}
+          // isDisabled 제거
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="primary"
size="medium"
isDisabled={step === 6}
className="ml-auto w-[4.8rem]"
onClick={nextStep}
>
다음
</Button>
</div>
<Button
variant="primary"
size="medium"
isDisabled={isAnimating}
className="ml-auto w-[4.8rem]"
onClick={nextStep}
>
다음
</Button>
</div>
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx around lines
123 to 132 the Button has isDisabled={step === 6} which is always false because
step ranges 0–5; remove this meaningless prop or replace it with a real
animation/transition flag (e.g., isDisabled={isAnimating}) so the Next button is
disabled during transitions; update the component to either drop isDisabled
entirely or wire it to the existing isAnimating state (ensure isAnimating is
defined and passed down or managed in this component).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 한번 확인해주세요-!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjangminii 네, 확인했습니다! CodeRabbit이 지적한 버그가 맞습니다.

문제점:

  • isDisabled={step === 6} 조건이 의미없습니다
  • step 상태는 0-5 범위에서만 동작하므로 step === 6은 항상 false입니다
  • 따라서 "다음" 버튼이 실제로는 비활성화되지 않습니다

수정 방안:

  1. 단순 제거 (가장 간단):
<Button
  variant="primary"
  size="medium"
  className="ml-auto w-[4.8rem]"
  onClick={nextStep}
>
  다음
</Button>
  1. 애니메이션 중 비활성화 (더 나은 UX):
// 상태 추가
const [isAnimating, setIsAnimating] = useState(false);

// 애니메이션 처리
const nextStep = () => {
  setIsAnimating(true);
  // ... 기존 로직
  setTimeout(() => setIsAnimating(false), 400); // 애니메이션 duration과 맞춤
};

// 버튼에서 사용
<Button
  variant="primary"
  size="medium"
  isDisabled={isAnimating}
  className="ml-auto w-[4.8rem]"
  onClick={nextStep}
>
  다음
</Button>

어떤 방향으로 수정하실 건가요?

</div>
);
};

export default MainCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import dotori from '@assets/onBoarding/icons/dotori.svg';
import AlarmBox from '../AlarmBox';
interface AlarmStepProps {
selected: 1 | 2 | 3;
setSelected: (n: 1 | 2 | 3) => void;
}
const AlarmStep = ({ selected, setSelected }: AlarmStepProps) => {
return (
<div className="flex flex-col items-center justify-between">
<img src={dotori} className="mb-[1.2rem]" alt="dotori" />
<p className="head2 text-font-black-1">
도토리 찾으러 갈 시간을 정해볼까요?
</p>
<p className="body2-m text-font-gray-3 mb-[4.3rem] mt-[0.8rem] text-center">
매일 지정한 시간에 저장한 북마크를 리마인드해드려요
</p>

<div className="mb-[2rem] flex w-full items-center justify-center gap-[1.4rem]">
{([1, 2, 3] as const).map((n) => (
<AlarmBox
key={n}
select={n}
isDisabled={selected === n}
onClick={() => setSelected(n)}
/>
))}
</div>
</div>
);
};

export default AlarmStep;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import dotori from '@assets/onBoarding/icons/dotori.svg';
const FinalStep = () => {
return (
<div className="flex h-full flex-col items-center">
<img src={dotori} className="mb-[1.2rem]" alt="dotori" />
<p className="head2 text-font-black-1">
도토리 찾으러 갈 시간을 정해볼까요?
</p>
</div>
);
};
export default FinalStep;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import MacNotice from '@assets/onBoarding/story/macNotice.svg';
import dotori from '@assets/onBoarding/icons/dotori.svg';
const MacStep = () => {
return (
<div className="flex h-full flex-col items-center">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

절대 위치 요소가 기준 요소 없이 배치됩니다 — 컨테이너에 relative 추가 권장

하단 이미지가 absolute인데 상위에 포지셔닝 컨텍스트가 없어 레이아웃이 흔들릴 수 있습니다.

다음으로 수정해 주세요:

-    <div className="flex h-full flex-col items-center">
+    <div className="relative flex h-full flex-col items-center">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex h-full flex-col items-center">
<div className="relative flex h-full flex-col items-center">
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/step/MacStep.tsx around
line 5, the container <div className="flex h-full flex-col items-center"> is
missing a positioning context while it contains absolutely positioned children;
add the positioning context by adding the "relative" class to this container
(e.g., className="flex h-full flex-col items-center relative") so the
absolute-positioned image is anchored to this element.

<img src={dotori} className="mb-[1.2rem]" alt="dotori" />
<p className="head2 text-font-black-1">
도토리 찾으러 갈 시간을 정해볼까요?
</p>
<p className="body2-m text-font-gray-3 mb-[4.3rem] mt-[0.8rem] text-center">
Mac 사용자는 추가 알림 설정을 진행해 주세요.
</p>
<img src={MacNotice} className="absolute -bottom-[10.4rem]" alt="mac" />
</div>
);
};
export default MacStep;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { stories } from '@constants/stories';

interface StoryStepProps {
step: 0 | 1 | 2;
}

const StoryStep = ({ step }: StoryStepProps) => {
const story = stories[step];

return (
<div className="flex flex-col items-center">
<img
src={story.img}
className="mb-[1.6rem] mt-[2.4rem] w-[31.2rem]"
alt="onboarding"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step에 따라 이미지가 달라질 수 있으니 alt 속성에도 onboardingstep 관련 명시도 추가해주면 좋을 것 같아요!

/>
<p className="sub4-sb text-center text-black">{story.text}</p>
</div>
);
};

export default StoryStep;
Loading
Loading