Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
9cfb72d
refactor: button ui 컴포넌트 분리
d5br5 Apr 29, 2025
cdc6359
refactor: input ui 컴포넌트 분리
d5br5 Apr 29, 2025
634abe8
refactor: card ui 컴포넌트 분리
d5br5 Apr 29, 2025
a097d3f
chore: prettier 적용
d5br5 Apr 29, 2025
a6bdc20
refactor: textarea ui 컴포넌트 분리
d5br5 Apr 29, 2025
3f217fe
refactor: select ui 컴포넌트 분리
d5br5 Apr 29, 2025
fbb7cf9
refactor: dialog, table ui 컴포넌트 분리
d5br5 Apr 30, 2025
820a79e
fix: export 안한 컴포넌트들 export
d5br5 Apr 30, 2025
440cffe
chore: `@tanstack/react-query`, `zustand` 설치
d5br5 Apr 30, 2025
dd0c7a9
feat: prettier 적용
d5br5 Apr 30, 2025
07a99d7
feat: highlightText 유틸함수 분리
d5br5 Apr 30, 2025
4ccb43e
refactor: 사용자 상세 정보 dialog 분리
d5br5 Apr 30, 2025
132ea15
feat: 사용자 상세 정보 dialog 분리
d5br5 Apr 30, 2025
1bf8105
feat: 태그 목록 훅으로 분리
d5br5 Apr 30, 2025
ec02da6
feat: 게시물 상세 dialog 분리
d5br5 Apr 30, 2025
cfd0a69
feat: posts, users 훅 구현
d5br5 Apr 30, 2025
37b1e91
feat: test 코드에 query client provider 적용
d5br5 Apr 30, 2025
7943af8
refactor: post row 컴포넌트 분리
d5br5 May 1, 2025
31a0bc3
refactor: add comment dialog 분리
d5br5 May 1, 2025
e07c4ca
fix: render post table 함수 삭제
d5br5 May 1, 2025
c039598
fix: add comment dialog 분리
d5br5 May 1, 2025
0bc7213
refactor: edit comment dialog 분리
d5br5 May 1, 2025
0436220
refactor add post dialog 컴포넌트 분리
d5br5 May 1, 2025
af3121b
refactor: URL 업데이트 로직 분리
d5br5 May 1, 2025
470995c
refactor: tag selector 컴포넌트 분리
d5br5 May 1, 2025
209e016
refactor: posts 데이터 합치기
d5br5 May 1, 2025
9fe6aaf
refactor: pagination 관련 컴포넌트 분리
d5br5 May 1, 2025
ee4c2f9
refactor: 정렬 기준, 방향 선택 컴포넌트 분리
d5br5 May 1, 2025
245c2d7
refactor: post table 컴포넌트 분리
d5br5 May 1, 2025
5a2bd28
ci: path alias 설정 추가
d5br5 May 1, 2025
c565afe
refactor: app 폴더로 이동
d5br5 May 1, 2025
32a2428
refactor: highlightText 유틸함수를 shared/lib 폴더로 이동
d5br5 May 1, 2025
cc012d9
refactor: admin header widget 컴포넌트로 분리
d5br5 May 1, 2025
ab1f886
refactor: post filter widget component 분리
d5br5 May 1, 2025
df24a18
refactor: admin nav widget component 분리
d5br5 May 1, 2025
f5eb8ce
refactor: 이전, 다음 버튼 features 로 분리
d5br5 May 1, 2025
3ccf923
refactor: select component 분리
d5br5 May 1, 2025
960e483
refactor: query client provider를 분리
d5br5 May 1, 2025
ba48925
refactor: tag entity 분리
d5br5 May 1, 2025
c584fdd
refactor: add comment dialog 분리
d5br5 May 1, 2025
9646caf
refactor: user detail dialog 분리
d5br5 May 1, 2025
1cb0bba
refactor: useComments 를 comment entity로 이동
d5br5 May 1, 2025
e2bc199
refactor: post 관련 api 정리
d5br5 May 1, 2025
1c05bb5
refactor: move useUserPosts to entity layer and update related imports
d5br5 May 1, 2025
88b42c7
refactor: update import path for useUpdateURL to features layer
d5br5 May 1, 2025
7f940b5
refactor: reorganize add post dialog components and related API logic
d5br5 May 1, 2025
9c4300b
feat: add filter components for pagination, sorting, and searching
d5br5 May 1, 2025
11c807b
feat: implement post management components including header, footer, …
d5br5 May 1, 2025
442d997
refactor: post row, table컴포넌트 분리
d5br5 May 1, 2025
5f087bd
refactor: edit comment dialog 분리
d5br5 May 1, 2025
d9688fe
refactor: edit dialog 분리
d5br5 May 1, 2025
6e137b8
refactor: post detail dialog 를 widget으로 분리
d5br5 May 1, 2025
0c22b06
refactor: 상세 모달의 버튼 분리
d5br5 May 1, 2025
192a2af
refactor: store를 feature에 이관
d5br5 May 1, 2025
34349cd
fix: 댓글 수정 버튼 재활성화
d5br5 May 1, 2025
514d80f
refactor: post edit button 분리
d5br5 May 1, 2025
cecb036
refactor: 게시물 삭제 버튼 분리
d5br5 May 1, 2025
7be1620
refactor: tag badge 컴포넌트 분리
d5br5 May 1, 2025
873f62f
refactor: 새 댓글 추가 dialog open 버튼
d5br5 May 2, 2025
55dd4ec
refactor: 댓글 삭제 버튼 분리
d5br5 May 2, 2025
eccec4f
refactor: 게시물 추가 버튼 분리
d5br5 May 2, 2025
317f60d
refactor: post delete button 분리
d5br5 May 2, 2025
f3890e3
refactor: post 수정 dialog 열기 버튼 분리
d5br5 May 2, 2025
1880de2
feat: cn 유틸 추가
d5br5 May 2, 2025
4dc3e08
refactor: post add dialog 분리
d5br5 May 2, 2025
97d2d06
refactor: 게시물 수정 dialog
d5br5 May 2, 2025
5146e37
refactor: tag 분리
d5br5 May 2, 2025
dfe1d33
refactor: limit store 분리
d5br5 May 2, 2025
a7dbbc8
refactor: filter features 분리
d5br5 May 2, 2025
7043ba1
refactor: add comment dialog 분리
d5br5 May 2, 2025
02bd1fa
refactor: open edit dialog 분리
d5br5 May 2, 2025
1c75ac1
refactor: edit, add dialog 분리
d5br5 May 2, 2025
a1839fb
refactor: limit select 분리
d5br5 May 2, 2025
0f104fc
fix: import url
d5br5 May 2, 2025
485caf2
fix: import url
d5br5 May 2, 2025
1246b1f
feat: 게시물 작성시 목록에 추가
d5br5 May 2, 2025
aac9dae
fix: test 에 path alias 추가
d5br5 May 2, 2025
628a181
feat: 댓글 삭제 기능
d5br5 May 2, 2025
6e17515
feat: 쿼리 유효기간 10분 지정
d5br5 May 2, 2025
ee3a1cb
feat: 게시물 수정 기능
d5br5 May 2, 2025
548fb53
feat: 게시물 삭제 기능
d5br5 May 2, 2025
d49e0e2
feat: 댓글 추가 기능
d5br5 May 2, 2025
acba272
feat: 댓글 수정 기능
d5br5 May 2, 2025
a218bb7
feat: 댓글 좋아요 기능
d5br5 May 2, 2025
49e1843
ci: gh-pages 세팅
d5br5 May 2, 2025
e6c8693
fix: type error 수정
d5br5 May 2, 2025
0c6e096
fix: base path 수정
d5br5 May 2, 2025
4eb78e5
chore: 공백 추가
d5br5 May 2, 2025
750db06
fix: build filename 변경
d5br5 May 2, 2025
0d1bae6
fix: 배포 전용 API path
d5br5 May 2, 2025
b85d594
feat: @types/node 설치
d5br5 May 2, 2025
e52fb41
chore: build hash update
d5br5 May 2, 2025
905c326
fix: dialog 잘림 해결
d5br5 May 2, 2025
8f25535
Update README.md
d5br5 May 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 134 additions & 59 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script type="module" src="/src/app/main.tsx"></script>
</body>
</html>
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
"private": true,
"version": "0.0.0",
"type": "module",
"homepage": "https://d5br5.github.io/front-5th-chapter2-3/",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest",
"coverage": "vitest run --coverage"
"coverage": "vitest run --coverage",
"deploy": "pnpm run build && gh-pages -d dist"
},
"dependencies": {
"@tanstack/react-query": "^5.74.11",
"clsx": "^2.1.1",
"gh-pages": "^6.3.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"tailwind-merge": "^3.2.0",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
Expand All @@ -22,6 +29,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^22.15.3",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1",
Expand Down
361 changes: 315 additions & 46 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

20 changes: 0 additions & 20 deletions src/App.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BrowserRouter as Router } from "react-router-dom"

