Skip to content

Commit

Permalink
feat: 유저 정보 수정기능을 추가합니다.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zero-1016 committed Jun 3, 2024
1 parent 13eb4d2 commit 9845721
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 46 deletions.
11 changes: 7 additions & 4 deletions src/features/bookmark/hook/usePostMovieBookMark.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'react-toastify'

import { postMovieBookMark } from '@/features/bookmark/lib'
import { LOCAL_QUERY_KEY } from '@/shared/constants'
Expand All @@ -13,10 +14,12 @@ export function UsePostMovieBookMark(movieId: string) {
return { isLike: !prevData.isLike }
})
},
onError: () => {
queryClient.invalidateQueries({
queryKey: LOCAL_QUERY_KEY.movieBookMark(movieId),
})
onError: error => {
queryClient
.invalidateQueries({
queryKey: LOCAL_QUERY_KEY.movieBookMark(movieId),
})
.then(() => toast.error(error.message))
},
})
}
1 change: 1 addition & 0 deletions src/features/profile/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { useImageOnChange } from './useImageOnChange'
export { usePutUserNickname } from './usePutUserNickname'
4 changes: 2 additions & 2 deletions src/features/profile/hooks/useImageOnChange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import { ChangeEventHandler, useRef, useState } from 'react'

import { UsePutUserProfile } from '@/features/profile/hooks/usePutUserProfile'
import { usePutUserProfile } from './usePutUserProfile'

