Skip to content

Commit

Permalink
feat: update user name (picktoss#289)
Browse files Browse the repository at this point in the history
* feat: 사용자 profile api 요청 함수 및 훅 구현

* feat: change user name
  • Loading branch information
rabyeoljji committed Dec 3, 2024
1 parent 0eaf7e7 commit 79616e0
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 31 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@firebase/messaging": "^0.12.14",
"@hookform/resolvers": "^3.6.0",
"@hookform/resolvers": "^3.9.1",
"@lukemorales/query-key-factory": "^1.3.4",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
Expand Down
13 changes: 7 additions & 6 deletions src/app/(routes)/profile/account/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { cn } from '@/shared/lib/utils'
import CategoryDrawer from '@/features/user/components/category-drawer'
import SetNameDialog from '@/features/user/components/set-name-dialog'
import Link from 'next/link'
import { fetchUserInfo } from '@/requests/user'

const AccountPage = () => {
const email = '[email protected]'
const AccountPage = async () => {
const user = await fetchUserInfo()

return (
<main className="h-[calc(100dvh-54px-88px)] w-full overflow-y-auto px-[16px]">
Expand All @@ -25,9 +26,9 @@ const AccountPage = () => {
</div>

<div className="flex flex-col gap-[32px]">
<SetNameDialog userName={'픽토스'} />
<SetNameDialog userName={user.name} />

<CategoryDrawer interestedCategory={'IT·프로그래밍'} />
<CategoryDrawer interestedCategory={user.interestField?.[0] ?? '관심 분야 없음'} />

<Link href={'verify-email'} className="flex w-full items-center justify-between">
<div className="flex flex-col items-start gap-[4px]">
Expand All @@ -38,9 +39,9 @@ const AccountPage = () => {
{/* 이메일 등록 여부에 따라 다르게 보여야함 */}
<Text
typography="subtitle2-medium"
className={cn('text-text-caption', email && 'text-text-primary')}
className={cn('text-text-caption', user.email && 'text-text-primary')}
>
{email ? email : '이메일 주소를 등록해주세요'}
{user.email ? user.email : '이메일 주소를 등록해주세요'}
</Text>
</div>
<Icon name="chevron-right" className="size-[16px] text-icon-tertiary" />
Expand Down
95 changes: 78 additions & 17 deletions src/features/user/components/set-name-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { useUpdateUserName } from '@/requests/user/hooks'
import Icon from '@/shared/components/custom/icon'
import {
Dialog,
Expand All @@ -9,20 +10,63 @@ import {
DialogTrigger,
} from '@/shared/components/ui/dialog'
import Text from '@/shared/components/ui/text'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { Form, FormControl, FormField, FormItem } from '@/shared/components/ui/form'

const formSchema = z.object({
name: z.string().min(1, '이름을 입력해주세요'),
})

type FormValues = z.infer<typeof formSchema>

const SetNameDialog = ({ userName }: { userName: string }) => {
const [name, setName] = useState(userName) // 유저 정보에서 이름 가져오기
const router = useRouter()
const [open, setOpen] = useState(false)

const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
name: userName,
},
})

const { mutate, isPending } = useUpdateUserName()

const onSubmit = (values: { name: string }) => {
mutate(
{
name: values.name,
},
{
onSuccess: () => {
router.refresh()
setOpen(false)
},
}
)
}

return (
<Dialog>
<Dialog
open={open}
onOpenChange={(open) => {
setOpen(open)
if (!open) {
form.reset()
}
}}
>
<DialogTrigger asChild>
<div className="flex w-full cursor-pointer items-center justify-between">
<div className="flex flex-col items-start gap-[4px]">
<Text typography="text2-medium" className="text-text-sub">
이름
</Text>
<Text typography="subtitle2-medium">픽토스</Text>
<Text typography="subtitle2-medium">{userName}</Text>
</div>
<Icon name="chevron-right" className="size-[16px] text-icon-tertiary" />
</div>
Expand All @@ -31,26 +75,43 @@ const SetNameDialog = ({ userName }: { userName: string }) => {
<DialogContent
displayCloseButton={false}
className="h-fit w-[280px] rounded-[16px] bg-background-base-01 p-[24px] pb-[32px]"
onPointerDownOutside={(e) => {
if (isPending) {
e.preventDefault()
}
}}
>
<div>
<DialogTitle className="mb-[38px]">
<Text typography="subtitle2-bold">이름 변경</Text>
</DialogTitle>

<input
value={name}
onChange={(e) => setName(e.target.value)}
className="mb-[53px] w-full border-b border-border-divider py-[5px] text-subtitle2-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-background-disabled disabled:opacity-50 disabled:placeholder:text-text-disabled"
/>

<div className="flex items-center justify-end gap-[35px] text-button2">
<DialogClose>
<span className="text-button-text-tertiary">취소</span>
</DialogClose>
<DialogClose>
<span className="text-button-text-primary">저장하기</span>
</DialogClose>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormControl>
<input
{...field}
disabled={isPending}
className="size-full border-b border-border-divider py-[5px] text-subtitle2-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-background-disabled disabled:opacity-50 disabled:placeholder:text-text-disabled"
/>
</FormControl>
</FormItem>
)}
/>

<div className="mt-[53px] flex items-center justify-end gap-[35px] text-button2">
<DialogClose>
<span className="text-button-text-tertiary">취소</span>
</DialogClose>
<button className="text-button-text-primary">저장하기</button>
</div>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
Expand Down
2 changes: 2 additions & 0 deletions src/requests/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as directory from './directory'
import * as document from './document'
import * as quiz from './quiz'
import * as user from './user'

export const REQUEST = {
directory,
document,
quiz,
user,
}
55 changes: 55 additions & 0 deletions src/requests/user/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client'

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import {
fetchUserInfo,
updateTodayQuizCount,
updateQuizNotification,
updateUserName,
updateCollectionFields,
} from '.'

export const useUserInfo = () => {
return useQuery({
queryKey: ['userInfo'],
queryFn: async () => fetchUserInfo(),
})
}

export const useUpdateTodayQuizCount = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: async (payload: User.Request.UpdateTodayQuizCount) => updateTodayQuizCount(payload),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['userInfo'] }),
})
}

