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
32 changes: 6 additions & 26 deletions src/app/(user)/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
'use client';
import { ProfileInfo, ProfileSetting } from '@/components/pages/profile';
import { useGetUser } from '@/hooks/use-user';
import { useDeleteUser } from '@/hooks/use-user/use-user-delete';
import { useUpdateUser } from '@/hooks/use-user/use-user-update';

const MyPage = () => {
const userId = 1;
// 여기서 user 정보를 확인해서 undefined이면 로그인페이지로 리다이렉트

const { data } = useGetUser({ userId });

const { mutate: updateUser } = useUpdateUser({
nickName: '새로운 이름',
profileMessage: '새로운 메시지',
});

const { mutate: deleteUser } = useDeleteUser();

const handleUpdateClick = () => {
updateUser();
};

const handleDeleteClick = () => {
deleteUser();
};

const { data: user } = useGetUser({ userId });
if (!user) return null;
return (
<div>
<p>{data?.id}</p>
<p>{data?.nickName}</p>
<p>{data?.mbti}</p>

<button onClick={handleUpdateClick}>프로필 변경 요청</button>
<button onClick={handleDeleteClick}>회원 탈퇴</button>
<div className='flex min-h-[calc(100dvh-113px)] flex-col justify-between bg-gray-50'>
<ProfileInfo user={user} />
<ProfileSetting user={user} />
</div>
);
};
Expand Down
25 changes: 6 additions & 19 deletions src/app/(user)/profile/[userId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import { use } from 'react';

import { useFollowUser, useGetUser, useUnfollowUser } from '@/hooks/use-user';
import { ProfileInfo } from '@/components/pages/profile';
import { useGetUser } from '@/hooks/use-user';

interface Props {
params: Promise<{ userId: string }>;
Expand All @@ -12,27 +13,13 @@ const ProfilePage = ({ params }: Props) => {
const { userId: id } = use(params);
const userId = Number(id);

const { data } = useGetUser({ userId });
const { mutate: followUser } = useFollowUser({ followeeId: userId });
const { data: user } = useGetUser({ userId });

const { mutate: unfollowUser } = useUnfollowUser({ followeeId: userId });

const handleFollowClick = () => {
followUser();
};

const handleUnfollowClick = () => {
unfollowUser();
};
if (!user) return null;

return (
<div>
<p>{data?.id}</p>
<p>{data?.nickName}</p>
<p>{data?.mbti}</p>

<button onClick={handleFollowClick}>팔로우</button>
<button onClick={handleUnfollowClick}>팔로우 취소</button>
<div className='bg-gray-50'>
<ProfileInfo user={user} />
</div>
);
};
Expand Down
6 changes: 6 additions & 0 deletions src/components/pages/profile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { ProfileCard } from './profile-card';
export { ProfileDescription } from './profile-description';
export { ProfileDescriptionBadge } from './profile-description-badge';
export { ProfileFollowsBadge } from './profile-follows-badge';
export { ProfileInfo } from './profile-info';
export { ProfileSetting } from './profile-setting';
20 changes: 20 additions & 0 deletions src/components/pages/profile/profile-card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Image from 'next/image';

import { User } from '@/types/service/user';

interface Props {
user: User;
}

export const ProfileCard = ({ user }: Props) => {
const { profileImage, nickName, profileMessage } = user;
return (
<div className='flex-col-center mb-6'>
<div className='relative mb-3 size-24 overflow-hidden rounded-full'>
<Image alt='image' fill objectFit='cover' src={profileImage} />
</div>
<h2 className='text-text-xl-bold text-gray-900'>{nickName}</h2>
<p className='text-text-sm-medium text-gray-600'>{profileMessage}</p>
</div>
);
};
25 changes: 25 additions & 0 deletions src/components/pages/profile/profile-description-badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Icon, IconId } from '@/components/icon';

export interface ProfileDescriptionBadgeProps {
label: string;
iconId: IconId;
value: string;
}

interface Props {
badgeItems: ProfileDescriptionBadgeProps;
}

export const ProfileDescriptionBadge = ({ badgeItems }: Props) => {
return (
<div className='flex flex-row items-center gap-4'>
<div className='flex-center size-10 rounded-xl bg-gray-100'>
<Icon id={badgeItems.iconId} className='text-mint-500 size-6' />
</div>
<div className='flex flex-col'>
<span className='text-text-xs-medium text-gray-500'>{badgeItems.label}</span>
<span className='text-text-md-semibold text-gray-700'>{badgeItems.value}</span>
</div>
</div>
);
};
51 changes: 51 additions & 0 deletions src/components/pages/profile/profile-description/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { User } from '@/types/service/user';

import {
ProfileDescriptionBadge,
ProfileDescriptionBadgeProps,
} from '../profile-description-badge';

const formatISO = (dateString: string) => {
const date = new Date(dateString);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}. ${m}. ${d}`;
};

interface Props {
user: User;
}

export const ProfileDescription = ({ user }: Props) => {
const listMap: ProfileDescriptionBadgeProps[] = [
{
label: 'MBTI',
iconId: 'symbol',
value: user.mbti,
},
{
label: '가입 일자',
iconId: 'calendar-2',
value: formatISO(user.createdAt),
},
{
label: '모임 참여',
iconId: 'users-2',
value: `${user.joinedCount}회`,
},
{
label: '모임 생성',
iconId: 'map-pin-2',
value: `${user.createdCount}회`,
},
];

return (
<div className='bg-mono-white shadow-card mt-6 flex flex-col gap-5 rounded-3xl px-6 py-6.25'>
{listMap.map((item) => (
<ProfileDescriptionBadge key={item.label} badgeItems={item} />
))}
</div>
);
};
36 changes: 36 additions & 0 deletions src/components/pages/profile/profile-follows-badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Fragment } from 'react/jsx-runtime';

import { User } from '@/types/service/user';

interface Props {
user: User;
}

export const ProfileFollowsBadge = ({ user }: Props) => {
const listMap = [
{
label: '팔로워',
value: user.followersCount,
},
{
label: '팔로잉',
value: user.followeesCount,
},
];

const listLength = listMap.length;

return (
<div className='flex-center bg-mono-white shadow-card mb-4 rounded-3xl py-4'>
{listMap.map((item, index) => (
<Fragment key={item.label}>
<div className='flex-col-center w-full gap-0.75 py-0.75'>
<span className='text-text-xl-bold text-gray-800'>{item.value.toLocaleString()}</span>
<span className='text-text-xs-medium text-gray-500'>{item.label.toLocaleString()}</span>
</div>
{index < listLength - 1 && <div className='h-10 w-1 border-r-1 border-gray-200' />}
</Fragment>
))}
</div>
);
};
21 changes: 21 additions & 0 deletions src/components/pages/profile/profile-info/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Button } from '@/components/ui';
import { User } from '@/types/service/user';

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

interface Props {
user: User;
}

export const ProfileInfo = ({ user }: Props) => {
return (
<section className='px-4 py-8'>
<ProfileCard user={user} />
<ProfileFollowsBadge user={user} />
<Button>팔로우</Button>
<ProfileDescription user={user} />
</section>
);
};
26 changes: 26 additions & 0 deletions src/components/pages/profile/profile-setting/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';
import { useState } from 'react';

import { User } from '@/types/service/user';

import { ProfileActionButton, ProfileToggleButton } from './profile-setting-button';

interface Props {
user: User;
}

export const ProfileSetting = ({ user }: Props) => {
console.log(user);
// useState 로직은 추후 삭제 예정
const [isOn, setIsOn] = useState(false);

return (
<section className='bg-mono-white flex flex-col gap-3 px-3 py-6'>
<ProfileToggleButton value={isOn} onClick={() => setIsOn((prev) => !prev)}>
알림 받기
</ProfileToggleButton>
<ProfileActionButton onClick={() => console.log('로그아웃')}>로그아웃</ProfileActionButton>
<ProfileActionButton onClick={() => console.log('회원탈퇴')}>회원탈퇴</ProfileActionButton>
</section>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client';

import React, { ButtonHTMLAttributes } from 'react';

import * as m from 'motion/react-m';

interface ToggleButtonProps extends Omit<ButtonProps, 'value'> {
value?: boolean;
}
export const ProfileToggleButton = ({ children, value = false, ...props }: ToggleButtonProps) => {
return (
<Button {...props}>
{children}
<ToggleUI value={value} />
</Button>
);
};

type ActionButtonProps = ButtonProps;

export const ProfileActionButton = ({ children, ...props }: ActionButtonProps) => {
return <Button {...props}>{children}</Button>;
};

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
}

const Button = ({ children, ...props }: ButtonProps) => {
return (
<button
className='text-text-sm-semibold flex cursor-pointer flex-row items-center justify-between rounded-xl px-3 py-3 text-gray-700 transition-all duration-300 hover:bg-gray-50'
{...props}
>
{children}
</button>
);
};

interface ToggleUIProps {
value?: boolean;
}

const ToggleUI = ({ value = false }: ToggleUIProps) => {
return (
<div className='bg-mint-500 flex h-5 w-9 cursor-pointer rounded-full p-0.5'>
<m.div
className='bg-mono-white size-4 rounded-full'
animate={{ x: value ? 16 : 0 }}
transition={{
type: 'spring',
duration: 0.3,
bounce: 0.3,
}}
/>
</div>
);
};
Comment on lines +44 to +58
Copy link
Contributor

Choose a reason for hiding this comment

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

알림 on/off 시 색도 변경되었으면 합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

넵 추후 적용해볼게요!

30 changes: 24 additions & 6 deletions src/mock/service/user/users-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,48 @@ export const mockUserItems: User[] = [
id: 1,
email: '[email protected]',
nickName: '리오넬 메시',
profileImage: 'https://cdn.myapp.com/user/profile/123.png',
notification_enabled: '1',
profileImage:
'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
Copy link
Contributor

Choose a reason for hiding this comment

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

아.. 이거보고 이미지 unsplash 쓰면 된다는걸 알았네요
맨날 이미지 테스트한다고 config 파일에 domains에 주소 넣고 이미지 테스트 했었는데..ㅜㅜ

notification_enabled: true,
mbti: 'ISTJ',
phoneNumber: '010-1234-5678',
profileMessage: 'Zzz...',
followeesCount: 102356,
followersCount: 104,
createdAt: '2025-12-07T17:00:00+09:00',
joinedCount: 5,
createdCount: 3,
},
{
id: 2,
email: '[email protected]',
nickName: '크리스티아누 호날두',
profileImage: 'https://cdn.myapp.com/user/profile/123.png',
notification_enabled: '2',
profileImage:
'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
notification_enabled: false,
mbti: 'ENFP',
phoneNumber: '010-1234-5678',
profileMessage: '안녕하세요',
followeesCount: 7056512,
followersCount: 134,
createdAt: '2025-08-03T17:00:00+09:00',
joinedCount: 5,
createdCount: 3,
},
{
id: 3,
email: '[email protected]',
nickName: '페르난도 토레스',
profileImage: 'https://cdn.myapp.com/user/profile/123.png',
notification_enabled: '3',
profileImage:
'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
notification_enabled: true,
mbti: 'ESFJ',
phoneNumber: '010-1234-5678',
profileMessage: '반갑습니다',
followeesCount: 15,
followersCount: 12,
createdAt: '2025-11-03T17:00:00+09:00',
joinedCount: 2,
createdCount: 1,
},
];
4 changes: 4 additions & 0 deletions src/styles/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@
@utility flex-col-between {
@apply flex flex-col items-center justify-between;
}

@utility shadow-card {
box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.08);
}
Loading