-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor : 회원가입 실패 이유 표출 * Refactor : 앱바 기본동작 추가 * Minor : Disable 버튼 스타일 추가 * New : FixedBottomCTA 버튼 추가 * Refactor : 회원가입 Multi-step-form 적용
- Loading branch information
1 parent
ffa54e6
commit e42380a
Showing
9 changed files
with
389 additions
and
128 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,38 @@ | ||
"use client"; | ||
|
||
import SignupPageContext from "@/store/auth/SignupPageContext"; | ||
import { SignupRequirement } from "@/types/auth/signupRequirement"; | ||
import { Container, Paper } from "@mui/material"; | ||
import { ReactNode, useState } from "react"; | ||
|
||
type layoutProps = { | ||
children: ReactNode; | ||
}; | ||
|
||
const layout = ({ children }: layoutProps) => { | ||
const [disableBtn, setDisableBtn] = useState(false); | ||
const [formData, setFormData] = useState<SignupRequirement>({ | ||
id: "", | ||
email: "", | ||
password: "", | ||
nickname: "", | ||
}); | ||
return ( | ||
<SignupPageContext.Provider value={{ formData, setFormData, disableBtn, setDisableBtn }}> | ||
<Container sx={{ px: { xs: 0, sm: 4 } }} maxWidth={"lg"}> | ||
<Paper component={'form'} | ||
sx={{ | ||
display: "flex", | ||
flexDirection: "column", | ||
minHeight: "calc(100vh - 56px)", | ||
overflowX:'hidden' | ||
}} | ||
> | ||
{children} | ||
</Paper> | ||
</Container> | ||
</SignupPageContext.Provider> | ||
); | ||
}; | ||
|
||
export default layout; |
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,137 +1,174 @@ | ||
"use client"; | ||
import Avatar from "@mui/material/Avatar"; | ||
import Button from "@mui/material/Button"; | ||
import CssBaseline from "@mui/material/CssBaseline"; | ||
import TextField from "@mui/material/TextField"; | ||
import FormControlLabel from "@mui/material/FormControlLabel"; | ||
import Checkbox from "@mui/material/Checkbox"; | ||
|
||
import Grid from "@mui/material/Grid"; | ||
import Box from "@mui/material/Box"; | ||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; | ||
import Typography from "@mui/material/Typography"; | ||
import Container from "@mui/material/Container"; | ||
import { SIGNIN } from "@/const/clientPath"; | ||
import Link from "next/link"; | ||
import { ChangeEvent, useState } from "react"; | ||
import { SignupRequirement } from "@/types/auth/signupRequirement"; | ||
import { ChangeEvent, useContext, useCallback, useState } from "react"; | ||
import useSignupMutation from "@/queries/auth/useSignupMutation"; | ||
import SignupPageContext from "@/store/auth/SignupPageContext"; | ||
import useMultistepForm from "@/hooks/useMultistepForm"; | ||
import SignupStep from "@/components/auth/signup/SignupStep"; | ||
import { TextField, LinearProgress } from "@mui/material"; | ||
import CustomAppbar from "@/components/CustomAppbar"; | ||
import { useRouter } from "next/navigation"; | ||
import HOME from "@/const/clientPath"; | ||
import { HomeOutlined } from "@mui/icons-material"; | ||
import { SignupRequirement } from "@/types/auth/signupRequirement"; | ||
import FixedBottomCTA from "@/components/FixedBottomCTA"; | ||
|
||
export default function SignUpPage() { | ||
const [formData, setFormData] = useState<SignupRequirement>({ | ||
id: "", | ||
email: "", | ||
password: "", | ||
nickname: "", | ||
}); | ||
const changeHandler = ({ | ||
target, | ||
}: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { | ||
setFormData((prev) => ({ ...prev, [target.name]: target.value })); | ||
}; | ||
const { mutate: submitHandler } = useSignupMutation(); | ||
const { formData, setFormData, disableBtn } = useContext(SignupPageContext); | ||
const router = useRouter(); | ||
|
||
const changeHandler = useCallback( | ||
({ target }: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { | ||
setFormData((prev) => ({ ...prev, [target.name]: target.value })); | ||
}, | ||
[] | ||
); | ||
const [doubleCheckPassword, setDoubleCheckPassword] = useState<string>(""); | ||
|
||
const { mutateAsync: signupHandler } = useSignupMutation(); | ||
const submitHandler = useCallback(async (data: SignupRequirement) => { | ||
try { | ||
await signupHandler(data); | ||
} catch (err) { | ||
goTo(0); | ||
} | ||
}, []); | ||
|
||
const { | ||
next, | ||
MultistepForm, | ||
isFirstStep, | ||
isLastStep, | ||
goTo, | ||
totalPageNum, | ||
currentIndex, | ||
} = useMultistepForm( | ||
[ | ||
<SignupStep | ||
title={`원활한 환경을 위해\n이메일을 입력해 주세요😃`} | ||
error={ | ||
!new RegExp(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/).test( | ||
formData.email | ||
) | ||
} | ||
> | ||
<TextField | ||
name="email" | ||
label="이메일" | ||
type="email" | ||
value={formData.email} | ||
helperText="이메일을 입력해 주세요." | ||
onChange={changeHandler} | ||
/> | ||
</SignupStep>, | ||
|
||
<SignupStep | ||
title={`🔐\n비밀번호를 입력해 주세요`} | ||
error={ | ||
formData.password | ||
? !new RegExp( | ||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$/ | ||
).test(formData.password) | ||
: undefined | ||
} | ||
> | ||
<TextField | ||
name="password" | ||
autoComplete="new-password" | ||
label="비밀번호" | ||
type="password" | ||
value={formData.password} | ||
placeholder="비밀번호를 입력해주세요" | ||
helperText="8~20자 대소문자, 숫자, 특수기호가 들어가야해요" | ||
onChange={changeHandler} | ||
/> | ||
</SignupStep>, | ||
|
||
<SignupStep | ||
title={`🔒🔒\n한번 더 입력해 주세요`} | ||
error={ | ||
doubleCheckPassword | ||
? !new RegExp( | ||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$/ | ||
).test(doubleCheckPassword) || | ||
doubleCheckPassword !== formData.password | ||
: undefined | ||
} | ||
> | ||
<TextField | ||
name="passwordcheck" | ||
autoComplete="new-password" | ||
label="비밀번호" | ||
type="password" | ||
value={doubleCheckPassword} | ||
placeholder="비밀번호를 입력해주세요" | ||
helperText="8~20자 대소문자, 숫자, 특수기호가 들어가야해요" | ||
onChange={({ target }) => setDoubleCheckPassword(target.value)} | ||
/> | ||
</SignupStep>, | ||
|
||
<SignupStep | ||
title={`거의 다 끝나가요!\n아이디와 닉네임을 설정해주세요🤓`} | ||
error={ | ||
formData.id ? !!formData.id && !(formData.id.length > 2) : undefined | ||
} | ||
> | ||
<TextField | ||
name="id" | ||
autoComplete="username" | ||
label="아이디" | ||
type="id" | ||
placeholder="아이디를 입력해주세요" | ||
helperText="이메일 노출없이 아이디로 대체해요" | ||
value={formData.id} | ||
onChange={changeHandler} | ||
/> | ||
</SignupStep>, | ||
|
||
<SignupStep | ||
title={`거의 다 끝나가요!\n아이디와 닉네임을 설정해주세요🤓`} | ||
error={ | ||
formData.nickname | ||
? !!formData.nickname && !(formData.nickname.length > 2) | ||
: undefined | ||
} | ||
> | ||
<TextField | ||
name="nickname" | ||
label="닉네임" | ||
placeholder="닉네임을 입력해주세요" | ||
value={formData.nickname} | ||
autoComplete="off" | ||
helperText="사용할 닉네임을 입력해 주세요." | ||
onChange={changeHandler} | ||
/> | ||
</SignupStep>, | ||
], | ||
0 | ||
); | ||
|
||
return ( | ||
<Container component="main" maxWidth="xs"> | ||
<CssBaseline /> | ||
<Box | ||
sx={{ | ||
marginTop: 8, | ||
display: "flex", | ||
flexDirection: "column", | ||
alignItems: "center", | ||
<> | ||
<CustomAppbar | ||
appendButton={"취소"} | ||
prependButton={isFirstStep ? <HomeOutlined sx={{ p: 0 }} /> : undefined} | ||
onClickPrepend={() => (isFirstStep ? router.push(HOME) : router.back())} | ||
onClickAppend={() => router.push(HOME)} | ||
/> | ||
<LinearProgress | ||
variant="determinate" | ||
value={(currentIndex / (totalPageNum - 1)) * 100} | ||
/> | ||
{MultistepForm} | ||
<FixedBottomCTA | ||
onClick={() => { | ||
!isLastStep ? next() : submitHandler(formData); | ||
}} | ||
size="large" | ||
disabled={disableBtn} | ||
> | ||
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}> | ||
<LockOutlinedIcon /> | ||
</Avatar> | ||
<Typography variant="h1">회원가입</Typography> | ||
<Box | ||
component="form" | ||
onSubmit={(e) => { | ||
e.preventDefault(); | ||
submitHandler(formData); | ||
}} | ||
noValidate | ||
sx={{ mt: 3 }} | ||
> | ||
<Grid container spacing={2}> | ||
<Grid item xs={12}> | ||
<TextField | ||
required | ||
autoFocus | ||
fullWidth | ||
id="email" | ||
label="이메일" | ||
name="email" | ||
autoComplete="email" | ||
onChange={(e) => changeHandler(e)} | ||
/> | ||
</Grid> | ||
<Grid item xs={12}> | ||
<TextField | ||
required | ||
fullWidth | ||
name="password" | ||
label="비밀번호" | ||
type="password" | ||
id="password" | ||
onChange={(e) => changeHandler(e)} | ||
autoComplete="new-password" | ||
/> | ||
</Grid> | ||
<Grid item xs={12} sm={6}> | ||
<TextField | ||
name="id" | ||
required | ||
fullWidth | ||
id="id" | ||
onChange={(e) => changeHandler(e)} | ||
label="유저 아이디" | ||
/> | ||
</Grid> | ||
<Grid item xs={12} sm={6}> | ||
<TextField | ||
required | ||
fullWidth | ||
id="nickname" | ||
label="닉네임" | ||
onChange={(e) => changeHandler(e)} | ||
name="nickname" | ||
/> | ||
</Grid> | ||
<Grid item xs={12}> | ||
<FormControlLabel | ||
control={<Checkbox value="allowExtraEmails" color="primary" />} | ||
label="유용한 정보를 받아볼게요." | ||
/> | ||
</Grid> | ||
</Grid> | ||
<Button | ||
type="submit" | ||
fullWidth | ||
variant="contained" | ||
sx={{ mt: 3, mb: 2 }} | ||
> | ||
회원가입 | ||
</Button> | ||
<Grid container justifyContent="flex-end"> | ||
<Grid item> | ||
<Link href={SIGNIN}> | ||
<Typography variant="label"> | ||
Already have an account?{" "} | ||
<Typography | ||
variant="label" | ||
sx={{ color: "primary", fontWeight: "bold" }} | ||
> | ||
로그인 하러가기 | ||
</Typography> | ||
</Typography> | ||
</Link> | ||
</Grid> | ||
</Grid> | ||
</Box> | ||
</Box> | ||
</Container> | ||
{!isLastStep ? "다음" : "투파이아 시작하기"} | ||
</FixedBottomCTA> | ||
</> | ||
); | ||
} |
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
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,33 @@ | ||
import { Button, ButtonProps, Paper } from "@mui/material"; | ||
|
||
const FixedBottomCTA = ({ ...props }: ButtonProps) => { | ||
return ( | ||
<> | ||
<Button | ||
sx={{ | ||
position: "absolute", | ||
bottom: 0, | ||
left: 0, | ||
right: 0, | ||
height: 56, | ||
borderRadius: 0, | ||
fontSize: "1rem", | ||
zIndex: 2, | ||
}} | ||
{...props} | ||
/> | ||
<Paper | ||
sx={{ | ||
position: "absolute", | ||
bottom: 0, | ||
left: 0, | ||
right: 0, | ||
height: 56, | ||
zIndex: 1, | ||
}} | ||
></Paper> | ||
</> | ||
); | ||
}; | ||
|
||
export default FixedBottomCTA; |
Oops, something went wrong.