Skip to content

Commit bd4cbca

Browse files
authored
Merge pull request #161 from wafflestudio/158-bug-story
✨ feat: d
2 parents 90dc3e5 + 44b2ef9 commit bd4cbca

File tree

5 files changed

+88
-35
lines changed

5 files changed

+88
-35
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { instance } from '@/shared/api/ky'
2+
import { StoryFeedItemSchema } from '@/entities/story/model/schema'
3+
import type { StoryFeedItem } from '@/entities/story/model/types'
4+
5+
export async function getStoryDetail(userId: string): Promise<StoryFeedItem> {
6+
const response = await instance.get(`api/v1/stories/user/${userId}`)
7+
const raw = (await response.json()) as { data: unknown }
8+
const parsed = StoryFeedItemSchema.parse(raw.data)
9+
return parsed
10+
}

src/features/story-viewer/ui/StoryOptionsModal.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useCurrentUser } from '@/shared/auth/useCurrentUser'
33
interface StoryOptionsModalProps {
44
isOpen: boolean
55
onClose: () => void
6+
isMine: boolean
67
userId: string | number
78
onDelete?: () => void
89
onReport?: () => void
@@ -12,18 +13,15 @@ interface StoryOptionsModalProps {
1213
export function StoryOptionsModal({
1314
isOpen,
1415
onClose,
15-
userId,
16+
isMine,
1617
onDelete,
1718
onReport,
1819
onAccountInfo,
1920
}: StoryOptionsModalProps) {
20-
const { data: me, isLoading } = useCurrentUser()
21+
const { isLoading } = useCurrentUser()
2122

2223
if (!isOpen || isLoading) return null
2324

24-
const isMine =
25-
me?.userId !== undefined && String(me.userId) === String(userId)
26-
2725
return (
2826
<div
2927
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 p-4"

src/features/story-viewer/ui/StoryViewer.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react'
1+
import { useState, useEffect, useMemo } from 'react'
22
import {
33
X,
44
ChevronLeft,
@@ -8,7 +8,7 @@ import {
88
MoreHorizontal,
99
} from 'lucide-react'
1010
import { useNavigate, Link } from '@tanstack/react-router'
11-
import { useQueryClient } from '@tanstack/react-query'
11+
import { useQueryClient, useQuery } from '@tanstack/react-query'
1212
import type { StoryFeedItem, Story } from '@/entities/story/model/types'
1313
import { useStoryViewer } from '../model/useStoryViewer'
1414
import { STORY_VIEWER_UI } from './constants'
@@ -17,6 +17,7 @@ import ReportModal from '@/components/post/ReportModal'
1717
import AccountInfoModal from '@/components/post/AccountInfoModal'
1818
import { instance } from '@/shared/api/ky'
1919
import { useCurrentUser } from '@/shared/auth/useCurrentUser'
20+
import { getStoryDetail } from '@/entities/story/api/getStoryDetail'
2021
import instagramLogo from '@/assets/instagram-black-logo.png'
2122

2223
interface StoryViewerProps {
@@ -30,25 +31,36 @@ const formatRelativeTime = (createdAt: string) => {
3031
const diffInMinutes = Math.floor(
3132
(now.getTime() - created.getTime()) / (1000 * 60)
3233
)
33-
3434
if (diffInMinutes < 1) return '방금 전'
3535
if (diffInMinutes < 60) return `${diffInMinutes}분 전`
36-
3736
const diffInHours = Math.floor(diffInMinutes / 60)
3837
return `${diffInHours}시간 전`
3938
}
4039

4140
export function StoryViewer({ feed, userId }: StoryViewerProps) {
4241
const navigate = useNavigate()
4342
const queryClient = useQueryClient()
44-
const { data: me } = useCurrentUser()
43+
const { data: me, isLoading: isMeLoading } = useCurrentUser()
4544

4645
const [imageError, setImageError] = useState(false)
4746
const [isOptionsOpen, setIsOptionsOpen] = useState(false)
4847
const [isReportOpen, setIsReportOpen] = useState(false)
4948
const [isAccountInfoOpen, setIsAccountInfoOpen] = useState(false)
5049
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false)
5150

51+
const { data: detailData, isLoading: isDetailLoading } = useQuery({
52+
queryKey: ['stories', 'user', userId],
53+
queryFn: () => getStoryDetail(userId),
54+
enabled: !!userId,
55+
})
56+
57+
const enrichedFeed = useMemo(() => {
58+
if (!detailData) return feed
59+
return feed.map((item) =>
60+
String(item.userId) === String(userId) ? detailData : item
61+
)
62+
}, [feed, detailData, userId])
63+
5264
const {
5365
currentUser,
5466
currentStory,
@@ -59,9 +71,10 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
5971
handleNext,
6072
handlePrev,
6173
togglePause,
62-
} = useStoryViewer(feed, userId)
74+
} = useStoryViewer(enrichedFeed, userId)
6375

6476
const isMine =
77+
!isMeLoading &&
6578
me?.userId !== undefined &&
6679
currentUser?.userId !== undefined &&
6780
String(me.userId) === String(currentUser.userId)
@@ -72,7 +85,13 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
7285
}
7386
}, [imageError, isPaused, togglePause])
7487

75-
if (!currentUser || !currentStory) return null
88+
if (isDetailLoading || !currentUser || !currentStory) {
89+
return (
90+
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black">
91+
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-600 border-t-white" />
92+
</div>
93+
)
94+
}
7695

7796
const isFirstStoryOfFirstUser =
7897
currentUserIndex === 0 && currentStoryIndex === 0
@@ -111,9 +130,9 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
111130
const response = await instance
112131
.delete(`api/v1/stories/${currentStory.id}`)
113132
.json<{ isSuccess: boolean; code: string; message: string }>()
114-
115133
if (response.isSuccess) {
116134
queryClient.invalidateQueries({ queryKey: ['stories', 'feed'] })
135+
queryClient.invalidateQueries({ queryKey: ['stories', 'user', userId] })
117136
navigate({ to: '/', search: { page: 1 } })
118137
}
119138
} catch (error) {
@@ -274,6 +293,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
274293
<StoryOptionsModal
275294
isOpen={isOptionsOpen}
276295
onClose={handleCloseOptions}
296+
isMine={isMine}
277297
userId={currentUser.userId}
278298
onReport={handleOpenReport}
279299
onAccountInfo={handleOpenAccountInfo}

src/mocks/handlers/story.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,29 +116,41 @@ export const storyHandlers = [
116116
http.get('*/api/v1/stories/user/:userId', ({ params }) => {
117117
const { userId: userIdParam } = params
118118
const userId = Number(userIdParam)
119-
const userStories = stories.filter((story) => story.userId === userId)
120-
const isMyStory = userId === MOCK_USER_ID
121119

122-
const StoryItemSchema = z.object({
123-
storyId: z.number(),
124-
imageUrl: z.string(),
125-
createdAt: z.string(),
126-
viewCount: z.number().nullable(),
127-
})
120+
const user = users.find((u) => u.userId === userId)
121+
if (!user) {
122+
return HttpResponse.json(
123+
{
124+
code: '404',
125+
message: '사용자를 찾을 수 없습니다.',
126+
isSuccess: false,
127+
},
128+
{ status: 404 }
129+
)
130+
}
128131

129-
const storyItems = userStories.map((story) =>
130-
StoryItemSchema.parse({
131-
storyId: story.storyId,
132-
imageUrl: story.imageUrl,
133-
createdAt: story.createdAt,
134-
viewCount: isMyStory ? story.viewCount : null,
135-
})
136-
)
132+
const userStories = stories
133+
.filter((story) => story.userId === userId)
134+
.map((s) => ({
135+
id: s.storyId,
136+
userId: String(s.userId),
137+
imageUrl: s.imageUrl,
138+
createdAt: s.createdAt,
139+
viewCount: s.viewCount,
140+
}))
141+
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+
})
137149

138-
const responseBody = ApiResponseSchema(StoryItemSchema.array()).parse({
150+
const responseBody = ApiResponseSchema(StoryFeedItemSchema).parse({
139151
code: '200',
140152
message: '요청에 성공하였습니다.',
141-
data: storyItems,
153+
data: responseData,
142154
isSuccess: true,
143155
})
144156

src/routes/stories/$user_id.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
11
import { createFileRoute } from '@tanstack/react-router'
22
import { StoryViewer } from '@/features/story-viewer/ui/StoryViewer'
33
import { useStoryFeedQuery } from '@/entities/story/model/hooks/useStoryFeedQuery'
4+
import { useQuery } from '@tanstack/react-query'
5+
import { getStoryDetail } from '@/entities/story/api/getStoryDetail'
46

57
export const Route = createFileRoute('/stories/$user_id')({
68
component: RouteComponent,
79
})
810

911
function RouteComponent() {
1012
const { user_id } = Route.useParams()
11-
const { data: feedData, isLoading } = useStoryFeedQuery()
1213

13-
if (isLoading) {
14+
const { data: feedData, isLoading: isFeedLoading } = useStoryFeedQuery()
15+
16+
const { data: detailData, isLoading: isDetailLoading } = useQuery({
17+
queryKey: ['stories', 'user', user_id],
18+
queryFn: () => getStoryDetail(user_id),
19+
enabled: !!user_id,
20+
})
21+
22+
if (isFeedLoading || isDetailLoading) {
1423
return (
1524
<div className="flex h-screen w-full items-center justify-center bg-black">
1625
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-600 border-t-white" />
1726
</div>
1827
)
1928
}
2029

21-
if (!feedData || feedData.length === 0) {
30+
if (!feedData || !detailData) {
2231
return (
2332
<div className="flex h-screen w-full items-center justify-center bg-black text-white">
2433
<p>표시할 스토리가 없습니다.</p>
2534
</div>
2635
)
2736
}
2837

38+
const mergedFeed = feedData.map((item) =>
39+
String(item.userId) === String(user_id) ? detailData : item
40+
)
41+
2942
return (
3043
<div className="h-screen w-full bg-black">
31-
<StoryViewer feed={feedData} userId={user_id} />
44+
<StoryViewer feed={mergedFeed} userId={user_id} />
3245
</div>
3346
)
3447
}

0 commit comments

Comments
 (0)