diff --git a/src/assets/kakao-icon.svg b/src/assets/icons/kakao_icon.svg similarity index 100% rename from src/assets/kakao-icon.svg rename to src/assets/icons/kakao_icon.svg diff --git a/src/assets/naver-icon.svg b/src/assets/icons/naver_icon.svg similarity index 100% rename from src/assets/naver-icon.svg rename to src/assets/icons/naver_icon.svg diff --git a/src/assets/icons/seeat_logo.svg b/src/assets/icons/seeat_logo.svg new file mode 100644 index 0000000..719d4fe --- /dev/null +++ b/src/assets/icons/seeat_logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/common/ProgressBar/ProgressBar.tsx b/src/components/common/ProgressBar/ProgressBar.tsx new file mode 100644 index 0000000..75c9355 --- /dev/null +++ b/src/components/common/ProgressBar/ProgressBar.tsx @@ -0,0 +1,29 @@ +interface ProgressBarProps { + currentStep: number; + totalSteps: number; +} + +const ProgressBar = ({ currentStep, totalSteps }: ProgressBarProps) => { + const percentage = (currentStep / totalSteps) * 100; + + return ( + <> + {/* 진행도 바 */} +
+
+
+
+
+ + {/* 진행 텍스트 */} +
+ {`${currentStep}/${totalSteps}`} +
+ + ); +}; + +export default ProgressBar; diff --git a/src/components/common/ToggleTab/ToggleTab.tsx b/src/components/common/ToggleTab/ToggleTab.tsx index 7840afa..0678d95 100644 --- a/src/components/common/ToggleTab/ToggleTab.tsx +++ b/src/components/common/ToggleTab/ToggleTab.tsx @@ -1,11 +1,11 @@ import type { ToggleTabProps } from './ToggleTab.types'; const ToggleTab = ({ options, selected, onSelect }: ToggleTabProps) => { - const selectedIndex = options.findIndex((option) => option === selected); + const selectedIndex = options.findIndex((option) => option.value === selected); return ( -
-
+
+
{/* Slider */}
{ {/* Buttons */} {options.map((option) => ( ))}
diff --git a/src/components/common/ToggleTab/ToggleTab.types.ts b/src/components/common/ToggleTab/ToggleTab.types.ts index 0ac4c3d..39c0e59 100644 --- a/src/components/common/ToggleTab/ToggleTab.types.ts +++ b/src/components/common/ToggleTab/ToggleTab.types.ts @@ -1,5 +1,11 @@ +export interface ToggleOption { + label: string; + value: string; +} + export interface ToggleTabProps { - options: string[]; + options: ToggleOption[]; selected: string; onSelect: (option: string) => void; + className?: string; } diff --git a/src/components/index.tsx b/src/components/index.tsx index a7500fd..b84824d 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -12,6 +12,7 @@ import TagSection from '@/components/review/TagSection'; import Modal from '@/components/common/Modal/Modal'; import Header from '@/components/common/Header/Header'; import ImagePreviewItem from './common/ImagePreview/ImagePreviewItem'; + export { InputField, Button, diff --git a/src/pages/onboarding/OnboardingGenrePage.tsx b/src/pages/onboarding/OnboardingGenrePage.tsx index 191681b..94bea90 100644 --- a/src/pages/onboarding/OnboardingGenrePage.tsx +++ b/src/pages/onboarding/OnboardingGenrePage.tsx @@ -1,37 +1,81 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import ToggleTab from '@/components/common/ToggleTab/ToggleTab'; -import { useOnboardingStore } from '@/store/useOnboardingStore'; +import { Button, Header } from '@/components'; +import { genreOptions } from '@/types/onboarding'; import type { GenreType } from '@/types/onboarding'; - -const genreOptions: GenreType[] = ['액션', '로맨스']; +import ProgressBar from '@/components/common/ProgressBar/ProgressBar'; const OnboardingGenrePage = () => { const navigate = useNavigate(); - const { setGenre } = useOnboardingStore(); - const [selectedGenre, setSelectedGenre] = useState(''); + const [selectedGenres, setSelectedGenres] = useState([]); - const handleNext = () => { - if (!selectedGenre) { - alert('하나 이상의 장르를 선택해주세요!'); - return; + const toggleGenre = (genre: GenreType) => { + const isSelected = selectedGenres.includes(genre); + if (isSelected) { + setSelectedGenres(selectedGenres.filter((g) => g !== genre)); + } else { + if (selectedGenres.length >= 5) return; + setSelectedGenres([...selectedGenres, genre]); } + }; + + const handleBack = () => { + navigate(-1); + }; - setGenre([selectedGenre]); - navigate('/signup/onboarding/theater'); + const handleNext = () => { + if (selectedGenres.length === 0) return; + navigate('/onboarding/theater'); }; return ( -
- setSelectedGenre(genre as GenreType)} // optional - /> - - +
+ {/* 상단 헤더 */} +
+ + {/* 진행도 바 */} + + + {/* 콘텐츠 영역 */} +
+ {/* 타이틀 */} +

좋아하는 장르를 선택해주세요

+

최대 5개까지 추가할 수 있어요.

+ + {/* 장르 선택 버튼 */} +
+ {genreOptions.map((genre) => { + const isSelected = selectedGenres.includes(genre); + return ( + + ); + })} +
+
+ + {/* 하단 버튼 */} +
+ +
); }; diff --git a/src/pages/onboarding/OnboardingNicknamePage.tsx b/src/pages/onboarding/OnboardingNicknamePage.tsx index 2de6f8c..06f3eaf 100644 --- a/src/pages/onboarding/OnboardingNicknamePage.tsx +++ b/src/pages/onboarding/OnboardingNicknamePage.tsx @@ -1,43 +1,70 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useOnboardingStore } from '@/store/useOnboardingStore'; +import { Button, Header } from '@/components'; +import Input from '@/components/common/Input/Input'; +import ProgressBar from '@/components/common/ProgressBar/ProgressBar'; const OnboardingNicknamePage = () => { - const [nickname, setNickname] = useState(''); - const { setNickname: saveNickname } = useOnboardingStore(); + const [input, setInput] = useState(''); const navigate = useNavigate(); const handleNext = () => { - if (!nickname.trim()) return; - saveNickname(nickname); - navigate('/signup/genre'); // 다음 단계로 이동 + navigate('/onboarding/genre'); + }; + + const handleBack = () => { + navigate(-1); }; return ( -
-

1/3

-

닉네임을 입력해주세요

-

다른 유저에게 보여질 이름이에요

- - setNickname(e.target.value)} - placeholder="닉네임을 입력하세요" - className="w-full max-w-sm px-4 py-3 rounded-md bg-[#1E1E1E] text-white border border-gray-600 placeholder-gray-500 focus:outline-none mb-6" - /> - - +
+ {/* 상단 헤더 */} +
+ + {/* 진행도 바 */} + + + {/* 콘텐츠 영역 */} +
+ {/* 타이틀 */} +

프로필을 만들어주세요

+ + {/* 프로필 이미지 (예시용 박스) */} +
+
+ 갤러리 아이콘 +
+
+ + {/* 닉네임 입력 */} +
+ +
+ +

10자 이내로 작성해주세요.

+
+ + {/* 하단 버튼 */} +
+ +
); }; diff --git a/src/pages/onboarding/OnboardingTheaterPage.tsx b/src/pages/onboarding/OnboardingTheaterPage.tsx index 27143f3..527f65e 100644 --- a/src/pages/onboarding/OnboardingTheaterPage.tsx +++ b/src/pages/onboarding/OnboardingTheaterPage.tsx @@ -1,73 +1,103 @@ -import { Button, ToggleTab } from '@/components'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useOnboardingStore } from '@/store/useOnboardingStore'; +import { Button, ToggleTab, Header } from '@/components'; +import { theaterData } from '@/types/onboarding'; import type { CinemaType, CinemaFormat } from '@/types/onboarding'; - -const theaterOptions: CinemaType[] = [ - 'CGV 강남', - '롯데시네마 홍대', - '메가박스 코엑스', - 'CGV 용산', - '메가박스 성수', -]; - -const formatOptions: CinemaFormat[] = ['IMAX', 'Dolby Cinema']; +import ProgressBar from '@/components/common/ProgressBar/ProgressBar'; const OnboardingTheaterPage = () => { const navigate = useNavigate(); const { selectedCinemas, - toggleCinema, - cinemaFormat, + setSelectedCinemas, setCinemaFormat, } = useOnboardingStore(); + const [selectedTab, setSelectedTab] = useState('IMAX'); + + const handleToggleTab = (tab: string) => { + const format = tab as CinemaFormat; + setSelectedTab(format); + setCinemaFormat(format); + }; + + const toggleTheater = (theater: CinemaType) => { + const isSelected = selectedCinemas.includes(theater); + if (isSelected) { + setSelectedCinemas(selectedCinemas.filter((t) => t !== theater)); + } else { + if (selectedCinemas.length >= 2) return; + setSelectedCinemas([...selectedCinemas, theater]); + } + }; + const handleNext = () => { if (selectedCinemas.length === 0) return; - // TODO: 온보딩 완료 후 홈 등으로 이동 - navigate('/'); + navigate('/signup/complete'); + }; + + const handleBack = () => { + navigate(-1); }; return ( -
-

자주 가는 영화관을 선택해주세요

-

최대 3개까지 선택할 수 있어요

+
+ {/* 상단 헤더 */} +
-
- {theaterOptions.map((theater) => { - const isSelected = selectedCinemas.includes(theater); - return ( - - ); - })} -
+ {/* 진행도 바 */} + + + {/* 콘텐츠 영역 */} +
+

자주 가는 영화관을 선택해주세요

+

최대 2개까지 선택할 수 있어요.

+ + -

선호 상영 형식

- setCinemaFormat(format as CinemaFormat)} - /> +
- +
+ {theaterData[selectedTab].map((theater) => { + const isSelected = selectedCinemas.includes(theater); + return ( + + ); + })} +
+
+ + {/* 하단 버튼 */} +
+ +
); }; diff --git a/src/pages/review/CinemaSelect.tsx b/src/pages/review/CinemaSelect.tsx index c3e36e7..c686637 100644 --- a/src/pages/review/CinemaSelect.tsx +++ b/src/pages/review/CinemaSelect.tsx @@ -33,10 +33,13 @@ export default function CinemaSelect() {
setSelectedTab(option as 'IMAX' | 'Dolby Cinema')} - /> + />
diff --git a/src/pages/signup/LoginPage.tsx b/src/pages/signup/LoginPage.tsx index f336723..0d39f1f 100644 --- a/src/pages/signup/LoginPage.tsx +++ b/src/pages/signup/LoginPage.tsx @@ -1,23 +1,33 @@ -import KakaoIcon from '@/assets/kakao-icon.svg'; -import NaverIcon from '@/assets/naver-icon.svg'; +import KakaoIcon from '@/assets/icons/kakao_icon.svg?react'; +import NaverIcon from '@/assets/icons/naver_icon.svg?react'; +import SeeatLogo from '@/assets/icons/seeat_logo.svg?react'; function LoginPage() { return ( -
-
-

그래픽,,들어갈 자리,,

+
+ {/* 중앙 그래픽 자리 */} +
+

그래픽,,들어갈 자리,,

+ + {/* SEEAT 로고 */} +
-
- + {/* 소셜 로그인 버튼 */} +
+
+ {/* 카카오 로그인 */} + - + {/* 네이버 로그인 */} + +
); diff --git a/src/pages/splash/SplashPage.tsx b/src/pages/splash/SplashPage.tsx index 4d06ade..6212dff 100644 --- a/src/pages/splash/SplashPage.tsx +++ b/src/pages/splash/SplashPage.tsx @@ -1,4 +1,3 @@ -// src/pages/splash/SplashPage.tsx import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -8,14 +7,14 @@ function SplashPage() { useEffect(() => { const timer = setTimeout(() => { navigate('/login'); - }, 2000); // 2초 뒤 이동 + }, 2000); - return () => clearTimeout(timer); // cleanup + return () => clearTimeout(timer); }, [navigate]); return ( -
-

SEEAT

+
+

SEEAT

); } diff --git a/src/routes/route.tsx b/src/routes/route.tsx index 660679b..cf36b12 100644 --- a/src/routes/route.tsx +++ b/src/routes/route.tsx @@ -27,7 +27,7 @@ const router = createBrowserRouter([ path: '/home', element: , }, - //리뷰 + // 리뷰 { path: '/review', children: [ @@ -39,12 +39,12 @@ const router = createBrowserRouter([ { path: 'form', element: }, ], }, - - //마이 + // 마이 { path: '/my', element: , }, + // 온보딩 { path: '/onboarding/nickname', element: , diff --git a/src/store/useOnboardingStore.ts b/src/store/useOnboardingStore.ts index 400d41b..aba82d9 100644 --- a/src/store/useOnboardingStore.ts +++ b/src/store/useOnboardingStore.ts @@ -12,13 +12,14 @@ interface OnboardingState { setCinemaFormat: (format: CinemaFormat) => void; genre: GenreType[]; setGenre: (genres: GenreType[]) => void; + setSelectedCinemas: (cinemas: CinemaType[]) => void; } export const useOnboardingStore = create((set, get) => ({ genre: [], setGenre: (genres) => set({ genre: genres }), - + nickname: '', selectedGenres: [], selectedCinemas: [], @@ -47,4 +48,6 @@ export const useOnboardingStore = create((set, get) => ({ }, setCinemaFormat: (format) => set({ cinemaFormat: format }), + + setSelectedCinemas: (cinemas) => set({ selectedCinemas: cinemas }), // ✅ 추가 })); \ No newline at end of file diff --git a/src/types/onboarding.ts b/src/types/onboarding.ts index aa4043f..d462454 100644 --- a/src/types/onboarding.ts +++ b/src/types/onboarding.ts @@ -1,4 +1,29 @@ -export type GenreType = '액션' | '로맨스' | 'SF' | '공포' | '코미디' | '다큐' | '애니메이션'; -export type CinemaType = 'CGV 강남' | '롯데시네마 홍대' | '메가박스 코엑스'| 'CGV 용산' | '메가박스 성수'; +export type GenreType = + | '액션' | '호러' | '스릴러' | '코미디' + | 'SF' | '로맨스' | '판타지' | '미스터리' + | '범죄' | '모험' | '전쟁' | '역사' + | '뮤지컬' | '애니메이션' | '드라마'; -export type CinemaFormat = 'IMAX' | 'Dolby Cinema'; \ No newline at end of file + export const genreOptions: GenreType[] = [ + '액션', '호러', '스릴러', '코미디', + 'SF', '로맨스', '판타지', '미스터리', + '범죄', '모험', '전쟁', '역사', + '뮤지컬', '애니메이션', '드라마', +]; + +export type CinemaType = string; + +export type CinemaFormat = 'IMAX' | 'Dolby'; + +export const theaterData: Record = { + IMAX: [], + Dolby: [ + '남양주현대아울렛 스페이스원', + '대구 신세계(동대구)', + '대전신세계아트앤사이언스', + '송도(트리플스트리트)', + '수원AK플라자(수원역)', + '안성스타필드', + '코엑스', + ], +}; \ No newline at end of file