Skip to content

Commit 79ee8e0

Browse files
authored
Merge pull request #145 from wafflestudio/136-bug-fix
임의 url 접근 차단 로직 작성
2 parents 19b67f2 + d62447e commit 79ee8e0

File tree

17 files changed

+253
-117
lines changed

17 files changed

+253
-117
lines changed

index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
5+
<link rel="icon" href="/favicon.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>23-5-team10-web</title>
7+
<title>1gram</title>
88
</head>
9+
910
<body>
1011
<div id="root"></div>
1112
<script type="module" src="/src/main.tsx"></script>

public/favicon.png

2.62 KB
Loading

src/components/auth/LoginCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const LoginCard = () => {
9595
localStorage.setItem('accessToken', res.data.accessToken)
9696
localStorage.setItem('refreshToken', res.data.refreshToken)
9797
await invalidateCurrentUser()
98-
navigate({ to: '/' })
98+
navigate({ to: '/', search: { page: 1 } })
9999
} else {
100100
setErrorMsg(res.message || '로그인에 실패했습니다.')
101101
}

src/components/post/PostDetail.tsx

Lines changed: 159 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export default function PostDetail() {
3939
const navigate = useNavigate()
4040

4141
const [postData, setPostData] = useState<PostData | null>(null)
42+
const [isLoading, setIsLoading] = useState(true)
43+
const [isNotFound, setIsNotFound] = useState(false)
4244
const [currentIndex, setCurrentIndex] = useState(0)
4345
const [showHeart, setShowHeart] = useState(false)
4446
const [randomRotate, setRandomRotate] = useState(0)
@@ -48,16 +50,29 @@ export default function PostDetail() {
4850

4951
useEffect(() => {
5052
const fetchPost = async () => {
53+
setIsLoading(true)
54+
setIsNotFound(false)
55+
56+
if (!/^\d+$/.test(postId)) {
57+
setIsNotFound(true)
58+
setIsLoading(false)
59+
return
60+
}
61+
5162
try {
5263
const res = await instance
5364
.get(`api/v1/posts/${postId}`)
5465
.json<{ data: PostData; isSuccess: boolean }>()
5566

5667
if (res.isSuccess) {
5768
setPostData({ ...res.data })
69+
} else {
70+
setIsNotFound(true)
5871
}
5972
} catch {
60-
console.error('Failed to fetch post')
73+
setIsNotFound(true)
74+
} finally {
75+
setIsLoading(false)
6176
}
6277
}
6378
fetchPost()
@@ -76,7 +91,7 @@ export default function PostDetail() {
7691
search: returnToSearch,
7792
})
7893
} else {
79-
navigate({ to: '/' })
94+
navigate({ to: '/', search: { page: 1 } })
8095
}
8196
}
8297

@@ -132,108 +147,154 @@ export default function PostDetail() {
132147
className="relative flex h-fit max-h-[90%] w-[95%] max-w-[1200px] overflow-hidden rounded-sm bg-white shadow-2xl"
133148
onClick={(e) => e.stopPropagation()}
134149
>
135-
<div className="relative hidden w-[60%] flex-col items-center justify-center bg-black md:flex">
136-
<div
137-
className="relative w-full overflow-hidden"
138-
style={{ aspectRatio: '1 / 1' }}
139-
>
140-
<motion.div
141-
className="flex h-full w-full"
142-
animate={{ x: `-${currentIndex * 100}%` }}
143-
transition={{ type: 'spring', stiffness: 260, damping: 26 }}
144-
onDoubleClick={handleDoubleLike}
145-
>
146-
{images.map((img) => (
147-
<div
148-
key={img.id}
149-
className="flex h-full min-w-full shrink-0 items-center justify-center"
150-
>
151-
<img
152-
src={img.url}
153-
alt=""
154-
className="h-full w-full object-cover select-none"
155-
/>
150+
{(isLoading || isNotFound) && (
151+
<>
152+
<div
153+
className="relative hidden w-[60%] items-center justify-center bg-black md:flex"
154+
style={{ aspectRatio: '1 / 1' }}
155+
/>
156+
<div className="flex w-full flex-col items-center justify-center border-l border-gray-200 bg-white md:w-[40%]">
157+
{isLoading && (
158+
<p className="text-sm text-gray-500">게시물을 불러오는 중...</p>
159+
)}
160+
{isNotFound && (
161+
<div className="flex flex-col items-center justify-center gap-4 p-8">
162+
<p className="text-lg font-semibold">
163+
게시물을 찾을 수 없습니다
164+
</p>
165+
<p className="text-center text-sm text-gray-500">
166+
삭제되었거나 잘못된 링크일 수 있습니다.
167+
</p>
168+
<button
169+
type="button"
170+
onClick={handleClose}
171+
className="mt-2 rounded-lg bg-blue-500 px-4 py-2 text-sm font-medium text-white hover:bg-blue-600"
172+
>
173+
돌아가기
174+
</button>
156175
</div>
157-
))}
158-
</motion.div>
176+
)}
177+
</div>
178+
</>
179+
)}
159180

160-
<AnimatePresence>
161-
{showHeart && (
181+
{!isLoading && !isNotFound && (
182+
<>
183+
<div className="relative hidden w-[60%] flex-col items-center justify-center bg-black md:flex">
184+
<div
185+
className="relative w-full overflow-hidden"
186+
style={{ aspectRatio: '1 / 1' }}
187+
>
162188
<motion.div
163-
key="rising-heart"
164-
initial={{ scale: 0, opacity: 1, y: 0, rotate: randomRotate }}
165-
animate={{
166-
scale: [0, 1.2, 1],
167-
y: -20,
168-
rotate: [randomRotate, randomRotate, 0],
169-
}}
170-
exit={{
171-
y: -700,
172-
transition: { duration: 0.2, ease: 'circIn' },
173-
}}
174-
transition={{
175-
duration: 0.7,
176-
times: [0, 0.57, 1],
177-
ease: 'easeInOut',
178-
}}
179-
onAnimationComplete={() => setIsAnimating(false)}
180-
className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center"
189+
className="flex h-full w-full"
190+
animate={{ x: `-${currentIndex * 100}%` }}
191+
transition={{ type: 'spring', stiffness: 260, damping: 26 }}
192+
onDoubleClick={handleDoubleLike}
181193
>
182-
<Heart
183-
className="h-32 w-32 drop-shadow-2xl"
184-
style={{ fill: 'url(#heart-gradient)', stroke: 'none' }}
185-
/>
194+
{images.map((img) => (
195+
<div
196+
key={img.id}
197+
className="flex h-full min-w-full shrink-0 items-center justify-center"
198+
>
199+
<img
200+
src={img.url}
201+
alt=""
202+
className="h-full w-full object-cover select-none"
203+
/>
204+
</div>
205+
))}
186206
</motion.div>
187-
)}
188-
</AnimatePresence>
189-
190-
{images.length > 1 && currentIndex > 0 && (
191-
<button
192-
type="button"
193-
onClick={(e) => {
194-
e.stopPropagation()
195-
moveSlide(-1)
196-
}}
197-
className="absolute top-1/2 left-4 z-30 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/80 shadow-md hover:bg-white"
198-
>
199-
<ChevronLeft className="h-5 w-5 text-black" strokeWidth={3} />
200-
</button>
201-
)}
202-
{images.length > 1 && currentIndex < images.length - 1 && (
203-
<button
204-
type="button"
205-
onClick={(e) => {
206-
e.stopPropagation()
207-
moveSlide(1)
208-
}}
209-
className="absolute top-1/2 right-4 z-30 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/80 shadow-md hover:bg-white"
210-
>
211-
<ChevronRight className="h-5 w-5 text-black" strokeWidth={3} />
212-
</button>
213-
)}
214-
215-
{images.length > 1 && (
216-
<div className="absolute bottom-4 left-1/2 z-30 flex -translate-x-1/2 gap-1.5">
217-
{images.map((_, i) => (
218-
<div
219-
key={i}
220-
className={`h-1.5 w-1.5 rounded-full transition-colors ${
221-
i === currentIndex ? 'bg-white' : 'bg-white/50'
222-
}`}
223-
/>
224-
))}
207+
208+
<AnimatePresence>
209+
{showHeart && (
210+
<motion.div
211+
key="rising-heart"
212+
initial={{
213+
scale: 0,
214+
opacity: 1,
215+
y: 0,
216+
rotate: randomRotate,
217+
}}
218+
animate={{
219+
scale: [0, 1.2, 1],
220+
y: -20,
221+
rotate: [randomRotate, randomRotate, 0],
222+
}}
223+
exit={{
224+
y: -700,
225+
transition: { duration: 0.2, ease: 'circIn' },
226+
}}
227+
transition={{
228+
duration: 0.7,
229+
times: [0, 0.57, 1],
230+
ease: 'easeInOut',
231+
}}
232+
onAnimationComplete={() => setIsAnimating(false)}
233+
className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center"
234+
>
235+
<Heart
236+
className="h-32 w-32 drop-shadow-2xl"
237+
style={{ fill: 'url(#heart-gradient)', stroke: 'none' }}
238+
/>
239+
</motion.div>
240+
)}
241+
</AnimatePresence>
242+
243+
{images.length > 1 && currentIndex > 0 && (
244+
<button
245+
type="button"
246+
onClick={(e) => {
247+
e.stopPropagation()
248+
moveSlide(-1)
249+
}}
250+
className="absolute top-1/2 left-4 z-30 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/80 shadow-md hover:bg-white"
251+
>
252+
<ChevronLeft
253+
className="h-5 w-5 text-black"
254+
strokeWidth={3}
255+
/>
256+
</button>
257+
)}
258+
{images.length > 1 && currentIndex < images.length - 1 && (
259+
<button
260+
type="button"
261+
onClick={(e) => {
262+
e.stopPropagation()
263+
moveSlide(1)
264+
}}
265+
className="absolute top-1/2 right-4 z-30 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/80 shadow-md hover:bg-white"
266+
>
267+
<ChevronRight
268+
className="h-5 w-5 text-black"
269+
strokeWidth={3}
270+
/>
271+
</button>
272+
)}
273+
274+
{images.length > 1 && (
275+
<div className="absolute bottom-4 left-1/2 z-30 flex -translate-x-1/2 gap-1.5">
276+
{images.map((_, i) => (
277+
<div
278+
key={i}
279+
className={`h-1.5 w-1.5 rounded-full transition-colors ${
280+
i === currentIndex ? 'bg-white' : 'bg-white/50'
281+
}`}
282+
/>
283+
))}
284+
</div>
285+
)}
225286
</div>
226-
)}
227-
</div>
228-
</div>
229-
230-
<div className="flex w-full flex-col border-l border-gray-200 bg-white md:w-[40%]">
231-
<PostInfoSection
232-
data={postData}
233-
ref={infoSectionRef}
234-
onDataChange={handlePostDataChange}
235-
/>
236-
</div>
287+
</div>
288+
289+
<div className="flex w-full flex-col border-l border-gray-200 bg-white md:w-[40%]">
290+
<PostInfoSection
291+
data={postData}
292+
ref={infoSectionRef}
293+
onDataChange={handlePostDataChange}
294+
/>
295+
</div>
296+
</>
297+
)}
237298
</div>
238299
</div>
239300
)

src/entities/album/ui/AlbumSummaryCard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export function AlbumSummaryCard({
3232
onClick={handleClick}
3333
>
3434
<CardHeader className="flex items-start gap-2 px-4 py-3">
35-
<CardTitle className="text-base font-semibold">{title}</CardTitle>
35+
<CardTitle className="line-clamp-1 text-base font-semibold">
36+
{title}
37+
</CardTitle>
3638
</CardHeader>
3739

3840
<CardContent className="px-0 pb-0">
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { FileQuestion } from 'lucide-react'
2+
import { Button } from '@/shared/ui/button'
3+
4+
type InvalidPageStateProps = {
5+
totalPages: number
6+
onGoToFirstPage: () => void
7+
}
8+
9+
export function InvalidPageState({
10+
totalPages,
11+
onGoToFirstPage,
12+
}: InvalidPageStateProps) {
13+
return (
14+
<div className="flex flex-col items-center justify-center py-16">
15+
<div className="flex size-20 items-center justify-center rounded-full border-2 border-black">
16+
<FileQuestion className="size-10" strokeWidth={1} />
17+
</div>
18+
<h2 className="mt-6 text-3xl font-extrabold">
19+
존재하지 않는 페이지입니다
20+
</h2>
21+
<p className="mt-3 text-center text-sm text-gray-500">
22+
요청하신 페이지를 찾을 수 없습니다.
23+
<br />
24+
전체 {totalPages}페이지 중 유효한 페이지로 이동해 주세요.
25+
</p>
26+
<Button onClick={onGoToFirstPage} className="mt-6">
27+
첫 페이지로 이동
28+
</Button>
29+
</div>
30+
)
31+
}

src/entities/user/model/hooks/useProfile.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export function useProfile(userId: number) {
55
return useQuery({
66
queryKey: ['profile', userId],
77
queryFn: () => getProfile({ userId }),
8-
enabled: userId > 0,
8+
enabled: Number.isInteger(userId) && userId > 0,
9+
retry: false,
910
})
1011
}

src/features/auth/verification/main/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function useVerification() {
4444
navigate({
4545
to: '/',
4646
replace: true,
47-
search: {},
47+
search: { page: 1 },
4848
})
4949
}
5050
} catch (err) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function useStoryViewer(
4545
}, 0)
4646
} else {
4747
setTimeout(() => {
48-
navigate({ to: '/' })
48+
navigate({ to: '/', search: { page: 1 } })
4949
}, 0)
5050
}
5151
}, [currentUser, currentStoryIndex, currentUserIndex, storiesData, navigate])

0 commit comments

Comments
 (0)