export function useImageOnChange() {
const [imgFile, setImageFile] = useState<string | null>(null)
const imgRef = useRef<HTMLInputElement>(null)

const { mutateAsync } = UsePutUserProfile()
const { mutateAsync } = usePutUserProfile()

const onChange: ChangeEventHandler<HTMLInputElement> = () => {
if (!imgRef?.current || !imgRef.current.files) return
Expand Down
29 changes: 29 additions & 0 deletions src/features/profile/hooks/usePutUserNickname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useMutation } from '@tanstack/react-query'
import { useSession } from 'next-auth/react'

import { putUserNickname } from '@/features/profile/lib'

type FetchFUNC = {
formData: FormData
nickname?: string
}

export function usePutUserNickname() {
const { data: session, update } = useSession()

return useMutation({
mutationFn: ({ formData }: FetchFUNC) => putUserNickname(formData),
onMutate: async ({ nickname }: FetchFUNC) => {
const prevUser = session?.user
if (!prevUser) return
await update({ nickname })
return { prevUser }
},
onError: async (_1, _2, context) => {
await update({ ...context?.prevUser })
},
onSettled: async () => {
await update()
},
})
}
2 changes: 1 addition & 1 deletion src/features/profile/hooks/usePutUserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type FetchFUNC = {
url: string
}

export function UsePutUserProfile() {
export function usePutUserProfile() {
const { data: session, update } = useSession()

return useMutation({
Expand Down
2 changes: 2 additions & 0 deletions src/features/profile/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { putUserNickname } from './putUserNickname'
export { putUserPassword } from './putUserPassword'
export { putUserProfile } from './putUserProfile'
14 changes: 14 additions & 0 deletions src/features/profile/lib/putUserNickname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const putUserNickname = async (formData: FormData) => {
const res = await fetch(`${process.env.NEXT_PUBLIC_LOCAL_BASE_URL}/user/nickname`, {
method: 'PUT',
body: formData,
credentials: 'include',
cache: 'no-store',
})

if (!res.ok) {
throw new Error('Failed to fetch data')
}

return res.json()
}
14 changes: 14 additions & 0 deletions src/features/profile/lib/putUserPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const putUserPassword = async (formData: FormData) => {
const res = await fetch(`${process.env.NEXT_PUBLIC_LOCAL_BASE_URL}/user/password`, {
method: 'PUT',
body: formData,
credentials: 'include',
cache: 'no-store',
})

if (!res.ok) {
throw new Error('Failed to fetch data')
}

return res.json()
}
2 changes: 2 additions & 0 deletions src/features/profile/schema/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { nicknameFiledSchema } from './nickname-filed-schema'
export { passwordFiledSchema } from './password-filed-schema'
7 changes: 7 additions & 0 deletions src/features/profile/schema/nickname-filed-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from 'zod'

import { nicknameSchema } from '@/features/auth/schema'

export const nicknameFiledSchema = z.object({
nickname: nicknameSchema,
})
14 changes: 14 additions & 0 deletions src/features/profile/schema/password-filed-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { string, z } from 'zod'

import { passwordSchema } from '@/features/auth/schema'

export const passwordFiledSchema = z
.object({
prevPassword: string().optional(),
password: passwordSchema,
confirmPassword: passwordSchema,
})
.refine(data => data.password === data.confirmPassword, {
message: '비밀번호가 일치하지 않습니다.',
path: ['confirmPassword'],
})
16 changes: 16 additions & 0 deletions src/features/profile/ui/UserEmailFiled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Input, InputLabel } from '@mui/material'

import styles from './user-change-filed.module.scss'

type Props = {
email: string
}

export function UserEmailFiled({ email }: Props) {
return (
<InputLabel className={styles.labelBox}>
<span className={styles.labelName}>이메일</span>
<Input className={styles.input} sx={{ background: '#8E95A9' }} defaultValue={email} disabled />
</InputLabel>
)
}
65 changes: 65 additions & 0 deletions src/features/profile/ui/UserNickNameChangeFiled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { Input, InputLabel } from '@mui/material'
import Button from '@mui/material/Button'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'react-toastify'
import { z } from 'zod'

import { nicknameCheck } from '@/features/auth/lib'
import { usePutUserNickname } from '@/features/profile/hooks'
import { nicknameFiledSchema } from '@/features/profile/schema'

import styles from './user-change-filed.module.scss'

export function UserNickNameChangeFiled() {
const { mutateAsync } = usePutUserNickname()

const onSubmit: SubmitHandler<z.infer<typeof nicknameFiledSchema>> = async data => {
try {
const { nickname } = data
await nicknameCheck(nickname)
const formData = new FormData()
formData.append('nickname', nickname)
await mutateAsync({ formData, nickname })
toast.success('닉네임이 변경되었습니다.')
} catch (err) {
if (err instanceof Error) {
console.error(err.message)
}
}
}

const {
handleSubmit,
register,
formState: { errors },
} = useForm<z.infer<typeof nicknameFiledSchema>>({
resolver: zodResolver(nicknameFiledSchema),
defaultValues: {
nickname: '',
},
})

return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<InputLabel sx={{ display: 'flex', alignItems: 'center' }} htmlFor="nickname">
<span className={styles.labelName}>닉네임</span>
<div className={styles.inputButton}>
<Input
id="nickname"
className={styles.input}
sx={{ background: '#8E95A9' }}
defaultValue={name}

Check notice on line 53 in src/features/profile/ui/UserNickNameChangeFiled.tsx

View workflow job for this annotation

GitHub Actions / qodana

Deprecated symbol used

Deprecated symbol used, consult docs for better alternative
{...register('nickname')}
/>
<Button type="submit" variant="contained" size="small" className={styles.changeButton}>
변경 하기
</Button>
</div>
</InputLabel>
</form>
{errors['nickname'] && <span className={styles.errorMessage}>{errors['nickname']?.message}</span>}
</>
)
}
96 changes: 96 additions & 0 deletions src/features/profile/ui/UserPasswordChangeFiled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { Input, InputLabel } from '@mui/material'
import Button from '@mui/material/Button'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'react-toastify'
import { z } from 'zod'

import { nicknameCheck } from '@/features/auth/lib'
import { putUserPassword } from '@/features/profile/lib'
import { passwordFiledSchema } from '@/features/profile/schema'

import styles from './user-change-filed.module.scss'

export function UserPasswordChangeFiled() {
const onSubmit: SubmitHandler<z.infer<typeof passwordFiledSchema>> = async data => {
try {
const { prevPassword, password } = data
if (typeof prevPassword !== 'string') return
await nicknameCheck(password)
const formData = new FormData()
formData.append('prevPassword', prevPassword)
formData.append('password', password)
await nicknameCheck(password)
await putUserPassword(formData)
toast.success('비밀번호가 변경되었습니다.')
reset()
} catch (err) {
if (err instanceof Error) {
toast.error(err.message)
}
}
}

const {
handleSubmit,
register,
formState: { errors },
reset,
} = useForm<z.infer<typeof passwordFiledSchema>>({
resolver: zodResolver(passwordFiledSchema),
defaultValues: {
prevPassword: '',
password: '',
confirmPassword: '',
},
})

return (
<>
<form onSubmit={handleSubmit(onSubmit)} style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<InputLabel sx={{ display: 'flex', alignItems: 'center' }}>
<span className={styles.labelName}>이전 비밀번호</span>
<Input
type="password"
className={styles.input}
sx={{ background: '#8E95A9' }}
defaultValue={name}

Check notice on line 57 in src/features/profile/ui/UserPasswordChangeFiled.tsx

View workflow job for this annotation

GitHub Actions / qodana

Deprecated symbol used

Deprecated symbol used, consult docs for better alternative
{...register('prevPassword')}
/>
</InputLabel>
<InputLabel>
<span className={styles.labelName}>새 비밀번호</span>
<Input
type="password"
className={styles.input}
sx={{ background: '#8E95A9' }}
defaultValue={name}

Check notice on line 67 in src/features/profile/ui/UserPasswordChangeFiled.tsx

View workflow job for this annotation

GitHub Actions / qodana

Deprecated symbol used

Deprecated symbol used, consult docs for better alternative
{...register('password')}
/>
</InputLabel>
<InputLabel sx={{ display: 'flex', alignItems: 'center' }}>
<span className={styles.labelName}>비밀번호 확인</span>
<div className={styles.inputButton}>
<Input
type="password"
className={styles.input}
sx={{ background: '#8E95A9' }}
defaultValue={name}

Check notice on line 78 in src/features/profile/ui/UserPasswordChangeFiled.tsx

View workflow job for this annotation

GitHub Actions / qodana

Deprecated symbol used

Deprecated symbol used, consult docs for better alternative
{...register('confirmPassword')}
/>
<Button type="submit" variant="contained" size="small" className={styles.changeButton}>
변경 하기
</Button>
</div>
</InputLabel>
</form>
{errors.prevPassword ? (
<span className={styles.errorMessage}>{errors.prevPassword.message}</span>
) : errors.password ? (
<span className={styles.errorMessage}>{errors.password.message}</span>
) : errors.confirmPassword ? (
<span className={styles.errorMessage}>{errors.confirmPassword.message}</span>
) : null}
</>
)
}
3 changes: 3 additions & 0 deletions src/features/profile/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export { ProfileImageChangeButton } from './ProfileImageChangeButton'
export { UserEmailFiled } from './UserEmailFiled'
export { UserNickNameChangeFiled } from './UserNickNameChangeFiled'
export { UserPasswordChangeFiled } from './UserPasswordChangeFiled'
38 changes: 38 additions & 0 deletions src/features/profile/ui/user-change-filed.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.labelName {
display: inline-block;
width: 150px;
color: #8e95a9;
}

.input {
padding: 5px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
transition: 0.5s;
&:focus {
background-color: #fffffe !important;
outline: none;
}

&:disabled {
background-color: #101820 !important;
}

&::placeholder {
color: #8e95a9;
}
}

.inputButton {
display: flex;
gap: 10px;
}

.changeButton {
border-radius: 5px;
}

.errorMessage {
color: red;
margin-left: 50px;
}
1 change: 0 additions & 1 deletion src/features/search/ui/HeaderSearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export function HeaderSearchBar() {

try {
searchKeywordSchema.parse(keyword)

router.push(SITE_PATH.search(keyword))
} catch (error) {
if (error instanceof ZodError) {
Expand Down
Loading

0 comments on commit 9845721

Please sign in to comment.