Skip to content

Commit e92b7a4

Browse files
authored
Merge pull request #169 from wafflestudio/166-bug-deploy
스토리 관리 수정
2 parents 1d2dbd5 + 235162e commit e92b7a4

File tree

9 files changed

+127
-28
lines changed

9 files changed

+127
-28
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { instance } from '@/shared/api/ky'
2-
import { StoryFeedItemSchema } from '@/entities/story/model/schema'
3-
import type { StoryFeedItem } from '@/entities/story/model/types'
2+
import { UserStoriesDataSchema } from '@/entities/story/model/schema'
3+
import type { UserStoriesData } from '@/entities/story/model/types'
44

5-
export async function getStoryDetail(userId: string): Promise<StoryFeedItem> {
5+
export async function getStoryDetail(userId: string): Promise<UserStoriesData> {
66
const response = await instance.get(`api/v1/stories/user/${userId}`)
77
const raw = (await response.json()) as { data: unknown }
8-
const parsed = StoryFeedItemSchema.parse(raw.data)
8+
const parsed = UserStoriesDataSchema.parse(raw.data)
99
return parsed
1010
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { z } from 'zod'
2+
import { instance } from '@/shared/api/ky'
3+
import { UserStoriesDataSchema } from '@/entities/story/model/schema'
4+
import type { UserStoriesData } from '@/entities/story/model/types'
5+
6+
const UserStoriesResponseSchema = z.object({
7+
isSuccess: z.boolean(),
8+
code: z.string(),
9+
message: z.string(),
10+
data: UserStoriesDataSchema,
11+
})
12+
13+
export async function getUserStories(userId: number): Promise<UserStoriesData> {
14+
const response = await instance.get(`api/v1/stories/user/${userId}`)
15+
const raw = await response.json()
16+
const parsed = UserStoriesResponseSchema.parse(raw)
17+
18+
return parsed.data
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useQuery } from '@tanstack/react-query'
2+
import { getUserStories } from '@/entities/story/api/getUserStories'
3+
4+
export function useUserStoriesQuery(userId: number) {
5+
return useQuery({
6+
queryKey: ['stories', 'user', userId],
7+
queryFn: () => getUserStories(userId),
8+
enabled: userId > 0,
9+
})
10+
}

src/entities/story/model/schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ export const StoryFeedResponseSchema = z.object({
2323
message: z.string(),
2424
data: z.array(StoryFeedItemSchema),
2525
})
26+
27+
export const UserStoriesDataSchema = z.object({
28+
hasUnseenStory: z.boolean(),
29+
stories: z.array(StorySchema),
30+
})

src/entities/story/model/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ export interface StoryResponse<T> {
2121
message: string
2222
data: T
2323
}
24+
25+
export interface UserStoriesData {
26+
hasUnseenStory: boolean
27+
stories: Story[]
28+
}

src/mocks/handlers/story.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,21 +139,14 @@ export const storyHandlers = [
139139
viewCount: s.viewCount,
140140
}))
141141

142-
const responseData = StoryFeedItemSchema.parse({
143-
userId: String(user.userId),
144-
nickname: user.nickname,
145-
profileImageUrl: user.profileImageUrl,
146-
hasUnseenStory: false,
147-
stories: userStories,
148-
})
149-
150-
const responseBody = ApiResponseSchema(StoryFeedItemSchema).parse({
151-
code: '200',
152-
message: '요청에 성공하였습니다.',
153-
data: responseData,
142+
return HttpResponse.json({
143+
code: 'COMMON200',
144+
message: '성공입니다.',
145+
data: {
146+
hasUnseenStory: Math.random() > 0.5,
147+
stories: userStories,
148+
},
154149
isSuccess: true,
155150
})
156-
157-
return HttpResponse.json(responseBody)
158151
}),
159152
]

src/routes/stories/$user_id.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { StoryViewer } from '@/features/story-viewer/ui/StoryViewer'
33
import { useStoryFeedQuery } from '@/entities/story/model/hooks/useStoryFeedQuery'
44
import { useQuery } from '@tanstack/react-query'
55
import { getStoryDetail } from '@/entities/story/api/getStoryDetail'
6+
import { useProfile } from '@/entities/user/model/hooks/useProfile'
67
import { useMemo } from 'react'
8+
import type { StoryFeedItem } from '@/entities/story/model/types'
79

