Skip to content

Commit

Permalink
Merge pull request #153 from 80000Coding/feat/profile
Browse files Browse the repository at this point in the history
feat(web): 프로필 페이지
  • Loading branch information
leekh716 authored Oct 3, 2023
2 parents 196d43f + bb8d077 commit 6feda5b
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
19 changes: 19 additions & 0 deletions apps/web/src/app/profile/[id]/page.tsx
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>
)
}
51 changes: 51 additions & 0 deletions apps/web/src/components/profile/Profile.tsx
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>
)
}
33 changes: 33 additions & 0 deletions apps/web/src/components/profile/mock/profileMockData.ts
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',
},
],
}
39 changes: 39 additions & 0 deletions apps/web/src/components/profile/useProfile.ts
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,
}
}
77 changes: 77 additions & 0 deletions apps/web/src/components/profile/useProfileRouter.ts
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,
}
}

0 comments on commit 6feda5b

Please sign in to comment.