Skip to content

Commit

Permalink
Merge pull request #271 from gloddy-dev/feature/270-profile-personalityi
Browse files Browse the repository at this point in the history
[Feature] 회원 정보 수정 - 성격 api 구현
  • Loading branch information
guesung authored Aug 28, 2023
2 parents 2507eda + 98ff076 commit 412cac4
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 74 deletions.
9 changes: 6 additions & 3 deletions src/apis/profile/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getMates, getPraises, getProfile } from './apis';
import { Keys } from './keys';
import { personalityList } from '@/constants/personalityList';
import { useSuspenseQuery } from '@suspensive/react-query';

export const useGetProfile = () =>
Expand All @@ -8,13 +9,15 @@ export const useGetProfile = () =>
const { birth } = data;

const formattedBirth = {
year: birth.split('.')[0],
month: birth.split('.')[1],
date: birth.split('.')[2],
year: +birth.split('.')[0] + '년',
month: +birth.split('.')[1] + '월',
date: +birth.split('.')[2] + '일',
};

return { ...data, birth: formattedBirth };
},
staleTime: Infinity,
cacheTime: Infinity,
});

export const useGetPraises = () => useSuspenseQuery(Keys.getPraises(), getPraises);
Expand Down
2 changes: 1 addition & 1 deletion src/apis/profile/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface ProfileRequest {
birth: string;
gender: 'MAIL' | 'FEMAIL';
introduce: string;
personalities: string[];
personalities: Array<PersonalityType['keywordInEnglish']>;
}

