Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions src/components/pages/meetup/meetup-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export const MeetupModal = ({ type }: Props) => {

return (
<ModalContent>
<ModalTitle>{title}</ModalTitle>
<ModalDescription className='mb-6'>{description}</ModalDescription>
<ModalTitle className='pt-8 text-center'>{title}</ModalTitle>
<ModalDescription className='mb-6 text-center'>{description}</ModalDescription>
<div className='flex flex-row gap-2'>
<button
className='typo-text-sm-semibold w-34 cursor-pointer rounded-2xl border-1 border-gray-400 bg-white py-3 text-gray-600'
Expand Down
1 change: 1 addition & 0 deletions src/components/pages/profile/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { ProfileCard } from './profile-card';
export { ProfileDescription } from './profile-description';
export { ProfileDescriptionBadge } from './profile-description-badge';
export { ProfileEditModal } from './profile-edit-modal';
export { ProfileFollowsBadge } from './profile-follows-badge';
export { ProfileInfo } from './profile-info';
export { ProfileSetting } from './profile-setting';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Image from 'next/image';

import { AnyFieldApi } from '@tanstack/react-form';

import { Icon } from '@/components/icon';
import { ImageInput, ImageInputProps } from '@/components/ui';
import { cn } from '@/lib/utils';

type ImageUploadPropsWithoutChildren = Omit<ImageInputProps, 'children'>;

interface Props extends ImageUploadPropsWithoutChildren {
field: AnyFieldApi;
}
const ImageField = ({ field, initialImages }: Props) => {
return (
<div className='flex-center py-6'>
<ImageInput
accept='image/*'
initialImages={initialImages}
maxFiles={1}
mode='replace'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저번에 코멘트 남겼던 mode가 이렇게 사용되는군요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요! 프로필에서는 항상 이미지가 교체되기 때문에 replace를 적용했습니다 :)

multiple={false}
value={field.state.value}
onChange={field.handleChange}
>
{(images, _onRemoveImageClick, onFileSelectClick) => (
<>
{Object.entries(images).map(([url, _file]) => (
<div key={url} className='relative aspect-square size-24'>
<Image
className='rounded-full border-1 border-[rgba(0,0,0,0.04)]'
alt='프로필 이미지'
fill
src={url}
/>
<button
className={cn(
'flex-center absolute -right-1.75 bottom-0 size-8 cursor-pointer rounded-full border-1 border-gray-300 bg-gray-100',
'hover:scale-110 hover:bg-gray-200',
'transition-all duration-300',
)}
type='button'
onClick={onFileSelectClick}
>
<Icon id='edit' className='size-5 text-gray-600' />
</button>
</div>
))}
</>
)}
</ImageInput>
</div>
);
};

export default ImageField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AnyFieldApi } from '@tanstack/react-form';

import { Input, Label } from '@/components/ui';

interface Props {
field: AnyFieldApi;
}

