Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 내정보 관리 페이지 #83

Merged
merged 3 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/pages/mypage/manage/bottomSheetReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export type ModalState = {
isNicknameModalOpen: boolean;
isBirthModalOpen: boolean;
};

export type ModalAction =
| { type: 'OPEN_NICKNAME_MODAL' }
| { type: 'CLOSE_NICKNAME_MODAL' }
| { type: 'OPEN_BIRTH_MODAL' }
| { type: 'CLOSE_BIRTH_MODAL' }
| { type: 'CLOSE_ALL_MODALS' };

export const initialModalState: ModalState = {
isNicknameModalOpen: false,
isBirthModalOpen: false,
};

export const modalReducer = (
state: ModalState,
action: ModalAction,
): ModalState => {
switch (action.type) {
case 'OPEN_NICKNAME_MODAL':
return {
...state,
isNicknameModalOpen: true,
};
case 'CLOSE_NICKNAME_MODAL':
return {
...state,
isNicknameModalOpen: false,
};
case 'OPEN_BIRTH_MODAL':
return {
...state,
isBirthModalOpen: true,
};
case 'CLOSE_BIRTH_MODAL':
return {
...state,
isBirthModalOpen: false,
};
case 'CLOSE_ALL_MODALS':
return {
isNicknameModalOpen: false,
isBirthModalOpen: false,
};
default:
return state;
}
};
163 changes: 163 additions & 0 deletions src/pages/mypage/manage/components/birth-bottom-sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { isPast, isValid, parse } from 'date-fns';
import { SubmitHandler, useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
import { DeleteCir } from '@/assets';
import { birth } from '@/pages/userinfo/schema';
import { BottomSheet } from '@/ui/bottom-sheet/bottom-sheet';
import { Button } from '@/ui/button';
import {
Form,
FormErrorMessage,
FormField,
FormItem,
FormLabel,
} from '@/ui/form';
import { Input, InputContainer, InputRightElement } from '@/ui/input';
import { Spacer } from '@/ui/spacer/spacer';
import * as styles from './bottom-sheet.css';

const birthSchema = z.object({

Check warning on line 19 in src/pages/mypage/manage/components/birth-bottom-sheet.tsx

View workflow job for this annotation

GitHub Actions / Run Tests

'birthSchema' is assigned a value but only used as a type. Allowed unused vars must match /^_/u
birth,
});

type BirthSchema = z.infer<typeof birthSchema>;

type BirthBottomSheetProps = {
initialBirth: string;
open: boolean;
onOpenChange: VoidFunction;
};

export const BirthBottomSheet = (props: BirthBottomSheetProps) => {
const { initialBirth, open, onOpenChange } = props;

const form = useForm<BirthSchema>({
defaultValues: {
birth: initialBirth,
},
});
const {
control,
formState: { errors },
getValues,
setError,
setValue,
handleSubmit,
} = form;

const birth = useWatch({
control,
name: 'birth',
defaultValue: initialBirth,
});

const onSubmit: SubmitHandler<BirthSchema> = (data) => {
console.log(data);

checkDate(getValues('birth'));

if (errors.birth) {
return;
}
};

const checkDate = (date: string) => {
if (!/^\d{4}\.\d{2}\.\d{2}$/.test(date)) {
setError('birth', {
type: 'inValidDate',
message: 'YYYY.MM.DD 형식으로 입력해주세요.',
});
return;
}

const parsedDate = parse(date, 'yyyy.MM.dd', new Date());

if (!isValid(parsedDate)) {
setError('birth', {
type: 'inValidDate',
message: '잘못된 날짜에요.',
});
return;
}

if (!isPast(parsedDate)) {
setError('birth', {
type: 'inValidDate',
message: '과거의 날짜만 입력할 수 있어요.',
});
return;
}
};

const disabled = birth.length === 0;

return (
<BottomSheet.Root open={open} onOpenChange={onOpenChange}>
<BottomSheet.Overlay />
<BottomSheet.Content className={styles.container}>
<BottomSheet.Handle />
<Spacer size={20} />
<BottomSheet.Title>닉네임 변경</BottomSheet.Title>
<Spacer size={32} />

<Form {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<FormField
control={control}
name="birth"
render={({ field }) => (
<FormItem>
<FormLabel>생년월일 (8자리)</FormLabel>
<InputContainer>
<Input
{...field}
variant={errors.birth ? 'error' : 'default'}
placeholder="YYYY.MM.DD"
onChange={({ target: { value } }) => {
const formattedDate = formatBirthDate(value);
field.onChange(formattedDate);
}}
/>
{field.value && (
<InputRightElement onClick={() => setValue('birth', '')}>
<DeleteCir />
</InputRightElement>
)}
</InputContainer>
<FormErrorMessage />
</FormItem>
)}
/>
<div className={styles.buttonContainer}>
<Button
size="large"
className={styles.button}
disabled={disabled}
>
완료
</Button>
</div>
</form>
</Form>
</BottomSheet.Content>
</BottomSheet.Root>
);
};

const formatBirthDate = (value: string) => {
const numbers = value.replace(/\D/g, '');

if (numbers.length > 8) {
return value.slice(0, -1);
}

if (numbers.length < value.length) {
return numbers;
}

const format =
numbers.length <= 6 ? /(\d{4})(\d{1,2})/ : /(\d{4})(\d{2})(\d{2})/;
const template = numbers.length <= 6 ? '$1.$2' : '$1.$2.$3';

return numbers.replace(format, template);
};
23 changes: 23 additions & 0 deletions src/pages/mypage/manage/components/bottom-sheet.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { style } from '@vanilla-extract/css';

export const container = style({
height: 'calc(100dvh - 120px)',
});

export const buttonContainer = style({
width: '100%',
'@media': {
'screen and (min-width: 440px)': {
width: 440,
},
},
position: 'fixed',
left: 0,
bottom: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '14px 20px',
});

export const button = style({});
104 changes: 104 additions & 0 deletions src/pages/mypage/manage/components/nick-name-bottom-sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { SubmitHandler, useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
import { DeleteCir } from '@/assets';
import { name } from '@/pages/userinfo/schema';
import { BottomSheet } from '@/ui/bottom-sheet/bottom-sheet';
import { Button } from '@/ui/button';
import {
Form,
FormErrorMessage,
FormField,
FormItem,
FormLabel,
} from '@/ui/form';
import { Input, InputContainer, InputRightElement } from '@/ui/input';
import { Spacer } from '@/ui/spacer/spacer';
import * as styles from './bottom-sheet.css';

export const nameSchema = z.object({
name,
});

type NameSchema = z.infer<typeof nameSchema>;

type NickNameBottomSheetProps = {
initialNickName: string;
open: boolean;
onOpenChange: VoidFunction;
};

export const NickNameBottomSheet = (props: NickNameBottomSheetProps) => {
const { initialNickName, open, onOpenChange } = props;

const form = useForm<NameSchema>({
defaultValues: {
name: initialNickName,
},
});
const {
control,
formState: { errors },
setValue,
handleSubmit,
} = form;

const name = useWatch({
control,
name: 'name',
defaultValue: initialNickName,
});

const onSubmit: SubmitHandler<NameSchema> = (data) => {
console.log(data);
};

const disabled = name.length === 0;

return (
<BottomSheet.Root open={open} onOpenChange={onOpenChange}>
<BottomSheet.Overlay />
<BottomSheet.Content className={styles.container}>
<BottomSheet.Handle />
<Spacer size={20} />
<BottomSheet.Title>닉네임 변경</BottomSheet.Title>
<Spacer size={32} />

<Form {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<FormField
control={control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel required>닉네임</FormLabel>
<InputContainer>
<Input
{...field}
variant={errors.name ? 'error' : 'default'}
placeholder="한글, 영어, 숫자만 사용(최대 10자)"
/>
{field.value && (
<InputRightElement onClick={() => setValue('name', '')}>
<DeleteCir />
</InputRightElement>
)}
</InputContainer>
<FormErrorMessage />
</FormItem>
)}
/>
<div className={styles.buttonContainer}>
<Button
size="large"
className={styles.button}
disabled={disabled}
>
완료
</Button>
</div>
</form>
</Form>
</BottomSheet.Content>
</BottomSheet.Root>
);
};
Loading
Loading