-
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.
fix: TopNavigation CenterItem height 속성 제거
- Loading branch information
Showing
7 changed files
with
317 additions
and
26 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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,76 @@ | ||
import { Meta, StoryObj } from '@storybook/react'; | ||
import { SubmitHandler, useForm } from 'react-hook-form'; | ||
|
||
import Button from '@/ui/Base/Button'; | ||
import Input from '@/ui/Base/Input'; | ||
import InputLength from '@/ui/Base/InputLength'; | ||
import ErrorMessage from '@/ui/Base/ErrorMessage'; | ||
|
||
const meta: Meta<typeof InputLength> = { | ||
title: 'Base/InputLength', | ||
component: InputLength, | ||
tags: ['autodocs'], | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof InputLength>; | ||
|
||
type DefaultValues = { | ||
password: string; | ||
}; | ||
|
||
const InputLengthUseWithForm = () => { | ||
const { | ||
register, | ||
watch, | ||
handleSubmit, | ||
formState: { errors }, | ||
} = useForm<DefaultValues>({ | ||
mode: 'all', | ||
}); | ||
|
||
const handleSubmitForm: SubmitHandler<DefaultValues> = ({ password }) => { | ||
alert(`password: ${password}`); | ||
}; | ||
|
||
return ( | ||
<form | ||
onSubmit={handleSubmit(handleSubmitForm)} | ||
className="flex w-full flex-col gap-[1.6rem]" | ||
> | ||
<div className="flex flex-col gap-[0.5rem]"> | ||
<Input | ||
placeholder="비밀번호를 입력해주세요." | ||
{...register('password', { | ||
required: '필수 항목입니다.', | ||
minLength: { value: 2, message: '2자 이상 입력해 주세요.' }, | ||
maxLength: { value: 10, message: '10자 이하 입력해 주세요.' }, | ||
})} | ||
error={!!errors.password} | ||
/> | ||
<div className="flex flex-row-reverse justify-between gap-[0.4rem]"> | ||
<InputLength | ||
currentLength={watch('password')?.length} | ||
isError={!!errors.password} | ||
maxLength={10} | ||
/> | ||
{errors.password && ( | ||
<ErrorMessage>{errors.password.message}</ErrorMessage> | ||
)} | ||
</div> | ||
</div> | ||
<Button | ||
size="large" | ||
type="submit" | ||
onClick={handleSubmit(handleSubmitForm)} | ||
> | ||
Submit | ||
</Button> | ||
</form> | ||
); | ||
}; | ||
|
||
export const Default: Story = { | ||
render: () => <InputLengthUseWithForm />, | ||
}; |
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,22 @@ | ||
type InputLengthProps = { | ||
currentLength: number; | ||
isError: boolean; | ||
maxLength: number; | ||
}; | ||
|
||
const InputLength = ({ | ||
currentLength, | ||
isError, | ||
maxLength, | ||
}: InputLengthProps) => { | ||
const textColor = isError ? 'text-warning-800 ' : 'text-main-900'; | ||
|
||
return ( | ||
<div> | ||
<span className={textColor}>{currentLength ? currentLength : 0}</span>/ | ||
{maxLength} | ||
</div> | ||
); | ||
}; | ||
|
||
export default InputLength; |
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,193 @@ | ||
'use client'; | ||
|
||
import Link from 'next/link'; | ||
import { useRouter } from 'next/navigation'; | ||
import { SubmitHandler, useForm } from 'react-hook-form'; | ||
|
||
import type { APIJobGroup } from '@/types/job'; | ||
import type { APIUser } from '@/types/user'; | ||
|
||
import { isAxiosError } from 'axios'; | ||
import useMyProfileMutation from '@/queries/user/useMyProfileMutation'; | ||
|
||
import { IconClose } from '@public/icons'; | ||
|
||
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 UserProfileProps = { | ||
profile: Pick<APIUser, 'nickname' | 'job'>; | ||
jobGroups: APIJobGroup[]; | ||
}; | ||
|
||
type FormValues = { | ||
nickname: string; | ||
jobGroup: string; | ||
job: string; | ||
}; | ||
|
||
const EditProfile = ({ profile, jobGroups }: UserProfileProps) => { | ||
const { | ||
register, | ||
watch, | ||
handleSubmit, | ||
formState: { errors }, | ||
} = useForm<FormValues>({ | ||
mode: 'all', | ||
defaultValues: { | ||
nickname: profile.nickname || '', | ||
jobGroup: profile.job.jobGroupName || '', | ||
job: profile.job.jobName || '', | ||
}, | ||
}); | ||
|
||
const router = useRouter(); | ||
const myProfileMutation = useMyProfileMutation(); | ||
const toast = useToast(); | ||
|
||
const showToastEditSuccess = () => | ||
toast.show({ | ||
type: 'success', | ||
message: '프로필 수정 완료!', | ||
duration: 3000, | ||
}); | ||
|
||
const showToastEditFailed = () => | ||
toast.show({ | ||
type: 'error', | ||
message: '알 수 없는 에러가 발생했어요.', | ||
duration: 3000, | ||
}); | ||
|
||
const handleSubmitForm: SubmitHandler<FormValues> = ({ | ||
nickname, | ||
jobGroup, | ||
job, | ||
}) => { | ||
myProfileMutation.mutateAsync( | ||
{ | ||
nickname, | ||
job: { jobGroup, jobName: job }, | ||
}, | ||
{ | ||
onSuccess: () => { | ||
router.replace('/profile/me'); | ||
showToastEditSuccess(); | ||
}, | ||
onError: error => { | ||
if (isAxiosError(error) && error.response) { | ||
console.error(error.response.data); | ||
showToastEditFailed(); | ||
} | ||
}, | ||
} | ||
); | ||
}; | ||
|
||
return ( | ||
<> | ||
<TopNavigation> | ||
<TopNavigation.LeftItem> | ||
<Link href="/profile/me" className="h-[2rem] w-[2rem] cursor-pointer"> | ||
<IconClose className="fill-black-900" /> | ||
</Link> | ||
</TopNavigation.LeftItem> | ||
<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> | ||
|
||
<form | ||
onSubmit={handleSubmit(handleSubmitForm)} | ||
className="mt-[9.2rem] 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} | ||
> | ||
{jobGroups.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} | ||
> | ||
{jobGroups | ||
.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> | ||
</> | ||
); | ||
}; | ||
|
||
export default EditProfile; |