export const MBTIField = ({ field }: Props) => {
return (
<div className='mt-3 flex w-full flex-col gap-1'>
<Label htmlFor='post-meetup-title' required>
MBTI
</Label>

<Input
className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300'
placeholder='MBTI를 입력해주세요'
required
type='text'
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AnyFieldApi } from '@tanstack/react-form';

import { Input, Label } from '@/components/ui';

interface Props {
field: AnyFieldApi;
}

export const MessageField = ({ field }: Props) => {
return (
<div className='mt-3 flex w-full flex-col gap-1'>
<Label htmlFor='post-meetup-title' required>
한 줄 소개
</Label>

<Input
className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300'
placeholder='한 줄 소개를 입력해주세요'
required
type='text'
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AnyFieldApi } from '@tanstack/react-form';

import { Input, Label } from '@/components/ui';

interface Props {
field: AnyFieldApi;
}

export const NickNameField = ({ field }: Props) => {
return (
<div className='flex w-full flex-col gap-1'>
<Label htmlFor='post-meetup-title' required>
닉네임
</Label>

<Input
className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300'
placeholder='닉네임을 입력해주세요'
required
type='text'
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
);
};
70 changes: 70 additions & 0 deletions src/components/pages/profile/profile-edit-modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client';
import { useForm } from '@tanstack/react-form';

import {
Button,
ImageRecord,
ModalContent,
ModalDescription,
ModalTitle,
useModal,
} from '@/components/ui';
import { User } from '@/types/service/user';

import ImageField from '../profile-edit-fields/image-field';
import { MBTIField } from '../profile-edit-fields/mbti-field';
import { MessageField } from '../profile-edit-fields/message-field';
import { NickNameField } from '../profile-edit-fields/nickname-field';

interface Props {
user: User;
}

export const ProfileEditModal = ({ user }: Props) => {
const { profileImage: image, nickName, profileMessage, mbti } = user;

const { close } = useModal();

const form = useForm({
defaultValues: {
profileImage: {
[image]: null,
} as ImageRecord,
nickName,
profileMessage,
mbti,
},
onSubmit: async ({ value }) => {
console.log(value);
close();
},
});

return (
<ModalContent>
<ModalTitle>프로필 수정</ModalTitle>
<ModalDescription className='sr-only'>
이 모달은 자신의 프로필을 수정할 수 있는 모달입니다.
</ModalDescription>
<form
className='w-full max-w-70.5'
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<form.Field children={(field) => <ImageField field={field} />} name='profileImage' />
<form.Field children={(field) => <NickNameField field={field} />} name='nickName' />
<form.Field children={(field) => <MessageField field={field} />} name='profileMessage' />
<form.Field children={(field) => <MBTIField field={field} />} name='mbti' />
<div className='mt-6 flex gap-2'>
<Button variant='tertiary' onClick={close}>
취소
</Button>
<Button type='submit'>수정하기</Button>
</div>
</form>
</ModalContent>
);
};
12 changes: 10 additions & 2 deletions src/components/pages/profile/profile-info/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { Button } from '@/components/ui';
'use client';
import { Button, useModal } from '@/components/ui';
import { User } from '@/types/service/user';

import { ProfileCard } from '../profile-card';
import { ProfileDescription } from '../profile-description';
import { ProfileEditModal } from '../profile-edit-modal';
import { ProfileFollowsBadge } from '../profile-follows-badge';

interface Props {
user: User;
}

export const ProfileInfo = ({ user }: Props) => {
const { open } = useModal();

const handleButtonClick = () => {
open(<ProfileEditModal user={user} />);
};

return (
<section className='px-4 py-8'>
<ProfileCard user={user} />
<ProfileFollowsBadge user={user} />
<Button>팔로우</Button>
<Button onClick={handleButtonClick}>프로필 수정하기</Button>
<ProfileDescription user={user} />
</section>
);
Expand Down
10 changes: 6 additions & 4 deletions src/components/ui/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,15 @@ export const ModalContent = ({ children }: ModalContentProps) => {
>
<div
ref={modalRef}
className='relative flex flex-col items-center rounded-3xl bg-white p-4 pt-12'
className='rounded-3xl bg-white p-5'
onClick={(e) => {
e.stopPropagation();
}}
>
{children}
<ModalCloseButton />
<div className='relative'>
{children}
<ModalCloseButton />
</div>
</div>
</div>,
document.body,
Expand Down Expand Up @@ -212,7 +214,7 @@ export const ModalCloseButton = () => {
const { close } = useModal();
return (
<button
className='absolute top-4 right-4 rounded-sm transition-colors duration-300 hover:bg-gray-200 active:bg-gray-200'
className='absolute top-0 right-0 rounded-sm transition-colors duration-300 hover:bg-gray-200 active:bg-gray-200'
aria-label='모달 닫기'
type='button'
onClick={close}
Expand Down