Skip to content

Commit

Permalink
fix: TopNavigation CenterItem height 속성 제거
Browse files Browse the repository at this point in the history
  • Loading branch information
gxxrxn committed Nov 19, 2023
2 parents bdf9413 + 5ad8033 commit 2e79f04
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 26 deletions.
5 changes: 4 additions & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import type { Preview } from '@storybook/react';
import '@/styles/global.css';

import Layout from '../src/v1/layout/Layout';
import ToastProvider from '../src/ui/Base/Toast/ToastProvider';

const preview: Preview = {
Expand All @@ -22,7 +23,9 @@ const preview: Preview = {
decorators: [
Story => (
<ToastProvider>
<Story />
<Layout>
<Story />
</Layout>
</ToastProvider>
),
],
Expand Down
4 changes: 2 additions & 2 deletions public/icons/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 16 additions & 22 deletions src/app/profile/me/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
'use client';

import { Suspense } from 'react';
import useAllJobQuery from '@/queries/job/useAllJobQuery';
import useMyProfileQuery from '@/queries/user/useMyProfileQuery';
import AuthRequired from '@/ui/AuthRequired';
import TopNavigation from '@/ui/common/TopNavigation';
import ProfileForm from '@/ui/Profile/ProfileForm';

import { isAuthed } from '@/utils/helpers';
import { Skeleton, VStack } from '@chakra-ui/react';
import { Suspense } from 'react';
import AuthRequired from '@/ui/AuthRequired';

import EditProfile from '@/v1/profile/EditProfile';

/**
* @todo
* Fallback UI 추가하기
*/

const EditMyPage = () => {
const EditProfilePage = () => {
return (
<AuthRequired>
<VStack justify="center" align="center">
<TopNavigation pageTitle="내 프로필 수정" />
<Suspense
fallback={
<VStack gap="2rem" align="stretch" w="100%">
<Skeleton w="100%" height="6rem" />
<Skeleton w="100%" height="6rem" />
<Skeleton w="100%" height="6rem" />
</VStack>
}
>
<Contents />
</Suspense>
</VStack>
<Suspense fallback={null}>
<Contents />
</Suspense>
</AuthRequired>
);
};
Expand All @@ -35,8 +29,8 @@ const Contents = () => {
const { data: profileData } = useMyProfileQuery();

return allJobQuery.isSuccess ? (
<ProfileForm profile={profileData} jobGroups={allJobQuery.data.jobGroups} />
<EditProfile profile={profileData} jobGroups={allJobQuery.data.jobGroups} />
) : null;
};

export default EditMyPage;
export default EditProfilePage;
5 changes: 4 additions & 1 deletion src/components/ContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ChakraThemeProvider from '@/components/ChakraThemeProvider';
import ReactQueryProvider from '@/components/ReactQueryProvider';
import { ReactNode } from 'react';
import ErrorPage from '@/app/error';
import ToastProvider from '@/ui/Base/Toast/ToastProvider';

const ContextProvider = ({ children }: { children: ReactNode }) => {
return (
Expand All @@ -15,7 +16,9 @@ const ContextProvider = ({ children }: { children: ReactNode }) => {
<ReactQueryProvider>
<ChakraThemeProvider>
<ErrorBoundary fallbackRender={ErrorPage}>
<Layout>{children}</Layout>
<ToastProvider>
<Layout>{children}</Layout>
</ToastProvider>
</ErrorBoundary>
</ChakraThemeProvider>
</ReactQueryProvider>
Expand Down
76 changes: 76 additions & 0 deletions src/stories/Base/InputLength.stories.tsx
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 />,
};
22 changes: 22 additions & 0 deletions src/ui/Base/InputLength.tsx
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;
193 changes: 193 additions & 0 deletions src/v1/profile/EditProfile.tsx
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;

0 comments on commit 2e79f04

Please sign in to comment.