-
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.
* feat: 프로필 생성 페이지 구현 (WIP) * chore: 프로필 등록 페이지 Storybook 삭제 * feat: 프로필 등록 페이지 기능 구현 * chore: component, type 명칭 및 토스트 메시지 수정 * refactor: 쿼리 키 팩토리 패턴 적용 및 쿼리 staleTime 옵션 적용
- Loading branch information
Showing
4 changed files
with
231 additions
and
32 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 |
---|---|---|
@@ -1,41 +1,29 @@ | ||
'use client'; | ||
|
||
import { Suspense } from 'react'; | ||
import useAllJobQuery from '@/queries/job/useAllJobQuery'; | ||
import useMyProfileQuery from '@/queries/user/useMyProfileQuery'; | ||
import AuthRequired from '@/ui/AuthRequired'; | ||
import ProfileForm from '@/ui/Profile/ProfileForm'; | ||
import { isAuthed } from '@/utils/helpers'; | ||
import { Text, VStack } from '@chakra-ui/react'; | ||
|
||
const AdditionalProfile = () => { | ||
const allJobQuery = useAllJobQuery({ enabled: isAuthed() }); | ||
const userProfileQuery = useMyProfileQuery({ enabled: isAuthed() }); | ||
import { isAuthed } from '@/utils/helpers'; | ||
import AuthRequired from '@/ui/AuthRequired'; | ||
|
||
const isSuccess = allJobQuery.isSuccess && userProfileQuery.isSuccess; | ||
import AddJobProfile from '@/v1/profile/AddJobProfile'; | ||
|
||
const AddJobProfilePage = () => { | ||
return ( | ||
<AuthRequired> | ||
<VStack position="relative" zIndex={10} pt="6rem" gap="1rem"> | ||
<Text fontSize="lg" fontWeight="bold"> | ||
추가 정보를 입력해 주세요! | ||
</Text> | ||
<Text fontSize="md" textAlign="center"> | ||
추가 정보를 입력하면 | ||
<br /> | ||
<Text as="span" color="main" fontWeight="bold"> | ||
다독다독 | ||
</Text> | ||
이 추천하는 책장을 볼 수 있어요! | ||
</Text> | ||
{isSuccess && ( | ||
<ProfileForm | ||
profile={userProfileQuery.data} | ||
jobGroups={allJobQuery.data.jobGroups} | ||
/> | ||
)} | ||
</VStack> | ||
<Suspense fallback={null}> | ||
<Contents /> | ||
</Suspense> | ||
</AuthRequired> | ||
); | ||
}; | ||
|
||
export default AdditionalProfile; | ||
const Contents = () => { | ||
const allJobQuery = useAllJobQuery({ enabled: isAuthed() }); | ||
|
||
return allJobQuery.isSuccess ? ( | ||
<AddJobProfile jobCategories={allJobQuery.data.jobGroups} /> | ||
) : null; | ||
}; | ||
|
||
export default AddJobProfilePage; |
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,6 @@ | ||
const jobKeys = { | ||
all: ['job'] as const, | ||
category: () => [...jobKeys.all, 'category'] as const, | ||
}; | ||
|
||
export default jobKeys; |
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,16 +1,19 @@ | ||
import jobAPI from '@/apis/job'; | ||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'; | ||
|
||
import jobAPI from '@/apis/job'; | ||
|
||
import jobKeys from '@/queries/job/key'; | ||
|
||
type Options = Pick< | ||
UseQueryOptions<Awaited<ReturnType<typeof jobAPI.getAllJobs>>['data']>, | ||
'enabled' | ||
>; | ||
|
||
const useAllJobQuery = (options?: Options) => | ||
useQuery( | ||
['allJob'], | ||
jobKeys.category(), | ||
() => jobAPI.getAllJobs().then(response => response.data), | ||
options | ||
{ ...options, staleTime: Infinity } | ||
); | ||
|
||
export default useAllJobQuery; |
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,202 @@ | ||
'use client'; | ||
|
||
import { useRouter } from 'next/navigation'; | ||
import { SubmitHandler, useForm } from 'react-hook-form'; | ||
|
||
import type { APIJobGroup } from '@/types/job'; | ||
|
||
import { isAxiosError } from 'axios'; | ||
import useMyProfileMutation from '@/queries/user/useMyProfileMutation'; | ||
|
||
import TopNavigation from '@/ui/Base/TopNavigation'; | ||
import Input from '@/ui/Base/Input'; | ||
import InputLength from '@/ui/Base/InputLength'; | ||
import Select from '@/ui/Base/Select'; | ||
import ErrorMessage from '@/ui/Base/ErrorMessage'; | ||
import useToast from '@/ui/Base/Toast/useToast'; | ||
|
||
type AddJobProfileProps = { | ||
jobCategories: APIJobGroup[]; | ||
}; | ||
|
||
type FormValues = { | ||
nickname: string; | ||
jobGroup: string; | ||
job: string; | ||
}; | ||
|
||
const AddJobProfile = ({ jobCategories }: AddJobProfileProps) => { | ||
const { | ||
register, | ||
watch, | ||
handleSubmit, | ||
formState: { errors }, | ||
} = useForm<FormValues>({ | ||
mode: 'all', | ||
defaultValues: { | ||
nickname: '', | ||
jobGroup: '', | ||
job: '', | ||
}, | ||
}); | ||
|
||
const router = useRouter(); | ||
const myProfileMutation = useMyProfileMutation(); | ||
const toast = useToast(); | ||
|
||
const showToastEditSuccess = () => | ||
toast.show({ | ||
type: 'success', | ||
message: '프로필을 등록했어요!', | ||
duration: 3000, | ||
}); | ||
|
||
/** | ||
* @todo | ||
* showToastEditFailed() | ||
* 범용적으로 에러 핸들링 할 수 있도록 수정 | ||
*/ | ||
|
||
const showToastEditFailed = () => | ||
toast.show({ | ||
type: 'error', | ||
message: '잠시 후 다시 시도해 주세요.', | ||
duration: 3000, | ||
}); | ||
|
||
const handleSubmitForm: SubmitHandler<FormValues> = ({ | ||
nickname, | ||
jobGroup, | ||
job, | ||
}) => { | ||
myProfileMutation.mutateAsync( | ||
{ | ||
nickname, | ||
job: { jobGroup: jobGroup, jobName: job }, | ||
}, | ||
{ | ||
onSuccess: () => { | ||
router.replace('/bookarchive'); | ||
showToastEditSuccess(); | ||
}, | ||
onError: error => { | ||
if (isAxiosError(error) && error.response) { | ||
console.error(error.response.data); | ||
showToastEditFailed(); | ||
} | ||
}, | ||
} | ||
); | ||
}; | ||
|
||
return ( | ||
<> | ||
<TopNavigation> | ||
<TopNavigation.CenterItem textAlign="center"> | ||
<span className="text-md font-normal text-black-900"> | ||
프로필 등록 | ||
</span> | ||
</TopNavigation.CenterItem> | ||
<TopNavigation.RightItem> | ||
<span | ||
onClick={handleSubmit(handleSubmitForm)} | ||
className="cursor-pointer text-md font-bold text-main-900" | ||
> | ||
완료 | ||
</span> | ||
</TopNavigation.RightItem> | ||
</TopNavigation> | ||
|
||
<div className="mt-[9.2rem] flex w-full flex-col gap-[3.3rem]"> | ||
<div className="flex flex-col gap-[1rem] font-normal"> | ||
<span className="text-lg text-black-700">프로필을 등록해주세요!</span> | ||
<div className="text-sm text-placeholder"> | ||
<p>프로필을 등록하면</p> | ||
<p> | ||
<span className="text-main-900">다독다독</span>이 추천하는 책장을 | ||
볼 수 있어요. | ||
</p> | ||
</div> | ||
</div> | ||
|
||
<form | ||
onSubmit={handleSubmit(handleSubmitForm)} | ||
className="flex w-full flex-col gap-[3.2rem]" | ||
> | ||
<div className="flex flex-col gap-[1rem]"> | ||
<span className="h-[2.1rem] text-md font-normal text-black-700"> | ||
닉네임 | ||
</span> | ||
<div className="flex flex-col gap-[0.5rem]"> | ||
<Input | ||
placeholder="닉네임을 입력해주세요." | ||
{...register('nickname', { | ||
required: '닉네임을 입력해주세요.', | ||
minLength: { value: 2, message: '2자 이상 입력해 주세요.' }, | ||
maxLength: { value: 10, message: '10자 이하 입력해 주세요.' }, | ||
})} | ||
error={!!errors.nickname} | ||
/> | ||
<div className="flex h-[1.4rem] flex-row-reverse justify-between"> | ||
<InputLength | ||
currentLength={watch('nickname')?.length} | ||
isError={!!errors.nickname} | ||
maxLength={10} | ||
/> | ||
{errors.nickname && ( | ||
<ErrorMessage>{errors.nickname.message}</ErrorMessage> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div className="flex flex-col gap-[1rem]"> | ||
<span className="h-[2.1rem] text-md font-normal text-black-700"> | ||
직업/직군 | ||
</span> | ||
|
||
<div className="flex flex-col gap-[0.5rem]"> | ||
<Select | ||
placeholder="직군을 선택해주세요." | ||
{...register('jobGroup', { | ||
required: '직군을 선택해주세요.', | ||
})} | ||
error={!!errors.jobGroup} | ||
> | ||
{jobCategories.map(({ name, koreanName }) => ( | ||
<Select.Option key={name} value={name}> | ||
{koreanName} | ||
</Select.Option> | ||
))} | ||
</Select> | ||
{errors.jobGroup && ( | ||
<ErrorMessage>{errors.jobGroup.message}</ErrorMessage> | ||
)} | ||
</div> | ||
|
||
<div className="flex flex-col gap-[0.5rem]"> | ||
<Select | ||
placeholder="직업을 선택해주세요." | ||
{...register('job', { | ||
required: '직업을 선택해주세요.', | ||
})} | ||
error={!!errors.job} | ||
> | ||
{jobCategories | ||
.find(({ name }) => name === watch('jobGroup')) | ||
?.jobs.map(({ name, koreanName }) => ( | ||
<Select.Option key={name} value={name}> | ||
{koreanName} | ||
</Select.Option> | ||
))} | ||
</Select> | ||
{errors.job && <ErrorMessage>{errors.job.message}</ErrorMessage>} | ||
</div> | ||
</div> | ||
</form> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default AddJobProfile; |