-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat]회원가입 온보딩 페이지 구현 #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "feat/#19/\uD68C\uC6D0\uAC00\uC785-\uC628\uBCF4\uB529-\uD398\uC774\uC9C0-\uAD6C\uD604"
Changes from 9 commits
347108e
daa29f3
855ed93
4e7e8cf
e77e9b3
416ab8b
c34270c
5a6f23f
fac1024
038ac02
1e2a564
c45faff
be41cfb
9238002
e5195e6
c67cc05
2bc1c65
869141e
78ba203
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,94 @@ | ||
| import { useState } from 'react'; | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import ToggleTab from '@/components/common/ToggleTab/ToggleTab'; | ||
| import { useOnboardingStore } from '@/store/useOnboardingStore'; | ||
| import Header from '@/components/common/Header/Header'; | ||
| import Button from '@/components/common/Button'; | ||
| import type { GenreType } from '@/types/onboarding'; | ||
|
|
||
| const genreOptions: GenreType[] = ['액션', '로맨스']; | ||
| const genreOptions = [ | ||
| '액션', '호러', '스릴러', '코미디', | ||
| 'SF', '로맨스', '판타지', '미스터리', | ||
| '범죄', '모험', '전쟁', '역사', | ||
| '뮤지컬', '애니메이션', '드라마', | ||
| ] as GenreType[]; | ||
|
||
|
|
||
| const OnboardingGenrePage = () => { | ||
| const navigate = useNavigate(); | ||
| const { setGenre } = useOnboardingStore(); | ||
| const [selectedGenre, setSelectedGenre] = useState<GenreType | ''>(''); | ||
| const [selectedGenres, setSelectedGenres] = useState<GenreType[]>([]); | ||
|
|
||
| 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]); | ||
| } | ||
| }; | ||
|
|
||
| setGenre([selectedGenre]); | ||
| navigate('/signup/onboarding/theater'); | ||
| const handleBack = () => { | ||
| navigate(-1); | ||
| }; | ||
|
|
||
| const handleNext = () => { | ||
| if (selectedGenres.length === 0) return; | ||
| navigate('/onboarding/theater'); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-8 items-center justify-center w-full h-screen px-6 py-8"> | ||
| <ToggleTab | ||
| options={genreOptions} | ||
| selected={selectedGenre} | ||
| onSelect={(genre) => setSelectedGenre(genre as GenreType)} // optional | ||
| /> | ||
|
|
||
| <button | ||
| onClick={handleNext} | ||
| className="bg-white text-black hover:opacity-90 w-full max-w-sm" | ||
| > | ||
| 다음 | ||
| </button> | ||
| <div className="min-h-screen bg-gray-900 text-white w-full max-w-[375px] mx-auto relative pb-32"> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀드렸듯이, 이 부분에서도 배경색, 너비, 높이 관련 속성은 제거해주셔도 괜찮을 것 같아요~~ |
||
| {/* 상단 헤더 */} | ||
| <Header title="" onBackClick={handleBack} /> | ||
|
||
|
|
||
| {/* 진행도 바 */} | ||
| <div className="mt-6 mb-2"> | ||
| <div className="h-2 w-full bg-gray-800 relative"> | ||
| <div className="absolute left-0 top-0 h-full bg-red-400" style={{ width: '66.66%' }} /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* 콘텐츠 영역 */} | ||
| <div className="px-6 mt-6"> | ||
| {/* 진행도 */} | ||
| <div className="text-title-1 text-white mb-3">2/3</div> | ||
|
|
||
| {/* 타이틀 */} | ||
| <h1 className="text-title-2 text-white mb-1">좋아하는 장르를 선택해주세요</h1> | ||
| <p className="text-caption-2 text-red-300 mb-6">최대 5개까지 추가할 수 있어요.</p> | ||
|
|
||
| {/* 장르 선택 버튼 */} | ||
| <div className="flex flex-wrap gap-3 mb-20"> | ||
| {genreOptions.map((genre) => { | ||
| const isSelected = selectedGenres.includes(genre); | ||
| return ( | ||
| <Button | ||
| key={genre} | ||
| variant="secondary-assistive" | ||
| selected={isSelected} | ||
| onClick={() => toggleGenre(genre)} | ||
| fontType="body-1" | ||
| className="px-4 py-1" | ||
| > | ||
| {genre} | ||
| </Button> | ||
| ); | ||
| })} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* 하단 버튼 */} | ||
| <div className="fixed bottom-8 left-1/2 -translate-x-1/2 w-full max-w-[375px] px-6"> | ||
| <Button | ||
| onClick={handleNext} | ||
| disabled={selectedGenres.length === 0} | ||
| variant="primary" | ||
| color="red" | ||
| size="lg" | ||
| fontType="title-3" | ||
| className="w-full" | ||
| > | ||
| 다음 | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 온보딩 페이지에서 공통되는 속성들이 많은데 지민님이 올려주신 pr #23 에 제가 남겼던 리뷰 참고해서 라우팅 방식을 수정하는 것도 좋을 것 같다는 생각이 듭니다~~ 참고해주시면 좋을 것 같아요~ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,78 @@ | ||
| import { useState } from 'react'; | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import { useOnboardingStore } from '@/store/useOnboardingStore'; | ||
| import { Button } from '@/components'; | ||
| import Input from '@/components/common/Input/Input'; | ||
| import Header from '@/components/common/Header/Header'; | ||
|
||
|
|
||
| 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'); // 다음 단계로 이동 | ||
| console.log('닉네임:', input); | ||
| navigate('/onboarding/genre'); | ||
| }; | ||
|
|
||
| const handleBack = () => { | ||
| navigate(-1); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="h-screen bg-[#121212] text-white flex flex-col items-center px-6 pt-20"> | ||
| <p className="text-[#E31221] text-sm mb-2">1/3</p> | ||
| <h1 className="text-xl font-semibold mb-2">닉네임을 입력해주세요</h1> | ||
| <p className="text-sm text-gray-400 mb-8">다른 유저에게 보여질 이름이에요</p> | ||
|
|
||
| <input | ||
| type="text" | ||
| value={nickname} | ||
| onChange={(e) => 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" | ||
| /> | ||
|
|
||
| <button | ||
| onClick={handleNext} | ||
| disabled={!nickname.trim()} | ||
| className={`w-full max-w-sm py-3 rounded-md font-semibold ${ | ||
| nickname.trim() | ||
| ? 'bg-[#E31221] text-white' | ||
| : 'bg-gray-600 text-gray-400 cursor-not-allowed' | ||
| }`} | ||
| > | ||
| 다음 | ||
| </button> | ||
| <div className="min-h-screen w-full max-w-[375px] mx-auto bg-gray-900 text-white relative pb-32"> | ||
| {/* 상단 헤더 */} | ||
| <Header title="" onBackClick={handleBack} /> | ||
|
|
||
| {/* 진행도 바 */} | ||
| <div className="mt-6 mb-2"> | ||
| <div className="h-2 w-full bg-gray-800 relative"> | ||
| <div className="absolute left-0 top-0 h-full bg-red-400" style={{ width: '33.33%' }} /> | ||
| </div> | ||
| </div> | ||
|
||
|
|
||
| {/* 콘텐츠 영역 */} | ||
| <div className="px-6 mt-6"> | ||
| {/* 진행도 */} | ||
| <div className="text-title-1 text-white mb-3">1/3</div> | ||
|
||
|
|
||
| {/* 타이틀 */} | ||
| <h1 className="text-title-2 mb-10">프로필을 만들어주세요</h1> | ||
|
|
||
| {/* 프로필 이미지 (예시용 박스) */} | ||
| <div className="flex justify-center mb-10"> | ||
| <div className="w-36 h-36 rounded-full bg-gray-100 flex items-center justify-center text-black text-sm"> | ||
| 갤러리 아이콘 | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* 닉네임 입력 */} | ||
| <div className="mb-2"> | ||
| <Input | ||
| label="" | ||
| value={input} | ||
| onChange={setInput} | ||
| placeholder="닉네임을 입력해주세요" | ||
| placeholderColorType="gray" | ||
| showBackground={true} | ||
| /> | ||
| </div> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Input 컴포넌트도 제가 onClickPlus prop을 전달받지 않으면 + 버튼 랜더링 되지 않도록 수정해두었습니다! pull하신 후에 잘 적용되는지 한 번 확인해주세요 🙌🙌 |
||
|
|
||
| <p className="text-caption-3 text-gray-500 ml-1">10자 이내로 작성해주세요.</p> | ||
| </div> | ||
|
|
||
| {/* 하단 버튼 */} | ||
| <div className="fixed bottom-8 left-1/2 -translate-x-1/2 w-full max-w-[375px] px-6"> | ||
| <Button | ||
| onClick={handleNext} | ||
| disabled={!input.trim()} | ||
| variant="primary" | ||
| color="red" | ||
| size="lg" | ||
| fontType="title-3" | ||
| className="w-full" | ||
| > | ||
| 다음 | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기는 import {Header, Button} from '@/components' 이런식으로 더 간단하게 쓰실 수 있습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 통일된 구조로 수정했습니다.