Skip to content
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
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