-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from DevKor-github/signup
Signup
- Loading branch information
Showing
28 changed files
with
952 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const TOTAL_PROGRESS = 4; | ||
|
||
export type SchoolType = 'korea' | 'yonsei' | null; | ||
|
||
export interface SignupFormType { | ||
school: SchoolType; | ||
nickname: string; | ||
phoneNumber: string; | ||
agreement: boolean; | ||
} | ||
|
||
export type SignupElements = keyof SignupFormType; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,133 @@ | ||
'use client'; | ||
|
||
import styled from 'styled-components'; | ||
import { SwiperRef } from 'swiper/react'; | ||
import { useRouter } from 'next/navigation'; | ||
import { useCallback, useEffect, useRef, useState } from 'react'; | ||
import { useSignupError, useSignupForm } from '@/app/signup/store'; | ||
|
||
import { TOTAL_PROGRESS } from '@/app/signup/constants'; | ||
import SignupTopBar from '@/components/Signup/SignupTopBar'; | ||
import SignupProgress from '@/components/Signup/SignupProgress'; | ||
import SignupFunnel from '@/components/Signup/SignupFunnel'; | ||
|
||
export default function SignUp() { | ||
const router = useRouter(); | ||
|
||
const formState = useSignupForm(); | ||
const errorState = useSignupError(); | ||
const [clickable, setClickable] = useState(false); | ||
|
||
const totalProgress = TOTAL_PROGRESS; | ||
const [progress, setProgress] = useState(0); | ||
const swiperRef = useRef<SwiperRef>(null); | ||
useEffect(() => { | ||
// 스와이퍼와 progress 동기화 | ||
swiperRef.current?.swiper.slideTo(progress); | ||
}, [progress]); | ||
|
||
useEffect(() => { | ||
// TODO: Auth Check | ||
if (false) { | ||
router.push('/'); | ||
} | ||
}, [router]); | ||
|
||
const handlePrevButton = useCallback(() => { | ||
if (progress === 0) { | ||
router.back(); | ||
} else { | ||
setProgress((prev) => prev - 1); | ||
} | ||
}, [router, progress]); | ||
|
||
// 다음 버튼 활성화 로직 | ||
useEffect(() => { | ||
switch (progress) { | ||
case 0: | ||
setClickable(formState.school !== null); | ||
break; | ||
case 1: | ||
setClickable(formState.nickname !== ''); | ||
break; | ||
case 2: | ||
setClickable( | ||
formState.phoneNumber.length === 11 && | ||
/^01([0|1|6|7|8|9]?)?([0-9]{3,4})?([0-9]{4})$/.test(formState.phoneNumber), | ||
); | ||
break; | ||
case 3: | ||
setClickable(formState.agreement); | ||
break; | ||
} | ||
}, [progress, formState]); | ||
|
||
const handleNextButton = () => { | ||
if (clickable) { | ||
switch (progress) { | ||
case 0: | ||
setProgress((prev) => prev + 1); | ||
break; | ||
case 1: | ||
// TODO: Nickname Validation Check | ||
if (formState.nickname !== 'error' && formState.nickname !== '에러') { | ||
setProgress((prev) => prev + 1); | ||
} else { | ||
errorState.setError('nickname'); | ||
} | ||
break; | ||
case 2: | ||
setProgress((prev) => prev + 1); | ||
break; | ||
case 3: | ||
setProgress((prev) => prev + 1); | ||
// TODO: Sign-up Form Submit | ||
console.log(formState); | ||
break; | ||
case 4: | ||
// 토키 시작하기 | ||
router.push('/'); | ||
break; | ||
} | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<h1>Sign Up</h1> | ||
<SignupTopBar handlePrevButton={handlePrevButton} /> | ||
<SignupProgress curProgress={progress} totalProgress={totalProgress} /> | ||
<SignupFunnel ref={swiperRef} /> | ||
<SignupFooter $isDone={clickable} onClick={handleNextButton}> | ||
{progress === totalProgress ? '토키 시작하기' : '다음'} | ||
</SignupFooter> | ||
</div> | ||
); | ||
} | ||
|
||
const SignupFooter = styled.button<{ $isDone: boolean }>` | ||
position: fixed; | ||
bottom: 0; | ||
width: 100%; | ||
height: 64px; | ||
padding: 20px 0; | ||
background: var( | ||
--Background-5, | ||
linear-gradient(0deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.05) 100%), | ||
#121212 | ||
); | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
font-size: 20px; | ||
font-weight: 700; | ||
letter-spacing: -0.8px; | ||
color: ${({ $isDone }) => | ||
$isDone | ||
? 'var(--white-high-emphasis-87, rgba(255, 255, 255, 0.87))' | ||
: 'var(--white-15, rgba(255, 255, 255, 0.15))'}; | ||
transition: all 0.2s; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { create } from 'zustand'; | ||
import { SignupElements, SchoolType, SignupFormType } from '@/app/signup/constants'; | ||
|
||
interface SignupFormStore extends SignupFormType { | ||
setSchool: (select: SchoolType) => void; | ||
setNickname: (input: string) => void; | ||
setPhoneNumber: (input: string) => void; | ||
setAgreement: (input: boolean) => void; | ||
} | ||
export const useSignupForm = create<SignupFormStore>((set) => ({ | ||
school: null, | ||
nickname: '', | ||
phoneNumber: '', | ||
agreement: false, | ||
setSchool: (select: SchoolType) => set({ school: select }), | ||
setNickname: (input: string) => set({ nickname: input }), | ||
setPhoneNumber: (input: string) => set({ phoneNumber: input }), | ||
setAgreement: (input: boolean) => set({ agreement: input }), | ||
})); | ||
|
||
interface ErrorStore { | ||
error: SignupElements | null; | ||
setError: (message: SignupElements) => void; | ||
clearError: () => void; | ||
} | ||
export const useSignupError = create<ErrorStore>((set) => ({ | ||
error: null, | ||
setError: (code: SignupElements) => set({ error: code }), | ||
clearError: () => set({ error: null }), | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { SignupElements } from '@/app/signup/constants'; | ||
import { useSignupError } from '@/app/signup/store'; | ||
import { useState } from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
type StatusType = 'default' | 'focus' | 'filled'; | ||
|
||
interface InputBoxProps { | ||
type: SignupElements; | ||
value: string; | ||
setValue: (value: string) => void; | ||
placeholder?: string; | ||
maxLength?: number; | ||
} | ||
export function InputBox({ type, value, setValue, placeholder, maxLength }: InputBoxProps) { | ||
const [status, setStatus] = useState<StatusType>('default'); | ||
const errorState = useSignupError(); | ||
|
||
return ( | ||
<Input | ||
$status={status} | ||
$error={errorState.error === type} | ||
value={value} | ||
onChange={(e) => setValue(e.target.value)} | ||
placeholder={placeholder} | ||
onFocus={() => { | ||
setStatus('focus'); | ||
errorState.clearError(); | ||
}} | ||
onBlur={() => { | ||
if (value.length === 0) { | ||
setStatus('default'); | ||
} else { | ||
setStatus('filled'); | ||
} | ||
}} | ||
maxLength={maxLength} | ||
/> | ||
); | ||
} | ||
|
||
const Input = styled.input<{ $status: StatusType; $error?: boolean }>` | ||
padding: 16px; | ||
border: none; | ||
outline: none; | ||
background: none; | ||
border-radius: 8px; | ||
border: 1px solid; | ||
border-color: ${({ $status, $error }) => { | ||
if ($error) return `var(--Light-Red, #F95B6E)`; | ||
switch ($status) { | ||
case 'focus': | ||
case 'filled': | ||
return `var(--white-high-emphasis-87, rgba(255, 255, 255, 0.87))`; | ||
case 'default': | ||
return `var(--white-15, rgba(255, 255, 255, 0.15))`; | ||
} | ||
}}; | ||
color: var(--white-high-emphasis-87, rgba(255, 255, 255, 0.87)); | ||
font-size: 15px; | ||
letter-spacing: -0.6px; | ||
&::placeholder { | ||
color: var(--white-disabled-38, rgba(255, 255, 255, 0.38)); | ||
} | ||
transition: all 0.2s; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { InputBox as default } from './InputBox'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import styled from 'styled-components'; | ||
import { useSignupError, useSignupForm } from '@/app/signup/store'; | ||
|
||
import InputBox from '@/components/Signup/InputBox'; | ||
|
||
export function SetNickname() { | ||
const nickname = useSignupForm((state) => state.nickname); | ||
const setNickname = useSignupForm((state) => state.setNickname); | ||
const error = useSignupError((state) => state.error); | ||
|
||
return ( | ||
<Wrapper> | ||
<Guide> | ||
<p>나중에 수정 가능해요!</p> | ||
<h2> | ||
<strong>닉네임</strong>을 입력해주세요. | ||
</h2> | ||
</Guide> | ||
<FormWrapper> | ||
<InputBox placeholder="닉네임" value={nickname} setValue={setNickname} type="nickname" maxLength={10} /> | ||
<InputStatus> | ||
<NicknameCount>{nickname.length}/10</NicknameCount> | ||
{error === 'nickname' && <ErrorMessage>이미 존재하는 닉네임 입니다.</ErrorMessage>} | ||
</InputStatus> | ||
</FormWrapper> | ||
</Wrapper> | ||
); | ||
} | ||
|
||
const Wrapper = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 34px; | ||
`; | ||
const Guide = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 4px; | ||
color: var(--_60, rgba(255, 255, 255, 0.6)); | ||
& p { | ||
font-size: 12px; | ||
font-weight: 300; | ||
letter-spacing: -0.48px; | ||
} | ||
& h2 { | ||
font-size: 22px; | ||
letter-spacing: -0.88px; | ||
& strong { | ||
color: var(--white_0, #fff); | ||
} | ||
} | ||
`; | ||
|
||
const FormWrapper = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 8px; | ||
`; | ||
|
||
const InputStatus = styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
`; | ||
|
||
const NicknameCount = styled.span` | ||
color: var(--white-medium-emphasis-60, rgba(255, 255, 255, 0.6)); | ||
font-size: 14px; | ||
`; | ||
|
||
const ErrorMessage = styled.span` | ||
color: #f95b6e; | ||
font-size: 12px; | ||
font-weight: 300; | ||
letter-spacing: -0.48px; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { SetNickname as default } from './SetNickname'; |
Oops, something went wrong.