export const useUpdateQuizNotification = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: async (payload: User.Request.UpdateQuizNotification) =>
updateQuizNotification(payload),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['userInfo'] }),
})
}

export const useUpdateUserName = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: async (payload: User.Request.UpdateName) => updateUserName(payload),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['userInfo'] }),
})
}

export const useUpdateCollectionFields = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: async (payload: User.Request.UpdateCollectionFields) =>
updateCollectionFields(payload),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['userInfo'] }),
})
}
76 changes: 76 additions & 0 deletions src/requests/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use server'

import { auth } from '@/app/api/auth/[...nextauth]/auth'
import { API_ENDPOINTS } from '@/shared/configs/endpoint'
import { http } from '@/shared/lib/axios/http'

export const fetchUserInfo = async () => {
const session = await auth()

try {
const { data } = await http.get<User.Info>(API_ENDPOINTS.USER.GET.INFO, {
headers: {
Authorization: `Bearer ${session?.user.accessToken}`,
},
})
return data
} catch (error: unknown) {
throw error
}
}

export const updateTodayQuizCount = async (payload: User.Request.UpdateTodayQuizCount) => {
const session = await auth()

try {
await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_QUIZ_COUNT, payload, {
headers: {
Authorization: `Bearer ${session?.user.accessToken}`,
},
})
} catch (error: unknown) {
throw error
}
}

export const updateQuizNotification = async (payload: User.Request.UpdateQuizNotification) => {
const session = await auth()

try {
await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_NOTIFICATION, payload, {
headers: {
Authorization: `Bearer ${session?.user.accessToken}`,
},
})
} catch (error: unknown) {
throw error
}
}

export const updateUserName = async (payload: User.Request.UpdateName) => {
const session = await auth()

try {
await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_NAME, payload, {
headers: {
Authorization: `Bearer ${session?.user.accessToken}`,
},
})
} catch (error: unknown) {
throw error
}
}

export const updateCollectionFields = async (payload: User.Request.UpdateCollectionFields) => {
const session = await auth()

try {
await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_COLLECTION_FIELDS, payload, {
headers: {
Authorization: `Bearer ${session?.user.accessToken}`,
},
})
} catch (error: unknown) {
throw error
}
}
4 changes: 1 addition & 3 deletions src/shared/configs/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,7 @@ export const API_ENDPOINTS = {
CREATE: '/feedback',
},

MEMBER: {
/** GET /members/info - Get member info */
BASE: '/members',
USER: {
GET: {
/** GET /members/info - Get member info */
INFO: '/members/info',
Expand Down
Loading

0 comments on commit 79616e0

Please sign in to comment.