810
export const Route = createFileRoute('/stories/$user_id')({
911
component: RouteComponent,
@@ -12,13 +14,25 @@ export const Route = createFileRoute('/stories/$user_id')({
1214
function RouteComponent() {
1315
const { user_id } = Route.useParams()
1416
const { data: feedData } = useStoryFeedQuery()
17+
const { data: profileData } = useProfile(Number(user_id))
1518

1619
const { data: detailData, isLoading: isDetailLoading } = useQuery({
1720
queryKey: ['stories', 'user', user_id],
1821
queryFn: () => getStoryDetail(user_id),
1922
enabled: !!user_id,
2023
})
2124

25+
const detailUser: StoryFeedItem | undefined = useMemo(() => {
26+
if (!detailData || !profileData) return undefined
27+
return {
28+
userId: String(profileData.userId),
29+
nickname: profileData.nickname,
30+
profileImageUrl: profileData.profileImageUrl,
31+
hasUnseenStory: detailData.hasUnseenStory,
32+
stories: detailData.stories,
33+
}
34+
}, [detailData, profileData])
35+
2236
const mergedFeed = useMemo(() => {
2337
if (!feedData) return []
2438

@@ -44,7 +58,7 @@ function RouteComponent() {
4458
<StoryViewer
4559
feed={mergedFeed}
4660
userId={user_id}
47-
detailUser={detailData}
61+
detailUser={detailUser}
4862
isDetailLoading={isDetailLoading}
4963
/>
5064
</div>
Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Link } from '@tanstack/react-router'
12
import { cn } from '@/shared/lib/utils'
23
import { DefaultProfileImage } from '@/shared/ui/default-profile-image'
34

@@ -6,18 +7,60 @@ import { AVATAR_SIZE_CLASSNAME } from './constants'
67
type ProfileAvatarProps = {
78
avatarUrl?: string | null
89
nickname: string
10+
hasStory?: boolean
11+
hasUnseenStory?: boolean
12+
firstStoryId?: number
913
}
1014

11-
export function ProfileAvatar({ avatarUrl, nickname }: ProfileAvatarProps) {
12-
if (avatarUrl) {
15+
export function ProfileAvatar({
16+
avatarUrl,
17+
nickname,
18+
hasStory = false,
19+
hasUnseenStory = false,
20+
firstStoryId,
21+
}: ProfileAvatarProps) {
22+
const avatarContent = avatarUrl ? (
23+
<img
24+
src={avatarUrl}
25+
alt={`${nickname} 프로필 이미지`}
26+
className={cn(AVATAR_SIZE_CLASSNAME, 'rounded-full object-cover')}
27+
/>
28+
) : (
29+
<DefaultProfileImage className={AVATAR_SIZE_CLASSNAME} />
30+
)
31+
32+
if (!hasStory) {
33+
return avatarContent
34+
}
35+
36+
const ringClassName = hasUnseenStory
37+
? 'bg-gradient-to-tr from-yellow-400 via-red-500 to-purple-600'
38+
: 'bg-gray-300'
39+
40+
const content = (
41+
<div className="relative flex cursor-pointer items-center justify-center">
42+
<div
43+
className={cn(
44+
'absolute size-[164px] rounded-full p-[3px]',
45+
ringClassName
46+
)}
47+
>
48+
<div className="size-full rounded-full bg-white" />
49+
</div>
50+
<div className="relative">{avatarContent}</div>
51+
</div>
52+
)
53+
54+
if (firstStoryId) {
1355
return (
14-
<img
15-
src={avatarUrl}
16-
alt={`${nickname} 프로필 이미지`}
17-
className={cn(AVATAR_SIZE_CLASSNAME, 'rounded-full object-cover')}
18-
/>
56+
<Link
57+
to="/stories/$profile_name/$story_id"
58+
params={{ profile_name: nickname, story_id: String(firstStoryId) }}
59+
>
60+
{content}
61+
</Link>
1962
)
2063
}
2164

22-
return <DefaultProfileImage className={AVATAR_SIZE_CLASSNAME} />
65+
return content
2366
}

src/widgets/profile-header/ui/ProfileHeader.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { FollowListModal } from '@/features/follow-user/ui/FollowListModal'
88
import { useState } from 'react'
99
import { type FollowListType } from '@/features/follow-user/ui/FollowListModal'
1010
import { Link } from '@tanstack/react-router'
11+
import { useUserStoriesQuery } from '@/entities/story/model/hooks/useUserStoriesQuery'
1112

1213
interface ProfileHeaderProps {
1314
className?: string
@@ -44,6 +45,9 @@ export function ProfileHeader({
4445
type: null,
4546
})
4647

48+
const { data: userStoriesData } = useUserStoriesQuery(userId)
49+
const hasStory = (userStoriesData?.stories.length ?? 0) > 0
50+
4751
const handleFollowListClick = () => {
4852
setModalState({
4953
open: true,
@@ -76,7 +80,13 @@ export function ProfileHeader({
7680
<section className={cn(PROFILE_CONTAINER_CLASSNAME, className)}>
7781
<div className="flex gap-8 md:gap-16">
7882
<div className="flex w-[180px] justify-center">
79-
<ProfileAvatar avatarUrl={avatarUrl} nickname={nickname} />
83+
<ProfileAvatar
84+
avatarUrl={avatarUrl}
85+
nickname={nickname}
86+
hasStory={hasStory}
87+
hasUnseenStory={userStoriesData?.hasUnseenStory}
88+
firstStoryId={userStoriesData?.stories[0]?.id}
89+
/>
8090
</div>
8191

8292
<div className="min-w-0 flex-1">

0 commit comments

Comments
 (0)