export interface PraisesResponse {
Expand Down
7 changes: 3 additions & 4 deletions src/app/(main)/profile/components/ProfileSection.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ export default function ProfileSection() {
profileData;
const pathname = usePathname();

console.log(imageUrl);

return (
<section className="h-500 rounded-b-24 bg-white shadow-float">
<section className="rounded-b-24 bg-white shadow-float">
<Flex direction="column" align="center">
<Spacing size={7} />
<Avatar imageUrl={imageUrl} size="large" />
Expand All @@ -37,7 +35,7 @@ export default function ProfileSection() {
<span>{age}</span>
</Flex>
<Spacing size={16} />
<Flex className="gap-4">
<Flex className="gap-4" wrap="wrap" justify="center">
{personalities.map((personality) => (
<Tag key={personality} className="border-none bg-brand-color text-primary-dark">
{personalityList.find((it) => it.keywordInEnglish === personality)?.keyword}
Expand Down Expand Up @@ -81,6 +79,7 @@ export default function ProfileSection() {
<h4 className="text-secondary text-h4 text-sign-brand">{reviewCount}</h4>
</Link>
</Flex>
<Spacing size={32} />
</Flex>
</section>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,15 @@ import { FormProvider, useForm, useFormContext } from 'react-hook-form';
import type { ProfileEditState } from '../type';
import type { StrictPropsWithChildren } from '@/types';

const formDefaultValue: ProfileEditState = {
name: '',
birth: {
year: '',
month: '',
date: '',
},
gender: 'MAIL',
imageUrl: '',
introduce: '',
personalities: [],
};
interface EditProviderProps {
defaultValues?: ProfileEditState;
}

export default function EditProvider({ children }: StrictPropsWithChildren) {
const hookForm = useForm<ProfileEditState>({ defaultValues: formDefaultValue, mode: 'onBlur' });
export default function EditProvider({
defaultValues,
children,
}: StrictPropsWithChildren<EditProviderProps>) {
const hookForm = useForm<ProfileEditState>({ defaultValues, mode: 'onBlur' });

return <FormProvider {...hookForm}>{children}</FormProvider>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';
import EditProvider from './EditProvider.client';
import Step1 from './step1/Step1.client';
import Step2 from './step2/Step2.client';
import { useGetProfile } from '@/apis/profile';
import { useState } from 'react';

export default function ProfileEdit() {
const [step, setStep] = useState(1);
const { data: defaultProfileData } = useGetProfile();

return (
<EditProvider defaultValues={defaultProfileData}>
{step === 1 && <Step1 onNext={() => setStep(2)} />}
{step === 2 && <Step2 onPrev={() => setStep(1)} />}
</EditProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Step1Header from './Step1Header';
import Step1InputForm from './Step1InputForm.client';
import { Spacing } from '@/components/common/Spacing';

interface Step1Props {
onNext: () => void;
}

export default function Step1({ onNext }: Step1Props) {
return (
<>
<Step1Header />
<Spacing size={20} />
<Step1InputForm onNext={onNext} />
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Header } from '@/components/Header';
import Image from 'next/image';
import Link from 'next/link';

export default function EditHeader() {
export default function Step1Header() {
return (
<Header>
<Header.Left className="px-4">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';
import { useEditContext } from './EditProvider.client';
import { useGetProfile, usePatchProfile } from '@/apis/profile';
import { useEditContext } from '../EditProvider.client';
import { usePatchProfile } from '@/apis/profile';
import { Avatar } from '@/components/Avatar';
import { Button, ButtonGroup } from '@/components/Button';
import { Spacing } from '@/components/common/Spacing';
Expand All @@ -10,68 +9,59 @@ import { SegmentGroup } from '@/components/SegmentGroup';
import { Tag } from '@/components/Tag';
import { TextField, TextFieldController } from '@/components/TextField';
import { personalityList } from '@/constants/personalityList';
import { useDidMount } from '@/hooks/common/useDidMount';
import useBottomSheet from '@/hooks/useBottomSheet';
import { useFileUpload } from '@/hooks/useFileUpload';
import { formatDateDTO } from '@/utils/formatDateDTO';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useController } from 'react-hook-form';

import type { ProfileEditState } from '../type';
import type { ProfileEditState } from '../../type';

interface Step1InputFormProps {
onNext: () => void;
}
export default function Step1InputForm({ onNext }: Step1InputFormProps) {
const router = useRouter();
const { mutate } = usePatchProfile();

export default function InputForm() {
const hookForm = useEditContext();
const { control } = hookForm;
const {
data: { imageUrl, introduce, name, personalities, gender, birth },
} = useGetProfile();
const { control, watch, handleSubmit, setValue, register, formState } = hookForm;
const birth = watch('birth');
const personalities = watch('personalities');
const imageUrl = watch('imageUrl');

const {
field: { value, onChange },
} = useController({
name: 'imageUrl',
control,
});

const { handleFileUploadClick } = useFileUpload((files) => onChange(files[0]));

const { watch, handleSubmit, setValue, register, formState } = hookForm;
const {
isOpen: isOpenBirthdayBottomSheet,
open: openBirthdayBottomSheet,
close: closeBirthdayBottomSheet,
} = useBottomSheet();

const { mutate } = usePatchProfile();

useDidMount(() => {
setValue('imageUrl', imageUrl || '');
setValue('name', name || '');
setValue('introduce', introduce || '');
setValue('gender', gender || 'MAIL');
setValue('birth', birth || {});
});

const onSubmit = (data: ProfileEditState) => {
if (!isAllTyped) return;
const { birth, ...rest } = data;

const profileData = {
...rest,
birth: formatDateDTO(birth),
personalities: ['OUTGOING'],
};

mutate(profileData);
mutate(profileData, { onSuccess: () => router.push('/profile/setting') });
};

const isBirthDayEntered = !!birth.year && !!birth.month && !!birth.date;
const isAllTyped = formState.isValid && isBirthDayEntered && !!watch('gender');

return (
<Flex as="form" direction="column" onSubmit={handleSubmit(onSubmit)} className="px-20">
<Spacing size={20} />
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col px-20">
<Flex justify="center">
<Avatar
imageUrl={imageUrl}
Expand Down Expand Up @@ -145,7 +135,7 @@ export default function InputForm() {
register={register('introduce', {
required: true,
pattern: {
value: /^[a-zA-Z0-9ㄱ-ㅎ가-힣]{0,100}$/,
value: /^[\s\S]{0,100}$/,
message: '* 최대 100자 이하로 작성해주세요.',
},
})}
Expand All @@ -154,26 +144,25 @@ export default function InputForm() {

<p className="text-subtitle-3">성격</p>
<Spacing size={4} />
<Flex className="gap-4" align="center">
<Flex className="flex-wrap gap-4" align="center">
{personalities.map((personality, index) => (
<Tag isSelected size="small" variant="solid" key={index}>
{personalityList.find((it) => it.keywordInEnglish === personality)?.keyword}
</Tag>
))}
<div className="rounded-full bg-sign-brand">
<Link href="/profile/setting/edit/personality">
<Image src="/icons/24/add.svg" width={24} height={24} alt="plus" />
</Link>

<div className="rounded-full bg-sign-brand" onClick={onNext}>
<Image src="/icons/24/add.svg" width={24} height={24} alt="plus" />
</div>
</Flex>

<Spacing size={100} />

<ButtonGroup>
<Button type="submit" disabled={!isAllTyped}>
완료
확인
</Button>
</ButtonGroup>
</Flex>
</form>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Step2Header from './Step2Header';
import Step2InputForm from './Step2InputForm.client';
import { Spacing } from '@/components/common/Spacing';

interface Step2Props {
onPrev: () => void;
}

export default function Step2({ onPrev }: Step2Props) {
return (
<>
<Step2Header onClose={onPrev} />
<div className="px-20 pb-16 pt-32 text-h3 text-sign-cto">
사용자님의 성격을
<br />
선택해주세요!
</div>
<p className="px-20 text-subtitle-2 text-sign-tertiary">3개 이상 선택해주세요.</p>
<Spacing size={16} />
<Step2InputForm onClose={onPrev} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IconButton } from '@/components/Button';
import { Header } from '@/components/Header';
import Image from 'next/image';

interface Step2HeaderProps {
onClose: () => void;
}

export default function Step2Header({ onClose }: Step2HeaderProps) {
return (
<Header>
<Header.Left className="px-4">
<IconButton size="large" onClick={onClose}>
<Image src="/icons/24/close.svg" width={24} height={24} alt="back" />
</IconButton>

<p className="text-subtitle-1">성격 선택</p>
</Header.Left>
</Header>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client';

import { useEditContext } from '../EditProvider.client';
import { Button, ButtonGroup } from '@/components/Button';
import { Tag } from '@/components/Tag';
import { personalityList } from '@/constants/personalityList';
import { PersonalityType } from '@/types';
import { useCallback } from 'react';

interface InputFormProps {
onClose: () => void;
}

export default function Step2InputForm({ onClose }: InputFormProps) {
const { watch, setValue } = useEditContext();

const handleSelectedClick = useCallback(
(personality: PersonalityType['keywordInEnglish']) => {
const list = watch('personalities');
if (list.includes(personality)) {
setValue(
'personalities',
list.filter((it: string) => it !== personality)
);
return;
}
setValue('personalities', [...list, personality]);
},
[setValue, watch]
);

return (
<form className="px-20">
<section className="flex flex-wrap gap-12">
{personalityList.map((tag) => (
<Tag
key={tag.id}
id={tag.keywordInEnglish}
isSelected={watch('personalities').includes(tag.keywordInEnglish)}
onSelected={handleSelectedClick}
>
{tag.keyword}
</Tag>
))}
</section>
<ButtonGroup>
<Button disabled={watch('personalities').length < 3} type="button" onClick={onClose}>
완료
</Button>
</ButtonGroup>
</form>
);
}
Loading

0 comments on commit 412cac4

Please sign in to comment.