import { QueryClientProvider } from "@/app/providers/QueryClientProvider"

import PostsManagerPage from "@/pages/PostsManagerPage.tsx"

import Header from "@/widgets/layout/ui/Header"
import Footer from "@/widgets/layout/ui/Footer"

const App = () => {
return (
<QueryClientProvider>
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow container mx-auto px-4 py-8">
<PostsManagerPage />
</main>
<Footer />
</div>
</Router>
</QueryClientProvider>
)
}

export default App
1 change: 1 addition & 0 deletions src/app/config/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const API_BASE_PATH = process.env.NODE_ENV === "production" ? "https://dummyjson.com" : "/api"
10 changes: 10 additions & 0 deletions src/app/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import "./style/index.css"
import App from "@/app/App.tsx"

createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
)
15 changes: 15 additions & 0 deletions src/app/providers/QueryClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useState } from "react"
import { QueryClient, QueryClientProvider as ReactQueryClientProvider } from "@tanstack/react-query"

export const QueryClientProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: { refetchOnWindowFocus: false, staleTime: 10 * 60_000 },
},
}),
)

return <ReactQueryClientProvider client={queryClient}>{children}</ReactQueryClientProvider>
}
File renamed without changes.
File renamed without changes.
11 changes: 11 additions & 0 deletions src/entity/comment/api/addComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NewComment } from "../model/types"
import { API_BASE_PATH } from "@/app/config/fetch"

