Skip to content

Commit 47eb100

Browse files
kimnamheeeec0912jyclaude
authored
배포 (#104)
* ✨ feat(components): add sonner * ✨ feat(provider): integrate toaster * ✨ feat(components): add dropzone component * ✨ feat(components): integrate drag and drop to createmodal * ♻️ refactor(components): separate logic using custom hook * ♻️ refactor(components): separate ui components * ✨ feat(components): add post details pane * ✨ feat(components): add double modal on exit * 🧹 chore: add testing library * 🧪 test(createmodal): add create post modal related tests * ✨ feat(components): add profile header * ✨ feat(api): initialize ky instance * ✨ feat(components): add tabs * ✨ feat(routes): separate app route layer * ✨ feat(routes): update route tree * ✨ feat(components): add fallback image * ✨ feat(profile-posts): add grid ui * ✨ feat(profilepage): add posts grid ui * 🧹 chore(homepage): remove duplicate navigation shell * ♻️ refactor(profilepage): add common style container component * ✨ feat(profile): add profile page component * ✨ feat(navigation): add useNavController hook * 🧹 chore: rerun yarn install * ✨ feat(msw): msw 기본 설정 * ✨ feat(components): add carousel * ♻️ refactor(sidebar): collapse sidebar below xl * ✨ feat(components): add stories field * ✨ feat(stories): add link to story route * ✨ feat: msw 구조 수정 * ✨ feat: msw post db 업데이트 및 기본 레이아웃 구현 * ✨ feat: 사진 기본 ui 구현 * ✨ feat: 하트 색깔 및 불투명도 수정 * ✨ feat: 하트 올라가는 애니메이션 구현 * ✨ feat: 하트 애니메이션 구체화 및 버그 수정 * ✨ feat(msw): add common response type * 🐛 fix(msw): fix common response type field name * ✨ feat(msw): add mock users * ✨ 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 --------- Co-authored-by: c0912jy <c0912jy@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ea977ad commit 47eb100

Some content is hidden

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

42 files changed

+1932
-252
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
]
2727
},
2828
"dependencies": {
29+
"@radix-ui/react-avatar": "^1.1.11",
2930
"@radix-ui/react-dialog": "^1.1.15",
3031
"@radix-ui/react-dropdown-menu": "^2.1.16",
3132
"@radix-ui/react-separator": "^1.1.8",
@@ -59,8 +60,8 @@
5960
"@tanstack/router-plugin": "^1.145.4",
6061
"@testing-library/dom": "^10.4.1",
6162
"@testing-library/jest-dom": "^6.9.1",
62-
"@testing-library/user-event": "^14.6.1",
6363
"@testing-library/react": "^16.3.1",
64+
"@testing-library/user-event": "^14.6.1",
6465
"@types/node": "^24.10.1",
6566
"@types/react": "^19.2.5",
6667
"@types/react-dom": "^19.2.3",

src/app/providers/Providers.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import type { PropsWithChildren } from 'react'
22
import { ThemeProvider } from 'next-themes'
33
import { SidebarProvider } from '@/shared/ui/sidebar'
44
import { Toaster } from '@/shared/ui/sonner'
5-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
5+
import { QueryClientProvider } from '@tanstack/react-query'
66
import { AuthProvider } from '@/shared/auth/AuthProvider'
7-
8-
const queryClient = new QueryClient()
7+
import { queryClient } from '@/shared/api/queryClient'
98

109
export function Providers({ children }: PropsWithChildren) {
1110
return (

src/components/auth/LoginCard.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { useNavigate } from '@tanstack/react-router'
33
import instagramLogo from '../../assets/instagram-logo.svg'
44
import { instance } from '../../shared/api/ky'
55
import { MessageCircle } from 'lucide-react'
6+
import { useInvalidateCurrentUser } from '@/shared/auth/useCurrentUser'
67

7-
interface FloatingInputProps {
8+
type FloatingInputProps = {
89
label: string
910
value: string
1011
onChange: (value: string) => void
@@ -19,7 +20,6 @@ interface LoginResponse {
1920
message: string
2021
data: {
2122
accessToken: string
22-
refreshToken: string
2323
} | null
2424
}
2525

@@ -75,6 +75,7 @@ const LoginCard = () => {
7575
const [showPw, setShowPw] = useState(false)
7676
const [errorMsg, setErrorMsg] = useState('')
7777
const navigate = useNavigate()
78+
const invalidateCurrentUser = useInvalidateCurrentUser()
7879

7980
const isValid = id.length > 0 && pw.length >= 6
8081

@@ -84,15 +85,17 @@ const LoginCard = () => {
8485

8586
try {
8687
const res = await instance
87-
.post('api/v1/auth/login', {
88+
.post('auth/login', {
8889
json: { loginId: id, password: pw },
8990
})
9091
.json<LoginResponse>()
9192

9293
if (res.isSuccess && res.data) {
9394
localStorage.setItem('accessToken', res.data.accessToken)
94-
localStorage.setItem('refreshToken', res.data.refreshToken)
95+
await invalidateCurrentUser()
9596
navigate({ to: '/' })
97+
} else {
98+
setErrorMsg(res.message || '로그인에 실패했습니다.')
9699
}
97100
} catch {
98101
setErrorMsg('잘못된 이메일 또는 비밀번호입니다. 다시 입력해주세요.')

src/components/post/CommentItem.tsx

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,45 @@ import { useMemo, useState } from 'react'
22
import { Heart, MoreHorizontal } from 'lucide-react'
33
import { formatRelativeTime } from '../../utils/date.ts'
44
import CommentMenuModal from './CommentMenuModal'
5+
import { Avatar, AvatarFallback, AvatarImage } from '@/shared/ui/avatar.tsx'
6+
import { useDeleteCommentMutation } from '@/entities/post/model/hooks/useDeleteCommentMutation'
57

68
interface Comment {
79
id: number
10+
postId: number
811
userId: number
912
nickname: string
1013
content: string
1114
profileImageUrl: string
1215
createdAt: string
16+
updatedAt: string
17+
parentId: number | null
1318
likeCount: number
1419
liked: boolean
20+
likedUserIds: number[]
1521
}
1622

1723
interface CommentItemProps {
1824
comment: Comment
19-
isReply?: boolean
2025
isLiked: boolean
2126
onDoubleClick: (id: number) => void
2227
onHeartClick: (id: number, e: React.MouseEvent) => void
28+
onDeleteSuccess?: (commentId: number) => void
29+
onEditClick: (comment: Comment) => void
2330
}
2431

2532
export default function CommentItem({
2633
comment,
27-
isReply = false,
2834
isLiked,
2935
onDoubleClick,
3036
onHeartClick,
37+
onDeleteSuccess,
38+
onEditClick,
3139
}: CommentItemProps) {
3240
const [isMenuOpen, setIsMenuOpen] = useState(false)
33-
const currentUserId = 999
41+
const deleteMutation = useDeleteCommentMutation(comment.postId)
42+
43+
const isEdited = comment.createdAt !== comment.updatedAt
3444

3545
const timeDisplay = useMemo(
3646
() => formatRelativeTime(comment.createdAt),
@@ -43,6 +53,25 @@ export default function CommentItem({
4353
}
4454

4555
const handleDelete = () => {
56+
deleteMutation.mutate(comment.id, {
57+
onSuccess: () => {
58+
if (onDeleteSuccess) {
59+
onDeleteSuccess(comment.id)
60+
}
61+
},
62+
})
63+
setIsMenuOpen(false)
64+
}
65+
66+
const handleHide = () => {
67+
if (onDeleteSuccess) {
68+
onDeleteSuccess(comment.id)
69+
}
70+
setIsMenuOpen(false)
71+
}
72+
73+
const handleEdit = () => {
74+
onEditClick(comment)
4675
setIsMenuOpen(false)
4776
}
4877

@@ -55,30 +84,30 @@ export default function CommentItem({
5584
onDoubleClick={() => onDoubleClick(comment.id)}
5685
>
5786
<div className="flex flex-1 items-start gap-3">
58-
<img
59-
src={comment.profileImageUrl}
60-
className={`${isReply ? 'h-6 w-6' : 'h-8 w-8'} mt-0.5 shrink-0 rounded-full object-cover`}
61-
alt=""
62-
/>
87+
<Avatar className="mt-0.5 h-8 w-8 shrink-0">
88+
<AvatarImage
89+
src={comment.profileImageUrl}
90+
alt={comment.nickname}
91+
className="object-cover"
92+
/>
93+
<AvatarFallback>{comment.nickname[0]}</AvatarFallback>
94+
</Avatar>
6395
<div className="text-sm leading-tight">
6496
<span className="mr-2 font-semibold text-black">
6597
{comment.nickname}
6698
</span>
6799
<span className="break-all text-black">{comment.content}</span>
68100

69101
<div className="mt-[7px] flex h-4 items-center gap-3 text-xs font-semibold text-gray-500">
70-
<span className="font-normal">{timeDisplay}</span>
102+
<span className="font-normal">
103+
{timeDisplay}
104+
{isEdited && ' (수정됨)'}
105+
</span>
71106
{displayLikeCount > 0 && (
72107
<span className="font-semibold text-gray-500">
73108
좋아요 {displayLikeCount}
74109
</span>
75110
)}
76-
<button
77-
className="hover:text-gray-900"
78-
onClick={(e) => e.stopPropagation()}
79-
>
80-
답글 달기
81-
</button>
82111
<button
83112
className="p-1 opacity-0 transition-opacity group-hover:opacity-100"
84113
onClick={handleMenuClick}
@@ -110,7 +139,10 @@ export default function CommentItem({
110139
<CommentMenuModal
111140
onClose={() => setIsMenuOpen(false)}
112141
onDelete={handleDelete}
113-
isMine={comment.userId === currentUserId}
142+
onEdit={handleEdit}
143+
onHide={handleHide}
144+
authorId={comment.userId}
145+
nickname={comment.nickname}
114146
/>
115147
)}
116148
</>

src/components/post/CommentMenuModal.tsx

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,84 @@
11
import { useState, useEffect } from 'react'
2+
import { useCurrentUser } from '@/shared/auth/useCurrentUser'
3+
import ReportModal from './ReportModal'
24

3-
interface CommentMenuModalProps {
5+
type CommentMenuModalProps = {
46
onClose: () => void
57
onDelete?: () => void
6-
isMine: boolean
8+
onEdit?: () => void
9+
onHide: () => void
10+
authorId: number
11+
nickname: string
712
}
813

914
export default function CommentMenuModal({
1015
onClose,
1116
onDelete,
12-
isMine,
17+
onEdit,
18+
onHide,
19+
authorId,
20+
nickname,
1321
}: CommentMenuModalProps) {
1422
const [isVisible, setIsVisible] = useState(false)
23+
const [isReportOpen, setIsReportOpen] = useState(false)
24+
const { data: currentUser } = useCurrentUser()
25+
const isMine = currentUser?.userId === authorId
1526

1627
useEffect(() => {
1728
const frame = requestAnimationFrame(() => setIsVisible(true))
1829
return () => cancelAnimationFrame(frame)
1930
}, [])
2031

32+
if (isReportOpen) {
33+
return (
34+
<ReportModal
35+
onClose={onClose}
36+
onHideComment={onHide}
37+
nickname={nickname}
38+
/>
39+
)
40+
}
41+
2142
return (
2243
<div
2344
className={`fixed inset-0 z-[100] flex items-center justify-center bg-black/50 transition-opacity duration-200 ${
2445
isVisible ? 'opacity-100' : 'opacity-0'
2546
}`}
47+
onClick={onClose}
2648
>
2749
<div
28-
className={`w-full max-w-[560px] overflow-hidden rounded-[24px] bg-white transition-all duration-200 ease-out ${
50+
className={`w-full max-w-[400px] overflow-hidden rounded-[12px] bg-white transition-all duration-200 ease-out ${
2951
isVisible ? 'scale-100 opacity-100' : 'scale-110 opacity-0'
3052
}`}
3153
onClick={(e) => e.stopPropagation()}
3254
>
3355
<div className="flex flex-col text-center">
3456
{isMine ? (
57+
<>
58+
<button
59+
onClick={onDelete}
60+
className="w-full border-b border-gray-200 py-3.5 text-[14px] font-bold text-[#ED4956] active:bg-gray-100"
61+
>
62+
삭제
63+
</button>
64+
<button
65+
onClick={onEdit}
66+
className="w-full border-b border-gray-200 py-3.5 text-[14px] font-normal text-black active:bg-gray-100"
67+
>
68+
수정
69+
</button>
70+
</>
71+
) : (
3572
<button
36-
onClick={onDelete}
37-
className="w-full border-b border-gray-200 py-4 text-[14px] font-bold text-[#ED4956] active:bg-gray-100"
73+
onClick={() => setIsReportOpen(true)}
74+
className="w-full border-b border-gray-200 py-3.5 text-[14px] font-bold text-[#ED4956] active:bg-gray-100"
3875
>
39-
삭제
40-
</button>
41-
) : (
42-
<button className="w-full border-b border-gray-200 py-4 text-[14px] font-bold text-[#ED4956] active:bg-gray-100">
4376
신고
4477
</button>
4578
)}
4679
<button
4780
onClick={onClose}
48-
className="w-full py-4 text-[14px] font-normal text-black active:bg-gray-100"
81+
className="w-full py-3.5 text-[14px] font-normal text-black active:bg-gray-100"
4982
>
5083
취소
5184
</button>

0 commit comments

Comments
 (0)