-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #153 from 80000Coding/feat/profile
feat(web): 프로필 페이지
- Loading branch information
Showing
5 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import Header from '@/components/common/Header' | ||
import FeedList from '@/components/main/FeedList' | ||
import { listMockData } from '@/components/main/list/mock/listMockData' | ||
import { profileMockData } from '@/components/profile/mock/profileMockData' | ||
import Profile from '@/components/profile/Profile' | ||
|
||
export default function ProfilePage() { | ||
const profileResult = profileMockData | ||
const feedsResult = listMockData | ||
|
||
return ( | ||
<div> | ||
<Header /> | ||
|
||
<Profile data={profileResult} /> | ||
<FeedList items={feedsResult} /> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use client' | ||
|
||
import { IconChip } from '@80000coding/ui' | ||
import classNames from 'classnames' | ||
import Link from 'next/link' | ||
|
||
import { ProfileType } from './mock/profileMockData' | ||
import useProfile from './useProfile' | ||
import useProfileRouter from './useProfileRouter' | ||
import ImageProfile from '../shared/ImageProfile' | ||
|
||
export default function Profile({ data }: { data: ProfileType }) { | ||
const { profile, isMyProfile } = useProfile(data) | ||
const { moveToParams, profileTabs } = useProfileRouter() | ||
const { displayFeedCount, displayProjectCount, organizations, profile_image_url, user_name } = profile | ||
|
||
return ( | ||
<div> | ||
<div className='flex items-center gap-x-4 p-5'> | ||
<ImageProfile src={profile_image_url} alt={user_name} className='h-[72px] w-[72px] shrink-0 rounded-full' /> | ||
<div className='w-full space-y-4'> | ||
<div className='flex h-[30px] items-center justify-between'> | ||
<span className='large-title-1'>{user_name}</span> | ||
{isMyProfile && ( | ||
<Link href='/profile/update' className='note-3 rounded-2xl border border-gray-300 bg-white px-[15px] py-2'> | ||
설정 | ||
</Link> | ||
)} | ||
</div> | ||
<div className='body-3 flex items-center gap-x-5 text-gray-600'> | ||
<span>게시글 {displayFeedCount}</span> | ||
<span>프로젝트 {displayProjectCount}</span> | ||
</div> | ||
</div> | ||
</div> | ||
<div className='scrollbar-hide mb-9 flex items-center gap-x-2 overflow-x-auto px-5'> | ||
{organizations.map(({ icon, name }) => ( | ||
<IconChip key={name} size='md' categoryIconNames={icon} /> | ||
))} | ||
</div> | ||
<div className='flex items-center justify-between px-9 pb-4'> | ||
{profileTabs.map(({ icon: Icon, label, url, selected, selectedIcon: SelectedIcon }) => ( | ||
<div key={label} onClick={() => moveToParams({ tab: url })} className='flex items-center gap-x-1'> | ||
{selected ? <SelectedIcon className='icon-sm' /> : <Icon className='icon-sm' />} | ||
<span className={classNames(selected ? 'text-gray-700' : 'text-gray-400', 'body-1')}>{label}</span> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { CategoryIconNames } from '@80000coding/ui' | ||
|
||
export type OrganiztionType = { | ||
name: string | ||
icon: CategoryIconNames | ||
} | ||
|
||
export type ProfileType = { | ||
profile_image_url: string | ||
user_id: number | ||
user_name: string | ||
feeds_count: number | ||
projects_count: number | ||
organizations: OrganiztionType[] | ||
} | ||
|
||
export const profileMockData: ProfileType = { | ||
profile_image_url: 'https://picsum.photos/200/300', | ||
user_id: 1, | ||
user_name: 'kunlee', | ||
feeds_count: 13, | ||
projects_count: 10, | ||
organizations: [ | ||
{ | ||
name: '42Seoul', | ||
icon: '42Seoul-rounded', | ||
}, | ||
{ | ||
name: 'Android', | ||
icon: 'Android-rounded', | ||
}, | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { useState } from 'react' | ||
|
||
import { ProfileType } from './mock/profileMockData' | ||
|
||
export type Refined<T> = { | ||
/** 원본데이터 */ | ||
_raw: T | ||
} & T | ||
|
||
export type RefinedProfileType = Refined<ProfileType> & { | ||
/** 게시글 수 9999 -> 9,999 */ | ||
displayFeedCount: string | ||
/** 프로젝트 수 9999 -> 9,999 */ | ||
displayProjectCount: string | ||
} | ||
|
||
function refine(item: ProfileType): RefinedProfileType { | ||
const { feeds_count, projects_count } = item | ||
|
||
return { | ||
_raw: item, | ||
...item, | ||
displayFeedCount: feeds_count.toLocaleString(), | ||
displayProjectCount: projects_count.toLocaleString(), | ||
} | ||
} | ||
|
||
export default function useProfile(data: ProfileType) { | ||
/** TODO: 로그인된 유저 아이디 context로 가져와서 대체 */ | ||
const loggedInUserId = 1 | ||
const [profile, setProfile] = useState(refine(data)) | ||
|
||
const isMyProfile = data.user_id === loggedInUserId | ||
|
||
return { | ||
profile, | ||
isMyProfile, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { | ||
StaticLikeIcon, | ||
StaticLikeWhiteIcon, | ||
StaticPostIcon, | ||
StaticPostWhiteIcon, | ||
StaticProjectIcon, | ||
StaticProjectWhiteIcon, | ||
} from '@80000coding/web-icons' | ||
import { usePathname, useRouter, useSearchParams } from 'next/navigation' | ||
import { useCallback, useMemo } from 'react' | ||
|
||
import { as } from '@/lib/utils/as' | ||
|
||
const tabs = [ | ||
{ | ||
label: '게시글', | ||
url: 'feeds', | ||
selectedIcon: StaticPostIcon, | ||
icon: StaticPostWhiteIcon, | ||
selected: false, | ||
}, | ||
{ | ||
label: '프로젝트', | ||
url: 'projects', | ||
selectedIcon: StaticProjectIcon, | ||
icon: StaticProjectWhiteIcon, | ||
selected: false, | ||
}, | ||
{ | ||
label: '좋아요', | ||
url: 'likes', | ||
selectedIcon: StaticLikeIcon, | ||
icon: StaticLikeWhiteIcon, | ||
selected: false, | ||
}, | ||
] | ||
|
||
export default function useProfileRouter() { | ||
const router = useRouter() | ||
const pathname = usePathname() | ||
const searchParams = useSearchParams() | ||
|
||
const query = as<string>(searchParams.get('tab') || '') | ||
const profileTabs = useMemo( | ||
() => | ||
tabs.map((tab, index) => | ||
(!query && index === 0) || tab.url === query | ||
? { | ||
...tab, | ||
selected: true, | ||
} | ||
: tab, | ||
), | ||
[query], | ||
) | ||
|
||
const moveToParams = useCallback( | ||
(params: { tab: string }) => { | ||
const current = new URLSearchParams(Array.from(searchParams.entries())) | ||
Object.entries(params).forEach(([key, value]) => { | ||
current.set(key, value as string) | ||
}) | ||
|
||
const newQueryString = current.toString() | ||
const hasQueryString = newQueryString.length > 0 | ||
const url = hasQueryString ? `${pathname}?${newQueryString}` : pathname | ||
|
||
router.push(url) | ||
}, | ||
[router, pathname, searchParams], | ||
) | ||
|
||
return { | ||
moveToParams, | ||
profileTabs, | ||
} | ||
} |