Skip to content

Commit 1bc809e

Browse files
committed
✨ feat: d
1 parent 241236f commit 1bc809e

File tree

4 files changed

+73
-51
lines changed

4 files changed

+73
-51
lines changed

src/entities/story/model/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export const StoryFeedItemSchema = z.object({
1818
})
1919

2020
export const StoryFeedResponseSchema = z.object({
21+
isSuccess: z.boolean(),
2122
code: z.string(),
2223
message: z.string(),
2324
data: z.array(StoryFeedItemSchema),
24-
isSuccess: z.boolean(),
2525
})

src/features/story-viewer/model/useStoryViewer.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,82 +7,81 @@ export function useStoryViewer(
77
initialUserId: string
88
) {
99
const navigate = useNavigate()
10+
const STORY_DURATION = 5000
11+
const INTERVAL_MS = 10
1012

1113
const currentUserIndex = useMemo(() => {
12-
const index = storiesData.findIndex(
14+
return storiesData.findIndex(
1315
(u) => String(u.userId) === String(initialUserId)
1416
)
15-
return index !== -1 ? index : 0
1617
}, [storiesData, initialUserId])
1718

1819
const [currentStoryIndex, setCurrentStoryIndex] = useState(0)
1920
const [progress, setProgress] = useState(0)
2021
const [isPaused, setIsPaused] = useState(false)
22+
2123
const [prevUserId, setPrevUserId] = useState(initialUserId)
2224

2325
if (prevUserId !== initialUserId) {
2426
setPrevUserId(initialUserId)
2527
setCurrentStoryIndex(0)
2628
setProgress(0)
29+
setIsPaused(false)
2730
}
2831

2932
const currentUser = storiesData[currentUserIndex]
3033
const currentStory = currentUser?.stories?.[currentStoryIndex]
3134

3235
const handleNext = useCallback(() => {
33-
if (!currentUser || !currentUser.stories) return
34-
36+
if (!currentUser) return
3537
if (currentStoryIndex < currentUser.stories.length - 1) {
3638
setCurrentStoryIndex((prev) => prev + 1)
3739
setProgress(0)
3840
} else if (currentUserIndex < storiesData.length - 1) {
3941
const nextUser = storiesData[currentUserIndex + 1]
40-
setTimeout(() => {
41-
navigate({
42-
to: '/stories/$user_id',
43-
params: { user_id: String(nextUser.userId) },
44-
})
45-
}, 0)
42+
navigate({
43+
to: '/stories/$user_id',
44+
params: { user_id: String(nextUser.userId) },
45+
})
4646
} else {
47-
setTimeout(() => {
48-
navigate({ to: '/', search: { page: 1 } })
49-
}, 0)
47+
navigate({ to: '/', search: { page: 1 } })
5048
}
5149
}, [currentUser, currentStoryIndex, currentUserIndex, storiesData, navigate])
5250

5351
const handlePrev = useCallback(() => {
54-
if (!currentUser || !currentUser.stories) return
55-
52+
if (!currentUser) return
5653
if (currentStoryIndex > 0) {
5754
setCurrentStoryIndex((prev) => prev - 1)
5855
setProgress(0)
5956
} else if (currentUserIndex > 0) {
6057
const prevUser = storiesData[currentUserIndex - 1]
61-
setTimeout(() => {
62-
navigate({
63-
to: '/stories/$user_id',
64-
params: { user_id: String(prevUser.userId) },
65-
})
66-
}, 0)
58+
navigate({
59+
to: '/stories/$user_id',
60+
params: { user_id: String(prevUser.userId) },
61+
})
6762
}
6863
}, [currentUser, currentStoryIndex, currentUserIndex, storiesData, navigate])
6964

70-
const togglePause = useCallback(() => setIsPaused((prev) => !prev), [])
65+
const togglePause = useCallback(() => {
66+
setIsPaused((prev) => !prev)
67+
}, [])
7168

7269
useEffect(() => {
73-
if (isPaused || !currentUser || !currentUser.stories) return
70+
if (isPaused || !currentStory) return
7471

75-
const interval = setInterval(() => {
72+
const step = (INTERVAL_MS / STORY_DURATION) * 100
73+
const timer = setInterval(() => {
7674
setProgress((prev) => {
7775
if (prev >= 100) {
7876
handleNext()
7977
return 100
8078
}
81-
return prev + 1
79+
return prev + step
8280
})
83-
}, 50)
84-
return () => clearInterval(interval)
85-
}, [handleNext, isPaused, currentUser])
81+
}, INTERVAL_MS)
82+
83+
return () => clearInterval(timer)
84+
}, [isPaused, currentStoryIndex, handleNext, currentStory])
8685

