Skip to content

Commit 3151ac5

Browse files
kimnamheeeec0912jyclaude
authored
배포 (#126)
* ✨ feat(msw): add follow handlers * 💎 style(sidebar): add padding * ✨ feat(components): add follow list modal component needs to be fixed based on real api * ✨ feat(components): add mock data / match data type * 💎 style(search): adjust search drawer style * ✨ feat: ... (설정) 화면 모달 구현 * ✨ feat(search): apply debounce to input value * ✨ feat: msw 구현 (comment) * 🧹 chore: remove duplicate key * ✨ feat: 댓글 ui * ✨ feat: 댓글 설정창(...) ui 구현 * ✨ feat: ... 모달 로직 수정 * ✨ feat: 글 본문 및 프로필 ui 수정 (여백 정리) * ✨ feat(msw): warn unhandled requests for debugging * ✨ feat(gitignore): add env * ✨ feat(providers): add query client provider * ✨ feat(api): add test (health check) handler * ✨ feat(route): add test route for api connection test * ✨ feat: 액션 바 (상호작용) ui 구현 * ✨ feat: env 삭제 * 💎 style(sidebar): update layout stability * ✨ feat(sidebar): updage search drawer toggle logic * ✨ feat(sidebar): remain page width when opening search drawer * ✨ feat(components): add pagination * ✨ feat(components): add shared components lazyimage / card * ✨ feat(msw): add post mock data * ✨ feat(api): add feed schema / types * ✨ feat(api): add handler / api function * ✨ feat(home): add feed ui * ✨ feat(actions): add env variable on cd workflow * 💎 style(sidebar): collapse sidebar on search drawer toggle * 🐛 fix(route): update route from profile_id to post_id * ✨ feat(feed): link to post detail on click * ✨ feat(components): add dropdown * ✨ feat(msw): add album handler * ✨ feat(api): add album api call functions * ✨ feat(api): add album api call function * 🐛 fix(msw): adjust handler order * 💎 style(dropdown): remove circle icon * ✨ feat(album): add album dropdown * 🧪 test(album): add album dropdown tests * 🐛 fix(actions): apply secrets * 🧹 chore: update test:ci command option * ✨ feat(msw): add bookmarks handler * ✨ feat(bookmarks): add bookmarked posts ui * ✨ feat(postdetail): return to previous page when closing page * ✨ feat(postdetail): validate search params with zod * ✨ feat: 하단 푸터 - 위치 화면 구현 * ✨ feat: 하단 푸터 수정 (Instagram Lite 추가) * ✨ feat: 푸터 완성 * ✨ feat: 푸터 ui, 기능, 경로 설정 완성 * ✨ feat: 로그인 msw 설정 * ✨ feat: 로그인 검사 로직 추가 및 카카오톡 로그인 ui 구현 * ✨ feat: 파일 구조 변경 실제 구현과 유사하게 경로 변경 & 유지보수 및 관리 용이 목적 * ✨ feat: 푸터 완성 * ✨ feat: 비밀번호 찾기 창 ui 수정 * 💎 style(createmodal): fix modal ratio * ✨ feat(sidebar): 만들기 버튼을 게시글/스토리 업로드로 분리 - 기존 '만들기' 버튼을 '게시글 업로드'와 '스토리 업로드'로 분리 - 아이콘 구분: SquarePlus(게시글), CirclePlus(스토리) - 모바일 하단바 배경색 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ✨ feat(create-story): 스토리 업로드 기능 구현 - 사진 1장만 업로드 가능 (초과 시 toast 알림) - 9:16 비율 미리보기, 검은 배경으로 빈 공간 채움 - 본문/앨범 단계 없이 바로 공유하기 - 이미지 업로드 후 헤더 타이틀 숨김 처리 - 이탈 시 확인 다이얼로그 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * 💎 style(ui): 모달 외곽선 제거 및 안내 문구 word-break 추가 - DialogContent 기본 스타일에서 border 제거 - 이미지 업로드 안내 문구에 break-keep 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * 💎 style(story): 레이아웃 수정 - xs 너비에서 미리보기, dropzone 영역의 높이가 지나치게 높아지는 문제 해결 * 🧹 chore(constants): add constant for api errors * ✨ feat(msw): add handlers for image upload * ✨ feat(msw): add handlers for stories * ♻️ refactor(component): change component name * ✨ feat(msw): add search handler * ✨ feat(explore): separate page component * ♻️ refactor: update variable names * 🧹 chore(msw): add mock data * ✨ feat(explore): add ui / api handling * ✨ feat(profile): add albums tab * ✨ feat(profile): add albums summary ui * 💎 style(album): update badge background color * ✨ feat(msw): add relations to msw mock data * ✨ feat(msw): add like related handlers * ✨ feat(msw): add bookmark toggle handler * ✨ feat: 비밀번호 찾기 기능 구현 * ✨ feat(like/bookmark): add like, bookmark toggle functionality * ✨ feat: footer 파일 위치 변경 * ✨ feat(home): add footer * ✨ feat(album): integrate get album detail api * ✨ feat: 컨플릭트 수정 * 🐛 fix(route): fix routeTree syntax error * ✨ feat: 회원가입 ui 구현 * ✨ feat: 회원가입 아이디 중복 체크 로직 구현 * ✨ feat: 회원가입 컴포넌트 분리 및 아이디 랜덤 생성 로직 수정 * ✨ feat: reset.tsx 구조 분리 * ✨ feat(msw): add post create handler * ✨ feat(create-post): add functionality and ui for creating post * ✨ feat(create-post): show close button on post create finish * ✨ feat(utils): add crop image util function * ✨ feat(create-story): integrate upload story api / image crop logic * 🐛 fix(test): add react query provider to tests * ✨ feat: 회원가입 정보 입력 화면 구현 * ✨ feat(stories): implement story feed api / integrate with component * 🐛 fix(route): update route tree file * 💎 style: layout stability * ✨ feat: 회원가입 구현 아직 토큰 관리 미흡, 이메일 전송 로직 미구현 * ✨ feat: 회원가입 라우트 간 보안 강화 로직 추가 * ✨ feat: 회원가입 푸터 라우트 연결 * ✨ feat: 푸터 버그 수정 * ✨ feat: 로그인 버그 수정 * ✨ feat: 로그인 버그 수정 * ✨ feat(msw): add search, recent search api handler * ✨ feat(search): add api function for search * ✨ feat(api): add followed / name to search api * ✨ feat(recent): add recent search keyword management * 🧹 chore: remove unused files * ✨ feat: 댓글 api 연결 * 🐛 fix(entity): update schema make name, profileImageUrl nullable * ✨ feat(ui): add fallback for null profileImages * ✨ feat: 댓글 추가 및 좋아요 api 연결 * ✨ feat(follow): integrate follow/unfollow apis * ✨ feat(user): add GET profile api function * ✨ feat(search): add staletime for cache control * 🐛 fix(entity): update schema make bio, name, profileImageUrl nullable * ✨ feat(routes): update profile routes * 🐛 fix(msw): fix field name success -> isSuccess * ♻️ refactor(schema): rename success to isSuccess in API response schemas Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ♻️ refactor(api): update isSuccess field references in API functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ♻️ refactor(auth): update isSuccess field references in auth components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ✨ feat(user): add fallback UI for null profileImageUrl Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ♻️ refactor(search): update search UI components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ✨ feat(profile): update profile page and navigation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ✨ feat: 로그인 토큰 구현 * 🐛 fix(msw): fix handler order * 🐛 fix(test): update api field name for test * ✨ feat: 댓글 api 연결 수정 및 로그인 토큰 작업, 회원가입 시 자동 로그인 구현 * ✨ feat: 로그인 버그 수정 * ✨ feat: 게시글 날짜 표현 방식 수정 * ✨ feat: 댓글창 ui 수정 * ✨ feat: 비행기 ui 삭제 * ✨ feat(]): 댓글 삭제 api * ✨ feat: 댓글 신고 기능 구현 * ✨ feat: 댓글 신고 기능 1차 구현 * ✨ feat: 댓글 아이콘 클릭 기능 구현 * ✨ feat: 댓글 수정 구현 * ✨ feat(]): 대댓글 로직 삭제 * ✨ feat: 버그 수정 * ✨ feat(shared): add query client * ✨ feat(entities): add getCurrentUser api and schema * ✨ feat(auth): integrate useCurrentUser and session state * ✨ feat(auth): add refresh flow in ky * ✨ feat(auth): integrate useAuth, AuthProvider to current user * 🧹 chore(app): use shared queryClient * ♻️ refactor(components): use useCurrentUser in auth/post * ✨ feat(msw): add /users/me handler * 🐛 fix(auth): remove cookie from request and fix retry logic * 🐛 fix: fix path * 🐛 fix(fix api field): followed -> isFollowed * ✨ feat: 좋아요, 북마크 api 연결 * ✨ feat: 좋아요 바 애니메이션 수정 * ✨ feat: 게시글 컴포넌트 ui, 기능, api 연결 완료 * ✨ feat: 좋아요, 북마크, 댓글 관련 api 연결 및 기능 구현 * ✨ feat: 말풍선 아이콘 좌우반전 * ✨ feat: ... 모달 분기 구현 * ✨ feat: 내 계정 정보 모달 추가 * 🐛 fix(album): add logedInUser query parameter / update api endpoint * 🐛 fix(feed): update field name liked -> isLiked / bookmarked -> isBookmarked * 🐛 fix(post): update field name liked -> isLiked / bookmarked -> isBookmarked * 🐛 fix(create-post): fix upload strategies * 🐛 fix(story): fix story upload api content-type * 🐛 fix(follow): add loggedInUser query parameter * 🐛 fix(user): update field name me -> isMe * 🐛 fix(post / user): update field name * 🐛 fix(auth): add refresh token * ✨ feat(posts): add profile posts api * 🐛 fix(post): constrain mime types * ✨ feat: 수정 * ✨ feat: 게시글 삭제 기능 구현 * ✨ feat: 최종 수정 * 🐛 fix(msw): update api endpoint * ✨ feat(auth): improve user session handling * ✨ feat: post 수정 기능 구현 * ✨ feat(follow): update follower list handling * ✨ feat(profile): add empty states * ✨ feat(navigation): add profile api to sidebar * ✨ feat(create-post): add modal store * ✨ feat(follow): add remove follower * ✨ feat(profile): mock apis for user posts / albums --------- Co-authored-by: c0912jy <c0912jy@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 33df370 commit 3151ac5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1215
-179
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { useCallback, useState, useMemo } from 'react'
2+
import { Dialog, DialogContent } from '@/shared/ui/dialog'
3+
import { CheckCircle2, Loader2 } from 'lucide-react'
4+
import { toast } from 'sonner'
5+
import { instance } from '@/shared/api/ky'
6+
import { useQueryClient } from '@tanstack/react-query'
7+
8+
import { CreateModalHeader } from '@/features/create-post/ui/CreateModalHeader'
9+
import { PreviewPane } from '@/features/create-post/ui/PreviewPane'
10+
import { PostDetailsPane } from '@/features/create-post/ui/PostDetailsPane'
11+
import { useImageCarousel } from '@/features/create-post/model/hooks/useImageCarousel'
12+
import { useUpdatePostMutation } from '@/entities/post/model/hooks/useUpdatePostMutation'
13+
import type { PostData } from './PostDetail'
14+
15+
type EditPostModalProps = {
16+
open: boolean
17+
onOpenChange: (open: boolean) => void
18+
initialData: PostData
19+
onSuccess?: (updatedData: PostData) => void
20+
}
21+
22+
export function EditPostModal({
23+
open,
24+
onOpenChange,
25+
initialData,
26+
onSuccess,
27+
}: EditPostModalProps) {
28+
const [caption, setCaption] = useState(initialData.content)
29+
const [selectedAlbumId, setSelectedAlbumId] = useState<number>(
30+
initialData.albumId ?? -1
31+
)
32+
const [isSyncing, setIsSyncing] = useState(false)
33+
const [isSuccessState, setIsSuccessState] = useState(false)
34+
35+
const queryClient = useQueryClient()
36+
const updatePost = useUpdatePostMutation(initialData.id)
37+
const carousel = useImageCarousel(initialData.images.length)
38+
39+
const isDirty = useMemo(() => {
40+
return (
41+
caption !== initialData.content ||
42+
selectedAlbumId !== (initialData.albumId ?? -1)
43+
)
44+
}, [caption, selectedAlbumId, initialData])
45+
46+
const handleUpdate = useCallback(async () => {
47+
if (!isDirty) return
48+
setIsSyncing(true)
49+
50+
try {
51+
let updatedData: PostData = { ...initialData }
52+
53+
if (caption !== initialData.content) {
54+
const result = await updatePost.mutateAsync({
55+
content: caption,
56+
albumId: initialData.albumId ?? null,
57+
imageUrls: initialData.images.map((img) => img.url),
58+
})
59+
updatedData = { ...updatedData, content: result.content }
60+
}
61+
62+
if (selectedAlbumId !== (initialData.albumId ?? -1)) {
63+
if (selectedAlbumId === -1) {
64+
await instance.delete(
65+
`api/v1/albums/${initialData.albumId}/posts/${initialData.id}`
66+
)
67+
updatedData = { ...updatedData, albumId: null as unknown as number }
68+
} else {
69+
await instance.post(
70+
`api/v1/albums/${selectedAlbumId}/posts/${initialData.id}`
71+
)
72+
updatedData = { ...updatedData, albumId: selectedAlbumId }
73+
}
74+
}
75+
76+
setIsSuccessState(true)
77+
78+
setTimeout(() => {
79+
queryClient.invalidateQueries({ queryKey: ['posts'] })
80+
queryClient.invalidateQueries({ queryKey: ['albums'] })
81+
82+
onSuccess?.(updatedData)
83+
onOpenChange(false)
84+
}, 500)
85+
} catch {
86+
toast.error('정보 수정에 실패했습니다.')
87+
} finally {
88+
setIsSyncing(false)
89+
}
90+
}, [
91+
caption,
92+
selectedAlbumId,
93+
isDirty,
94+
updatePost,
95+
onOpenChange,
96+
initialData,
97+
onSuccess,
98+
queryClient,
99+
])
100+
101+
const activePreviewUrl = initialData.images[carousel.activeIndex]?.url
102+
103+
return (
104+
<Dialog open={open} onOpenChange={onOpenChange}>
105+
<DialogContent
106+
showCloseButton={false}
107+
overlayClassName="z-[110]"
108+
className="z-[120] flex flex-col gap-0 overflow-hidden rounded-4xl bg-white p-0 sm:w-[calc(80vh-51px+340px)] sm:max-w-[849px]"
109+
>
110+
<CreateModalHeader
111+
isUploaded={true}
112+
step="details"
113+
title="정보 수정"
114+
onBack={() => onOpenChange(false)}
115+
onShare={handleUpdate}
116+
isShareDisabled={!isDirty || isSyncing}
117+
isSharing={isSyncing}
118+
isShareSuccess={isSuccessState}
119+
/>
120+
<div className="h-px w-full bg-zinc-200" />
121+
122+
{isSyncing ? (
123+
<div className="flex aspect-square w-full items-center justify-center sm:h-[calc(80vh-51px)] sm:max-h-[509px]">
124+
<Loader2 className="size-16 animate-spin text-zinc-400" />
125+
</div>
126+
) : isSuccessState ? (
127+
<div className="flex aspect-square w-full flex-col items-center justify-center gap-4 sm:h-[calc(80vh-51px)] sm:max-h-[509px]">
128+
<CheckCircle2 className="size-24 text-green-500" />
129+
<p className="text-lg font-medium text-zinc-700">
130+
정보가 수정되었습니다.
131+
</p>
132+
</div>
133+
) : (
134+
<div className="flex min-h-0 flex-1 flex-col sm:flex-row">
135+
<div className="flex aspect-square w-full sm:h-[calc(80vh-51px)] sm:max-h-[509px] sm:w-[calc(80vh-51px)] sm:max-w-[509px] sm:flex-none">
136+
<PreviewPane
137+
activePreviewUrl={activePreviewUrl}
138+
filesCount={initialData.images.length}
139+
activeIndex={carousel.activeIndex}
140+
canGoPrev={carousel.canGoPrev}
141+
canGoNext={carousel.canGoNext}
142+
dots={carousel.dots}
143+
goPrev={carousel.goPrev}
144+
goNext={carousel.goNext}
145+
/>
146+
</div>
147+
148+
<div className="min-h-0 flex-1 sm:w-[340px] sm:shrink-0">
149+
<PostDetailsPane
150+
profileName={initialData.nickname}
151+
profileImageUrl={initialData.profileImageUrl}
152+
caption={caption}
153+
onCaptionChange={setCaption}
154+
selectedAlbumId={selectedAlbumId}
155+
onAlbumSelect={setSelectedAlbumId}
156+
/>
157+
</div>
158+
</div>
159+
)}
160+
</DialogContent>
161+
</Dialog>
162+
)
163+
}