export const addComment = async (comment: NewComment) => {
const response = await fetch(`${API_BASE_PATH}/comments/add`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(comment),
})
return await response.json()
}
8 changes: 8 additions & 0 deletions src/entity/comment/api/deleteComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { API_BASE_PATH } from "@/app/config/fetch"

export const deleteComment = async (commentId: number) => {
const response = await fetch(`${API_BASE_PATH}/comments/${commentId}`, {
method: "DELETE",
})
return await response.json()
}
10 changes: 10 additions & 0 deletions src/entity/comment/api/editComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { API_BASE_PATH } from "@/app/config/fetch"

export const editComment = async (commentId: number, body: string) => {
const response = await fetch(`${API_BASE_PATH}/comments/${commentId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body }),
})
return await response.json()
}
8 changes: 8 additions & 0 deletions src/entity/comment/api/getComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import axios from "axios"
import { GetCommentsResponse } from "../model/types"
import { API_BASE_PATH } from "@/app/config/fetch"

export const getComments = async (postId: number) => {
const res = await axios.get<GetCommentsResponse>(`${API_BASE_PATH}/comments/post/${postId}`)
return res.data
}
9 changes: 9 additions & 0 deletions src/entity/comment/api/likeComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { API_BASE_PATH } from "@/app/config/fetch"

export const likeComment = async (commentId: number, likes: number) => {
const response = await fetch(`${API_BASE_PATH}/comments/${commentId}`, {
method: "PATCH",
body: JSON.stringify({ likes }),
})
return response.json()
}
12 changes: 12 additions & 0 deletions src/entity/comment/model/selectedComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { create } from "zustand"
import { Comment } from "@/entity/comment/model/types"

interface SelectedCommentStore {
selectedComment: Comment | null
setSelectedComment: (comment: Comment | null) => void
}

export const useSelectedComment = create<SelectedCommentStore>((set) => ({
selectedComment: null,
setSelectedComment: (comment) => set({ selectedComment: comment }),
}))
22 changes: 22 additions & 0 deletions src/entity/comment/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { User } from "@/entity/user/model/types"

export interface NewComment {
userId: number
body: string
postId: number | null
}

export interface Comment {
body: string
id: number
likes: number
postId: number
user: User
}

export interface GetCommentsResponse {
limit: number
skip: number
total: number
comments: Comment[]
}
16 changes: 16 additions & 0 deletions src/entity/comment/model/useComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useQuery } from "@tanstack/react-query"
import { getComments } from "@/entity/comment/api/getComments"

export const useComments = (postId: number) => {
return useQuery({
queryKey: ["comments", postId],
queryFn: async () => {
try {
return await getComments(postId)
} catch (error) {
console.error("댓글 가져오기 오류:", error)
return
}
},
})
}
12 changes: 12 additions & 0 deletions src/entity/comment/ui/AddButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Button } from "@/shared/ui"
import { Plus } from "lucide-react"
import { ComponentProps } from "react"

export const CommentAddButton = (props: ComponentProps<typeof Button>) => {
return (
<Button size="sm" {...props}>
<Plus className="w-3 h-3 mr-1" />
댓글 추가
</Button>
)
}
11 changes: 11 additions & 0 deletions src/entity/comment/ui/DeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Button } from "@/shared/ui"
import { Trash2 } from "lucide-react"
import { ComponentProps } from "react"

export const CommentDeleteButton = (props: ComponentProps<typeof Button>) => {
return (
<Button variant="ghost" size="sm" {...props}>
<Trash2 className="w-3 h-3" />
</Button>
)
}
16 changes: 16 additions & 0 deletions src/entity/comment/ui/LikeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Button } from "@/shared/ui"
import { ThumbsUp } from "lucide-react"
import { ComponentProps } from "react"

interface CommentLikeButtonProps extends ComponentProps<typeof Button> {
likes: number
}

export const CommentLikeButton = ({ likes, ...props }: CommentLikeButtonProps) => {
return (
<Button variant="ghost" size="sm" {...props}>
<ThumbsUp className="w-3 h-3" />
<span className="ml-1 text-xs">{likes}</span>
</Button>
)
}
11 changes: 11 additions & 0 deletions src/entity/comment/ui/OpenEditDialogButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Button } from "@/shared/ui"
import { Edit2 } from "lucide-react"
import { ComponentProps } from "react"

export const OpenEditDialogButton = (props: ComponentProps<typeof Button>) => {
return (
<Button variant="ghost" size="sm" {...props}>
<Edit2 className="w-3 h-3" />
</Button>
)
}
12 changes: 12 additions & 0 deletions src/entity/post/api/addPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { API_BASE_PATH } from "@/app/config/fetch"
import { NewPost } from "../model/types"

export const addPost = async (post: NewPost) => {
const response = await fetch(`${API_BASE_PATH}/posts/add`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(post),
})
const data = await response.json()
return data
}
8 changes: 8 additions & 0 deletions src/entity/post/api/deletePost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { API_BASE_PATH } from "@/app/config/fetch"

export const deletePost = async (id: number) => {
const response = await fetch(`${API_BASE_PATH}/posts/${id}`, {
method: "DELETE",
})
return await response.json()
}
11 changes: 11 additions & 0 deletions src/entity/post/api/editPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { API_BASE_PATH } from "@/app/config/fetch"
import { Post } from "../model/types"

export const editPost = async (postId: number, post: Post) => {
const response = await fetch(`${API_BASE_PATH}/posts/${postId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(post),
})
return await response.json()
}
8 changes: 8 additions & 0 deletions src/entity/post/api/getPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import axios from "axios"
import { GetPostsResponse } from "../model/types"
import { API_BASE_PATH } from "@/app/config/fetch"

