Skip to content

Commit

Permalink
feat: 내 정보 수정 모달 생성
Browse files Browse the repository at this point in the history
  • Loading branch information
son-daehyeon committed Sep 19, 2024
1 parent 05e0291 commit 4e8e86b
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 7 deletions.
18 changes: 12 additions & 6 deletions src/app/about-us/member/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ const AboutUsMemberPage = () => {
const [members, setMembers] = useState<EachGetMembersResponseDto[]>([]);

useEffect(() => {
const fetchMembers = async () => {
const { members } = await WinkApi.Member.getMembers();
setMembers(members);
};

(async () => {
await fetchMembers();
})();
}, []);

async function fetchMembers() {
const { members } = await WinkApi.Member.getMembers();
setMembers(members);
}

return (
<div className="flex flex-col items-center mt-32">
<div className="flex flex-col items-center justify-center gap-2">
Expand Down Expand Up @@ -76,13 +76,15 @@ const AboutUsMemberPage = () => {
.map(({ _id, name, avatar, description, link, role }) => (
<ProfileCard
key={_id}
id={_id}
name={name}
avatar={avatar}
description={description}
github={link.github}
instagram={link.instagram}
blog={link.blog}
role={RoleKoreanMap[role]}
onUpdate={fetchMembers}
/>
))}
</div>
Expand All @@ -91,7 +93,7 @@ const AboutUsMemberPage = () => {

<div className="flex flex-row items-start gap-8">
{MEMBERS.map(({ title, description, filter, sort }) => (
<div className="flex flex-col items-center justify-center gap-6">
<div className="flex flex-col items-center justify-center gap-6" key={title}>
<h1 className="font-bold text-3xl text-center">&lt;{title}&gt;</h1>
<p className="font-normal text-lg text-center text-zinc-700]">{description}</p>

Expand All @@ -102,13 +104,15 @@ const AboutUsMemberPage = () => {
.map(({ _id, name, avatar, description, link, role }) => (
<ProfileCard
key={_id}
id={_id}
name={name}
avatar={avatar}
description={description}
github={link.github}
instagram={link.instagram}
blog={link.blog}
role={role.endsWith('HEAD') ? '부장' : '차장'}
onUpdate={fetchMembers}
/>
))}
</div>
Expand All @@ -124,13 +128,15 @@ const AboutUsMemberPage = () => {
.map(({ _id, name, avatar, description, link }) => (
<ProfileCard
key={_id}
id={_id}
name={name}
avatar={avatar}
description={description}
github={link.github}
instagram={link.instagram}
blog={link.blog}
role={null}
onUpdate={fetchMembers}
/>
))}
</div>
Expand Down
137 changes: 136 additions & 1 deletion src/component/activity/ProfileCard.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import React from 'react';
import React, { useEffect } from 'react';
import { FaEdit } from 'react-icons/fa';

import Image from 'next/image';
import Link from 'next/link';

import { Fields, FormContainer, Modal, TextField } from '@/component';

import { useMemberStore } from '@/store';

import { useForm } from '@/hook';

import { WinkApi } from '@/api';

import avatarImage from '@/public/profile.svg';

import * as yup from 'yup';

interface ProfileCardProps {
id: string;
name: string;
description: string | null;
avatar: string | null;
github: string | null;
instagram: string | null;
blog: string | null;
role: string | null;
onUpdate?: () => void;
}

type Inputs = 'description' | 'github' | 'instagram' | 'blog';

export const ProfileCard: React.FC<ProfileCardProps> = ({
id,
role,
name,
description,
avatar,
github,
instagram,
blog,
onUpdate,
}) => {
const URL = [
{
Expand All @@ -39,8 +56,74 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
},
];

const fields: Fields<Inputs>[] = [
{ id: 'description', type: 'text', placeholder: '한 줄 소개' },
{ id: 'github', type: 'url', placeholder: 'Github URL' },
{ id: 'instagram', type: 'url', placeholder: 'Instagram URL' },
{ id: 'blog', type: 'url', placeholder: 'Blog URL' },
];

const { member, setMember } = useMemberStore();

const [modalOpen, setModalOpen] = React.useState<boolean>(false);

const { values, setValues, errors, onChange, validate } = useForm<Inputs, string>(
yup.object({
description: yup.string().optional().max(20, '20자 이내로 입력해주세요.'),
github: yup.string().optional().url('유효한 URL을 입력해주세요.'),
instagram: yup.string().optional().url('유효한 URL을 입력해주세요.'),
blog: yup.string().optional().url('유효한 URL을 입력해주세요.'),
}),
);

useEffect(() => {
setValues({
description: member?.description || '',
github: member?.link.github || '',
instagram: member?.link.instagram || '',
blog: member?.link.blog || '',
});
}, []);

const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];

if (file) {
await WinkApi.Member.updateMyAvatar(file);
const { member } = await WinkApi.Auth.myInfo();

setMember(member);
onUpdate?.();
}
};

const handleSaveInfo = async () => {
if (!(await validate())) {
return;
}

await WinkApi.Member.updateMyInfo({
description: values.description === '' ? null : values.description,
github: values.github === '' ? null : values.github,
instagram: values.instagram === '' ? null : values.instagram,
blog: values.blog === '' ? null : values.blog,
});
const { member } = await WinkApi.Auth.myInfo();

setMember(member);
onUpdate?.();

setModalOpen(false);
};

return (
<div className="relative w-72 border border-wink-400 rounded-lg">
{member?._id === id && (
<div className="absolute top-4 right-4">
<FaEdit className="text-gray-400 cursor-pointer" onClick={() => setModalOpen(true)} />
</div>
)}

{role && (
<div className="absolute -top-3 left-5 bg-white px-3 py-0.5 rounded-full border border-wink-400 text-sm font-bold">
{role}
Expand Down Expand Up @@ -73,6 +156,58 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
</Link>
))}
</div>

<Modal isOpen={modalOpen} onClose={() => setModalOpen(false)}>
<h2 className="text-xl font-bold mb-4">내 프로필 수정</h2>
<div className="flex flex-col gap-2">
{member?._id === id && (
<input
id="fileInput"
type="file"
accept="image/*"
onChange={(e) => handleImageUpload(e)}
className="hidden"
/>
)}

<Image
src={avatar || avatarImage}
alt="Profile"
width={48}
height={48}
className={`w-24 h-24 object-cover rounded-full self-center ${member?._id === id ? 'cursor-pointer' : ''}`}
onClick={() => {
if (member?._id !== id) {
return;
}

document.getElementById('fileInput')?.click();
}}
/>

<FormContainer values={values} errors={errors} onChange={onChange}>
{fields.map(({ id, ...rest }) => (
<div key={id}>
<label htmlFor={id} className="text-sm">
{rest.placeholder}
</label>
<TextField
id={id}
className="px-3 py-2.5 border border-border rounded focus:outline-none focus:ring-1 focus:ring-wink-300 placeholder-gray-600"
{...rest}
/>
</div>
))}
</FormContainer>

<button
className="w-fit mt-4 px-8 py-2 bg-wink-500 hover:bg-wink-600 text-white rounded-md self-center"
onClick={handleSaveInfo}
>
저장
</button>
</div>
</Modal>
</div>
);
};

0 comments on commit 4e8e86b

Please sign in to comment.