src/components/post/PostDetail.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default function PostDetail() {
5454
.json<{ data: PostData; isSuccess: boolean }>()
5555

5656
if (res.isSuccess) {
57-
setPostData(res.data)
57+
setPostData({ ...res.data })
5858
}
5959
} catch {
6060
console.error('Failed to fetch post')
@@ -105,7 +105,7 @@ export default function PostDetail() {
105105
}
106106

107107
const handlePostDataChange = (newData: PostData) => {
108-
setPostData(newData)
108+
setPostData({ ...newData })
109109
}
110110

111111
return (

src/components/post/PostInfoSection.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ const PostInfoSection = forwardRef<PostInfoSectionRef, PostInfoSectionProps>(
5656
const commentInputRef = useRef<HTMLInputElement>(null)
5757

5858
useEffect(() => {
59-
setPostData(initialData)
59+
if (initialData) {
60+
setPostData({ ...initialData })
61+
}
6062
}, [initialData])
6163

6264
useImperativeHandle(ref, () => ({
@@ -294,6 +296,12 @@ const PostInfoSection = forwardRef<PostInfoSectionRef, PostInfoSectionProps>(
294296
}, 0)
295297
}
296298

299+
const handleUpdateSuccess = (updatedData: PostData) => {
300+
const clonedData = { ...updatedData }
301+
setPostData(clonedData)
302+
onDataChange?.(clonedData)
303+
}
304+
297305
const formattedFullDate = postData?.createdAt
298306
? new Date(postData.createdAt).toLocaleDateString('ko-KR', {
299307
year: 'numeric',
@@ -302,6 +310,12 @@ const PostInfoSection = forwardRef<PostInfoSectionRef, PostInfoSectionProps>(
302310
})
303311
: ''
304312

313+
const isEdited =
314+
postData?.updatedAt &&
315+
postData?.createdAt &&
316+
new Date(postData.updatedAt).getTime() >
317+
new Date(postData.createdAt).getTime() + 1000
318+
305319
return (
306320
<div className="flex h-full flex-col bg-white">
307321
<div className="flex shrink-0 items-center justify-between border-b border-gray-200 px-4 py-3">
@@ -368,7 +382,7 @@ const PostInfoSection = forwardRef<PostInfoSectionRef, PostInfoSectionProps>(
368382
<span className="whitespace-pre-wrap">{postData?.content}</span>
369383
<div className="mt-2 text-xs font-normal text-gray-500">
370384
{postData?.createdAt
371-
? formatRelativeTime(postData.createdAt)
385+
? `${formatRelativeTime(postData.createdAt)}${isEdited ? ' • 수정됨' : ''}`
372386
: ''}
373387
</div>
374388
</div>
@@ -411,6 +425,8 @@ const PostInfoSection = forwardRef<PostInfoSectionRef, PostInfoSectionProps>(
411425
nickname={postData?.nickname || ''}
412426
authorId={postData?.userId || 0}
413427
profileImageUrl={postData?.profileImageUrl || null}
428+
initialPostData={postData ?? undefined}
429+
onUpdateSuccess={handleUpdateSuccess}
414430
/>
415431
)}
416432
</div>

src/components/post/PostMenuModal.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@ import { useNavigate } from '@tanstack/react-router'
33
import ReportModal from './ReportModal'
44
import AccountInfoModal from './AccountInfoModal'
55
import EmbedModal from './EmbedModal'
6+
import { EditPostModal } from './EditPostModal'
67
import { useCurrentUserId } from '@/shared/auth/useCurrentUser'
78
import { useFollowing } from '@/features/follow-user/model/useFollowing'
89
import { useToggleFollow } from '@/features/follow-user/model/useToggleFollow'
910
import { instance } from '@/shared/api/ky'
11+
import type { PostData } from './PostDetail'
1012

1113
interface PostMenuModalProps {
1214
onClose: () => void
1315
nickname: string
1416
authorId: number
1517
postId: number
1618
profileImageUrl: string | null
19+
initialPostData?: PostData
20+
onUpdateSuccess?: (updatedData: PostData) => void
1721
}
1822

1923
export default function PostMenuModal({
@@ -22,9 +26,11 @@ export default function PostMenuModal({
2226
authorId,
2327
postId,
2428
profileImageUrl,
29+
initialPostData,
30+
onUpdateSuccess,
2531
}: PostMenuModalProps) {
2632
const [activeModal, setActiveModal] = useState<
27-
'menu' | 'report' | 'account' | 'embed' | 'delete_confirm'
33+
'menu' | 'report' | 'account' | 'embed' | 'delete_confirm' | 'edit'
2834
>('menu')
2935
const [showToast, setShowToast] = useState(false)
3036
const navigate = useNavigate()
@@ -68,14 +74,24 @@ export default function PostMenuModal({
6874

6975
if (response.isSuccess) {
7076
onClose()
71-
// 삭제 후 내 프로필 페이지나 홈으로 이동
7277
navigate({ to: `/${authorId}` })
7378
}
7479
} catch (err) {
7580
console.error('게시글 삭제 실패:', err)
7681
}
7782
}
7883

84+
const handleEditSuccess = (updatedData: PostData) => {
85+
if (onUpdateSuccess && initialPostData) {
86+
const finalData = {
87+
...initialPostData,
88+
...updatedData,
89+
}
90+
onUpdateSuccess(finalData)
91+
}
92+
onClose()
93+
}
94+
7995
if (activeModal === 'report') {
8096
return (
8197
<ReportModal
@@ -101,6 +117,17 @@ export default function PostMenuModal({
101117
return <EmbedModal onClose={onClose} postId={postId} nickname={nickname} />
102118
}
103119

120+
if (activeModal === 'edit' && initialPostData) {
121+
return (
122+
<EditPostModal
123+
open={true}
124+
onOpenChange={(open) => !open && onClose()}
125+
initialData={initialPostData}
126+
onSuccess={handleEditSuccess}
127+
/>
128+
)
129+
}
130+
104131
if (activeModal === 'delete_confirm') {
105132
return (
106133
<div
@@ -154,7 +181,10 @@ export default function PostMenuModal({
154181
>
155182
삭제
156183
</button>
157-
<button className="w-full border-b border-gray-200 py-3 active:bg-gray-100">
184+
<button
185+
onClick={() => setActiveModal('edit')}
186+
className="w-full border-b border-gray-200 py-3 active:bg-gray-100"
187+
>
158188
수정
159189
</button>
160190
</>

src/entities/album/ui/AlbumSummaryCard.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ export function AlbumSummaryCard({
3434
<CardTitle className="text-base font-semibold">{title}</CardTitle>
3535
</CardHeader>
3636

37-
<CardContent className="aspect-square px-0 pb-0">
38-
<div className="relative h-full overflow-hidden rounded-b-2xl">
37+
<CardContent className="px-0 pb-0">
38+
<div className="relative aspect-square w-full overflow-hidden rounded-b-2xl">
3939
<LazyImage
4040
src={thumbnailImageUrl}
4141
alt={title}
42-
wrapperClassName="h-full w-full"
43-
className="h-full w-full"
42+
wrapperClassName="absolute inset-0"
43+
className="h-full w-full object-cover"
4444
/>
4545

4646
<div className="pointer-events-none absolute inset-0 h-full w-full bg-linear-to-t from-black/35 via-black/5 to-transparent" />
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query'
2+
import { instance } from '@/shared/api/ky'
3+
import type { PostData } from '@/components/post/PostDetail'
4+
5+
export function useUpdatePostMutation(postId: number) {
6+
const queryClient = useQueryClient()
7+
8+
return useMutation({
9+
mutationFn: async (payload: {
10+
content: string
11+
albumId: number | null
12+
imageUrls: string[]
13+
}) => {
14+
const response = await instance
15+
.put(`api/v1/posts/${postId}`, { json: payload })
16+
.json<{ data: PostData; isSuccess: boolean }>()
17+
return response.data
18+
},
19+
onSuccess: (updatedData) => {
20+
queryClient.setQueryData(['post', postId], updatedData)
21+
queryClient.invalidateQueries({ queryKey: ['posts'] })
22+
},
23+
})
24+
}

0 commit comments

Comments
 (0)