diff --git a/src/app/routers/index.tsx b/src/app/routers/index.tsx index 467ec46..80215d9 100644 --- a/src/app/routers/index.tsx +++ b/src/app/routers/index.tsx @@ -16,7 +16,7 @@ const root: RouteObject[] = [ element: , children: [ { index: true, element: }, - { path: 'user', element: }, + { path: 'users/:userId', element: }, ], }, ]; diff --git a/src/pages/profile/ui/ProfileMainPage.tsx b/src/pages/profile/ui/ProfileMainPage.tsx index fd15128..f2d9b7d 100644 --- a/src/pages/profile/ui/ProfileMainPage.tsx +++ b/src/pages/profile/ui/ProfileMainPage.tsx @@ -1,13 +1,21 @@ -import { ProfileFeedList } from '@/widgets/profile-feed-list'; -import { ProfileHeader } from '@/widgets/profile-header'; +import { useParams } from 'react-router-dom'; + import { ProfileUser } from '@/widgets/profile-user'; +/** + * @todo userPK값 가져와서 판별하도록 구현 + * @todo ProfileFeed Api 구현 후, ProfileFeedList 컴포넌트 연결 + */ + export const ProfileMainPage = () => { + const { userId } = useParams<{ userId: string }>(); + const formattedUserId = Number(userId); + + const owner = formattedUserId === 1 ? true : false; + return (
- - - +
); }; diff --git a/src/shared/consts/types/index.ts b/src/shared/consts/types/index.ts index c3b5bb5..caeeafd 100644 --- a/src/shared/consts/types/index.ts +++ b/src/shared/consts/types/index.ts @@ -1,5 +1,5 @@ export type * from './feed'; export type { Comment } from './comment'; export type { Like } from './like'; -export type { User, RelationshipStatus } from './user'; +export type * from './user'; export type { ProfileFeed } from './profileFeed'; diff --git a/src/shared/consts/types/user.ts b/src/shared/consts/types/user.ts index 62c8962..a65277e 100644 --- a/src/shared/consts/types/user.ts +++ b/src/shared/consts/types/user.ts @@ -10,4 +10,28 @@ export interface User { followerCount: number; } +export interface FetchUser { + code: string; + data: { + user: { + id: number; + profileImage: string; + name: string; + content: string; + locked: boolean; + + feedCount: number; + followingCount: number; + followerCount: number; + }; + }; +} + export type RelationshipStatus = 'self' | 'following' | 'none' | 'pending'; + +export interface FetchRelationshipStatus { + code: string; + data: { + relationshipStatus: RelationshipStatus; + }; +} diff --git a/src/shared/react-query/consts/keys.ts b/src/shared/react-query/consts/keys.ts index 4054825..ea3fc87 100644 --- a/src/shared/react-query/consts/keys.ts +++ b/src/shared/react-query/consts/keys.ts @@ -1,3 +1,4 @@ export const QUERY_KEYS = Object.freeze({ feeds: 'feeds', + users: 'users', }); diff --git a/src/shared/ui/header/PageHeader.scss b/src/shared/ui/header/PageHeader.scss index 8213212..4481b9d 100644 --- a/src/shared/ui/header/PageHeader.scss +++ b/src/shared/ui/header/PageHeader.scss @@ -1,5 +1,5 @@ .page-header { - width: 320px; + width: 100%; height: 44px; display: flex; align-items: center; diff --git a/src/shared/ui/profile/Profile.scss b/src/shared/ui/profile/Profile.scss index 2e110ab..b49cdfc 100644 --- a/src/shared/ui/profile/Profile.scss +++ b/src/shared/ui/profile/Profile.scss @@ -5,30 +5,6 @@ border: none; background-color: white; - .profile-image { - width: 32px; - height: 32px; - - border-radius: 50%; - overflow: hidden; - } - - .no-proile-background { - position: relative; - width: 32px; - height: 32px; - border-radius: 50%; - overflow: hidden; - - background: $gray3; - - svg { - z-index: 1; - position: absolute; - bottom: 0; - } - } - .name-section { margin-left: 8px; diff --git a/src/shared/ui/profile/Profile.tsx b/src/shared/ui/profile/Profile.tsx index f97ff5e..57264a3 100644 --- a/src/shared/ui/profile/Profile.tsx +++ b/src/shared/ui/profile/Profile.tsx @@ -1,34 +1,32 @@ import { Link } from 'react-router-dom'; -import { Icon } from '..'; +import ProfileImage from './ProfileImage'; import './Profile.scss'; interface ProfileProps { profileImage: string; name: string; content: string; + userId: number; + isLink: boolean; } -/** - * @todo 임시로 구현된 Link, 추후 적절한 user로 이동하도록 수정 - */ - -export const Profile = ({ profileImage, name, content }: ProfileProps) => { +export const Profile = ({ + profileImage, + name, + content, + userId, + isLink, +}: ProfileProps) => { return (
- - {profileImage ? ( - {`${name} - ) : ( -
- -
- )} - + {isLink ? ( + + + + ) : ( + + )}
{name}

{content}

diff --git a/src/shared/ui/profile/ProfileImage.scss b/src/shared/ui/profile/ProfileImage.scss new file mode 100644 index 0000000..9439685 --- /dev/null +++ b/src/shared/ui/profile/ProfileImage.scss @@ -0,0 +1,23 @@ +.profile-image { + width: 32px; + height: 32px; + + border-radius: 50%; + overflow: hidden; +} + +.no-proile-background { + position: relative; + width: 32px; + height: 32px; + border-radius: 50%; + overflow: hidden; + + background: $gray3; + + svg { + z-index: 1; + position: absolute; + bottom: 0; + } +} diff --git a/src/shared/ui/profile/ProfileImage.tsx b/src/shared/ui/profile/ProfileImage.tsx new file mode 100644 index 0000000..828155a --- /dev/null +++ b/src/shared/ui/profile/ProfileImage.tsx @@ -0,0 +1,22 @@ +import { Icon } from '..'; +import './ProfileImage.scss'; + +interface ProfileImageProps { + profileImage: string; + name: string; +} + +const ProfileImage = ({ profileImage, name }: ProfileImageProps) => { + return profileImage ? ( + {`${name} + ) : ( +
+ +
+ ); +}; +export default ProfileImage; diff --git a/src/widgets/feed-main-list/ui/Feed.tsx b/src/widgets/feed-main-list/ui/Feed.tsx index 43e6eea..8c82b80 100644 --- a/src/widgets/feed-main-list/ui/Feed.tsx +++ b/src/widgets/feed-main-list/ui/Feed.tsx @@ -8,7 +8,10 @@ import { FeedKebabButton } from '@/widgets/feed-kebab'; import './Feed.scss'; -export const Feed: React.FC<{ feed: FeedProps }> = ({ feed }) => { +export const Feed: React.FC<{ isLink: boolean; feed: FeedProps }> = ({ + feed, + isLink, +}) => { const { id, user, @@ -29,6 +32,8 @@ export const Feed: React.FC<{ feed: FeedProps }> = ({ feed }) => { profileImage={user.profileImage} name={user.name} content={calculateElapsedTime(updatedAt)} + userId={user.id} + isLink={isLink} /> diff --git a/src/widgets/feed-main-list/ui/FeedMainList.tsx b/src/widgets/feed-main-list/ui/FeedMainList.tsx index f716db9..08e88f7 100644 --- a/src/widgets/feed-main-list/ui/FeedMainList.tsx +++ b/src/widgets/feed-main-list/ui/FeedMainList.tsx @@ -39,7 +39,7 @@ export const FeedMainList = () => { return hiddenType ? ( ) : ( - + ); }); })} diff --git a/src/widgets/profile-feed-list/ui/ProfileFeedList.tsx b/src/widgets/profile-feed-list/ui/ProfileFeedList.tsx index eed3991..8ae1fe7 100644 --- a/src/widgets/profile-feed-list/ui/ProfileFeedList.tsx +++ b/src/widgets/profile-feed-list/ui/ProfileFeedList.tsx @@ -60,7 +60,7 @@ export const ProfileFeedList = () => {

내 게시글

{dummyFeeds.map((dummyFeed) => (
- +
))} diff --git a/src/widgets/profile-user/api/index.ts b/src/widgets/profile-user/api/index.ts new file mode 100644 index 0000000..d184899 --- /dev/null +++ b/src/widgets/profile-user/api/index.ts @@ -0,0 +1 @@ +export { useGetUser } from './useGetUser'; diff --git a/src/widgets/profile-user/api/useGetUser.ts b/src/widgets/profile-user/api/useGetUser.ts new file mode 100644 index 0000000..825e1ef --- /dev/null +++ b/src/widgets/profile-user/api/useGetUser.ts @@ -0,0 +1,29 @@ +import { useQuery } from '@tanstack/react-query'; + +import { axiosInstance } from '@/shared/axios'; +import { FetchUser } from '@/shared/consts'; +import { QUERY_KEYS } from '@/shared/react-query'; + +async function fetchUser(userId: number): Promise { + const { data } = await axiosInstance.get(`/users/${userId}`); + return data; +} + +export const useGetUser = (userId: number) => { + const { + data, + isLoading, + isError, + refetch: refetchUser, + } = useQuery({ + queryKey: [QUERY_KEYS.users, userId], + queryFn: () => fetchUser(userId), + }); + + return { + data, + isLoading, + isError, + refetchUser, + }; +}; diff --git a/src/widgets/profile-user/assets/no-profile-icon.svg b/src/widgets/profile-user/assets/no-profile-icon.svg new file mode 100644 index 0000000..89594f9 --- /dev/null +++ b/src/widgets/profile-user/assets/no-profile-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/widgets/profile-user/assets/profile-change-icon.svg b/src/widgets/profile-user/assets/profile-change-icon.svg new file mode 100644 index 0000000..906d0ba --- /dev/null +++ b/src/widgets/profile-user/assets/profile-change-icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/widgets/profile-user/ui/ProfileUser.scss b/src/widgets/profile-user/ui/ProfileUser.scss index 8509d0e..5a711ac 100644 --- a/src/widgets/profile-user/ui/ProfileUser.scss +++ b/src/widgets/profile-user/ui/ProfileUser.scss @@ -6,41 +6,6 @@ align-items: center; box-shadow: 0px 20px 34.5px 0px #dddddd40; - .profile-image-box { - position: relative; - - .profile-image { - width: 81px; - height: 81px; - border-radius: 50%; - overflow: hidden; - padding-bottom: 5px; - } - - .no-proile-background { - position: relative; - width: 81px; - height: 81px; - border-radius: 50%; - overflow: hidden; - background: $gray3; - - svg { - z-index: 1; - position: absolute; - bottom: 0; - } - } - - .profile-change-btn { - position: absolute; - bottom: 4px; - right: -8px; - width: 24px; - height: 24px; - } - } - .profile-top-container { height: 148px; display: flex; @@ -52,13 +17,21 @@ text-align: center; } - .user-follow-btn { + .nickname-change-btn { width: 67px; height: 26px; border-radius: 5px; color: white; background-color: $mint3; } + + .user-follow-btn { + width: 46px; + height: 26px; + border-radius: 5px; + color: white; + background-color: $mint3; + } } .profile-count-container { diff --git a/src/widgets/profile-user/ui/ProfileUser.tsx b/src/widgets/profile-user/ui/ProfileUser.tsx index 565cc00..6835105 100644 --- a/src/widgets/profile-user/ui/ProfileUser.tsx +++ b/src/widgets/profile-user/ui/ProfileUser.tsx @@ -1,40 +1,55 @@ -import { Icon } from '@/shared/ui'; +import { NetworkError, PageHeader } from '@/shared/ui'; + +import { useGetUser } from '../api'; -import './ProfileUser.scss'; import { ProfileCount } from './ProfileCount'; +import './ProfileUser.scss'; +import { ProfileUserImage } from './ProfileUserImage'; + +interface ProfileUserProps { + userId: number; + isOwner: boolean; +} -export const ProfileUser = () => { - const profileImage = 'https://avatars.githubusercontent.com/u/101088491?v=4'; +export const ProfileUser = ({ userId, isOwner }: ProfileUserProps) => { + const { data, isLoading, isError, refetchUser } = useGetUser(userId); + + if (isLoading) { + return
스켈레톤 들어갈곳
; + } + + if (isError || !data) { + return ; + } + + const { profileImage, name, feedCount, followerCount, followingCount } = + data.data.user; return ( -
-
-
- {profileImage ? ( - {`붕어빵 + <> + +
+
+ +

{name}

+ {isOwner ? ( + ) : ( -
- -
+ )} - -
-

붕어빵

- -
-
- -
- -
- +
+
+ +
+ +
+ +
- + ); }; diff --git a/src/widgets/profile-user/ui/ProfileUserImage.scss b/src/widgets/profile-user/ui/ProfileUserImage.scss new file mode 100644 index 0000000..e86dc87 --- /dev/null +++ b/src/widgets/profile-user/ui/ProfileUserImage.scss @@ -0,0 +1,35 @@ +.profile-image-box { + position: relative; + + .profile-image { + width: 81px; + height: 81px; + border-radius: 50%; + overflow: hidden; + padding-bottom: 5px; + } + + .no-proile-background { + position: relative; + width: 81px; + height: 81px; + border-radius: 50%; + overflow: hidden; + background: $gray3; + + svg { + z-index: 1; + position: absolute; + bottom: 0; + } + } + + .profile-change-btn { + z-index: 10; + position: absolute; + top: 47px; + left: 62px; + width: 24px; + height: 24px; + } +} diff --git a/src/widgets/profile-user/ui/ProfileUserImage.tsx b/src/widgets/profile-user/ui/ProfileUserImage.tsx new file mode 100644 index 0000000..37cc630 --- /dev/null +++ b/src/widgets/profile-user/ui/ProfileUserImage.tsx @@ -0,0 +1,36 @@ +import NoProfileIcon from '../assets/no-profile-icon.svg?react'; +import ProfileChangeIcon from '../assets/profile-change-icon.svg?react'; +import './ProfileUserImage.scss'; + +interface ProfileUserImageProps { + profileImage: string; + name: string; + isOwner: boolean; +} + +export const ProfileUserImage = ({ + profileImage, + name, + isOwner, +}: ProfileUserImageProps) => { + return ( +
+ {profileImage ? ( + {`${name} + ) : ( +
+ +
+ )} + {isOwner && ( + + )} +
+ ); +};