export const getPosts = async (limit: number, skip: number) => {
const res = await axios.get<GetPostsResponse>(`${API_BASE_PATH}/posts?limit=${limit}&skip=${skip}`)
return res.data
}
8 changes: 8 additions & 0 deletions src/entity/post/api/getTagPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import axios from "axios"
import { GetPostsResponse } from "../model/types"
import { API_BASE_PATH } from "@/app/config/fetch"

export const getTagPosts = async (tag: string) => {
const res = await axios.get<GetPostsResponse>(`${API_BASE_PATH}/posts/tag/${tag}`)
return res.data
}
8 changes: 8 additions & 0 deletions src/entity/post/api/searchPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import axios from "axios"
import { GetPostsResponse } from "../model/types"
import { API_BASE_PATH } from "@/app/config/fetch"

export const searchPosts = async (query: string) => {
const res = await axios.get<GetPostsResponse>(`${API_BASE_PATH}/posts/search?q=${query}`)
return res.data
}
12 changes: 12 additions & 0 deletions src/entity/post/model/selectedPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { create } from "zustand"
import { Post } from "./types"

interface SelectedPostStore {
selectedPost: Post | null
setSelectedPost: (post: Post | null) => void
}

export const useSelectedPost = create<SelectedPostStore>((set) => ({
selectedPost: null,
setSelectedPost: (post) => set({ selectedPost: post }),
}))
25 changes: 25 additions & 0 deletions src/entity/post/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export interface Post {
id: number
body: string
reactions: {
dislikes: number
likes: number
}
tags: string[]
title: string
userId: number
views: number
}

export interface GetPostsResponse {
limit: number
skip: number
total: number
posts: Post[]
}

export interface NewPost {
title: string
body: string
userId: number
}
19 changes: 19 additions & 0 deletions src/entity/post/model/useNormalPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useQuery } from "@tanstack/react-query"

import { getPosts } from "../api/getPosts"
import { usePaginationStore } from "@/features/pagination/model/usePaginationStore"

export const useNormalPosts = () => {
const { limit, skip } = usePaginationStore()

return useQuery({
queryKey: ["posts", { limit, skip }],
queryFn: async () => {
try {
return await getPosts(limit, skip)
} catch (e) {
console.error("게시물 가져오기 오류:", e)
}
},
})
}
Loading