8786
return {
8887
currentUser,

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import { STORY_VIEWER_UI } from './constants'
55
interface StoryDetailViewProps {
66
profileName: string
77
storyId: string
8+
progress: number
9+
currentStoryIndex: number
10+
storiesCount: number
811
}
912

1013
export function StoryDetailView({
1114
profileName,
1215
storyId,
16+
progress,
17+
currentStoryIndex,
18+
storiesCount,
1319
}: StoryDetailViewProps) {
1420
const navigate = useNavigate()
1521

@@ -18,12 +24,21 @@ export function StoryDetailView({
1824
<div className={STORY_VIEWER_UI.STYLES.VIEWER_CARD}>
1925
<div className={STORY_VIEWER_UI.STYLES.OVERLAY_TOP}>
2026
<div className={STORY_VIEWER_UI.STYLES.PROGRESS_CONTAINER}>
21-
<div className={STORY_VIEWER_UI.STYLES.PROGRESS_BAR}>
22-
<div
23-
className={STORY_VIEWER_UI.STYLES.PROGRESS_BAR_FILL}
24-
style={{ width: '30%' }}
25-
/>
26-
</div>
27+
{Array.from({ length: storiesCount }).map((_, idx) => (
28+
<div key={idx} className={STORY_VIEWER_UI.STYLES.PROGRESS_BAR}>
29+
<div
30+
className={STORY_VIEWER_UI.STYLES.PROGRESS_BAR_FILL}
31+
style={{
32+
width:
33+
idx === currentStoryIndex
34+
? `${progress}%`
35+
: idx < currentStoryIndex
36+
? '100%'
37+
: '0%',
38+
}}
39+
/>
40+
</div>
41+
))}
2742
</div>
2843

2944
<div className={STORY_VIEWER_UI.STYLES.HEADER}>

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

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react'
1+
import { useState, useEffect } from 'react'
22
import {
33
X,
44
ChevronLeft,
@@ -16,7 +16,7 @@ import { StoryOptionsModal } from './StoryOptionsModal'
1616
import ReportModal from '@/components/post/ReportModal'
1717
import AccountInfoModal from '@/components/post/AccountInfoModal'
1818
import { instance } from '@/shared/api/ky'
19-
19+
import { useCurrentUser } from '@/shared/auth/useCurrentUser'
2020
import instagramLogo from '@/assets/instagram-black-logo.png'
2121

2222
interface StoryViewerProps {
@@ -41,6 +41,8 @@ const formatRelativeTime = (createdAt: string) => {
4141
export function StoryViewer({ feed, userId }: StoryViewerProps) {
4242
const navigate = useNavigate()
4343
const queryClient = useQueryClient()
44+
const { data: me } = useCurrentUser()
45+
4446
const [imageError, setImageError] = useState(false)
4547
const [isOptionsOpen, setIsOptionsOpen] = useState(false)
4648
const [isReportOpen, setIsReportOpen] = useState(false)
@@ -59,15 +61,23 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
5961
togglePause,
6062
} = useStoryViewer(feed, userId)
6163

64+
const isMine =
65+
me?.userId !== undefined &&
66+
String(currentUser?.userId) === String(me.userId)
67+
68+
useEffect(() => {
69+
if (imageError && !isPaused) {
70+
togglePause()
71+
}
72+
}, [imageError, isPaused, togglePause])
73+
6274
if (!currentUser || !currentStory) return null
6375

6476
const isFirstStoryOfFirstUser =
6577
currentUserIndex === 0 && currentStoryIndex === 0
6678
const isLastStoryOfLastUser =
6779
currentUserIndex === feed.length - 1 &&
68-
currentStoryIndex === currentUser.stories.length - 1
69-
70-
const isMine = String(currentUser.userId) === '1'
80+
currentStoryIndex === (currentUser?.stories?.length ?? 0) - 1
7181

7282
const handleOpenOptions = (e: React.MouseEvent) => {
7383
e.stopPropagation()
@@ -77,7 +87,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
7787

7888
const handleCloseOptions = () => {
7989
setIsOptionsOpen(false)
80-
if (isPaused) togglePause()
90+
if (isPaused && !imageError) togglePause()
8191
}
8292

8393
const handleOpenReport = () => {
@@ -98,9 +108,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
98108
const handleDeleteStory = async () => {
99109
try {
100110
const response = await instance
101-
.delete(`api/v1/stories/${currentStory.id}`, {
102-
searchParams: { loggedInUser: 1 },
103-
})
111+
.delete(`api/v1/stories/${currentStory.id}`)
104112
.json<{ isSuccess: boolean; code: string; message: string }>()
105113

106114
if (response.isSuccess) {
@@ -190,9 +198,10 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
190198
<button
191199
onClick={(e) => {
192200
e.stopPropagation()
193-
togglePause()
201+
if (!imageError) togglePause()
194202
}}
195-
className="p-1 text-white transition-opacity hover:opacity-70"
203+
className="p-1 text-white transition-opacity hover:opacity-70 disabled:opacity-30"
204+
disabled={imageError}
196205
>
197206
{isPaused ? (
198207
<Play className="h-5 w-5 fill-current" />
@@ -236,7 +245,6 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
236245
referrerPolicy="no-referrer"
237246
/>
238247

239-
{/* 내 스토리일 때 좌측 하단에 조회수 표시 */}
240248
{isMine && currentStory.viewCount !== undefined && (
241249
<div className="absolute bottom-4 left-4 z-50 flex flex-col items-start gap-1">
242250
<span className="text-[13px] font-semibold text-white drop-shadow-md">
@@ -275,7 +283,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
275283
<ReportModal
276284
onClose={() => {
277285
setIsReportOpen(false)
278-
if (isPaused) togglePause()
286+
if (isPaused && !imageError) togglePause()
279287
}}
280288
onHideComment={() => {}}
281289
nickname={currentUser.nickname}
@@ -287,7 +295,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
287295
<AccountInfoModal
288296
onClose={() => {
289297
setIsAccountInfoOpen(false)
290-
if (isPaused) togglePause()
298+
if (isPaused && !imageError) togglePause()
291299
}}
292300
nickname={currentUser.nickname}
293301
profileImageUrl={currentUser.profileImageUrl}
@@ -299,7 +307,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
299307
className="fixed inset-0 z-[110] flex items-center justify-center bg-black/60 p-4"
300308
onClick={() => {
301309
setIsDeleteConfirmOpen(false)
302-
if (isPaused) togglePause()
310+
if (isPaused && !imageError) togglePause()
303311
}}
304312
>
305313
<div
@@ -323,7 +331,7 @@ export function StoryViewer({ feed, userId }: StoryViewerProps) {
323331
<button
324332
onClick={() => {
325333
setIsDeleteConfirmOpen(false)
326-
if (isPaused) togglePause()
334+
if (isPaused && !imageError) togglePause()
327335
}}
328336
className="w-full border-t border-gray-200 py-3 text-[14px] active:bg-gray-50"
329337
>

0 commit comments

Comments
 (0)