diff --git a/package.json b/package.json index fcb2757eb..fbc609173 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "coverage": "vitest run --coverage" }, "dependencies": { + "@tanstack/react-query": "^5.75.2", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1c5650db..7505666f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@tanstack/react-query': + specifier: ^5.75.2 + version: 5.75.2(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -899,6 +902,14 @@ packages: cpu: [x64] os: [win32] + '@tanstack/query-core@5.75.0': + resolution: {integrity: sha512-rk8KQuCdhoRkzjRVF3QxLgAfFUyS0k7+GCQjlGEpEGco+qazJ0eMH6aO1DjDjibH7/ik383nnztua3BG+lOnwg==} + + '@tanstack/react-query@5.75.2': + resolution: {integrity: sha512-8F8VOsWUfSkCFoi62O9HSZT9jDgg28Ln8Z2dYKfRo/O2A0sgvr0uxTuNoon3PPXoDuHofv5V3elBI1M2Gh1MPg==} + peerDependencies: + react: ^18 || ^19 + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -2936,6 +2947,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true + '@tanstack/query-core@5.75.0': {} + + '@tanstack/react-query@5.75.2(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.75.0 + react: 19.1.0 + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 @@ -3177,7 +3195,7 @@ snapshots: '@vitest/utils@2.1.3': dependencies: '@vitest/pretty-format': 2.1.3 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 1.2.0 '@vitest/utils@3.1.2': diff --git a/src/App.tsx b/src/App.tsx index 82d35d55b..5f699dab6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,17 +2,28 @@ import { BrowserRouter as Router } from "react-router-dom" import Header from "./widgets/ui/Header.tsx" import Footer from "./widgets/ui/Footer.tsx" import PostsManagerPage from "./pages/PostsManagerPage.tsx" +import { PostsProvider } from "./features/post/get-posts/context.tsx" +import { CommentsProvider } from "./features/comment/get-comments/context.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" const App = () => { + const queryClient = new QueryClient() + return ( -
-
-
- -
-
-
+ +
+
+
+ + + + + +
+
+
) } diff --git a/src/entities/comment/api.ts b/src/entities/comment/api.ts new file mode 100644 index 000000000..57e9d6576 --- /dev/null +++ b/src/entities/comment/api.ts @@ -0,0 +1,24 @@ +import ApiClient from "../../shared/api/apiClient.ts" +import { Comment, Comments, NewComment } from "./model.ts" + +export const createComment = (comment: NewComment) => { + return ApiClient.post("/comments/add", comment) +} + +export const fetchComments = (postId: number) => { + return ApiClient.get(`/comments/post/${postId}`) +} + +export const updateComment = (comment: Comment) => { + return ApiClient.put>(`/comments/${comment.id}`, { + body: comment.body, + }) +} + +export const updateCommentLikes = (commentId: number, likes: number) => { + return ApiClient.patch>(`/comments/${commentId}`, { likes }) +} + +export const deleteComment = (commentId: number) => { + return ApiClient.del(`/comments/${commentId}`) +} diff --git a/src/entities/comment/model.ts b/src/entities/comment/model.ts new file mode 100644 index 000000000..26603f640 --- /dev/null +++ b/src/entities/comment/model.ts @@ -0,0 +1,26 @@ +interface User { + id: number + username: string + fullName: string +} + +export interface Comment { + id: number + postId: number + body: string + likes: number + user: User +} + +export interface Comments { + comments: Comment[] + limit: number + skip: number + total: number +} + +export interface NewComment { + userId: User["id"] + postId: Comment["postId"] + body: Comment["body"] +} diff --git a/src/entities/post/api.ts b/src/entities/post/api.ts new file mode 100644 index 000000000..a45cb3fe4 --- /dev/null +++ b/src/entities/post/api.ts @@ -0,0 +1,26 @@ +import ApiClient from "../../shared/api/apiClient.ts" +import { Post, Posts, NewPost } from "./model.ts" + +export const createPost = (post: NewPost) => { + return ApiClient.post(`/posts/add`, post) +} + +export const fetchPosts = (limit: number, skip: number) => { + return ApiClient.get("/posts", { limit, skip }) +} + +export const updatePost = (post: Post) => { + return ApiClient.put(`/posts/${post.id}`, post) +} + +export const deletePost = (postId: number) => { + return ApiClient.del(`/posts/${postId}`) +} + +export const searchPosts = (searchQuery: string) => { + return ApiClient.get(`/posts/search`, { q: searchQuery }) +} + +export const searchPostsByTag = (tag: string) => { + return ApiClient.get(`/posts/tag/${tag}`) +} diff --git a/src/entities/post/model.ts b/src/entities/post/model.ts new file mode 100644 index 000000000..ec0802c02 --- /dev/null +++ b/src/entities/post/model.ts @@ -0,0 +1,25 @@ +export interface Post { + id: number + userId: number + title: string + body: string + tags: string[] + reactions: { + likes: number + dislikes: number + } + views: number +} + +export interface Posts { + limit: number + posts: Post[] + skip: number + total: number +} + +export interface NewPost { + userId: Post["userId"] + title: Post["title"] + body: Post["body"] +} diff --git a/src/entities/tag/api.ts b/src/entities/tag/api.ts new file mode 100644 index 000000000..f5eb7c563 --- /dev/null +++ b/src/entities/tag/api.ts @@ -0,0 +1,6 @@ +import ApiClient from "../../shared/api/apiClient.ts" +import { Tag } from "./model.ts" + +export const fetchTags = () => { + return ApiClient.get("/posts/tags") +} diff --git a/src/entities/tag/model.ts b/src/entities/tag/model.ts new file mode 100644 index 000000000..28fac11d2 --- /dev/null +++ b/src/entities/tag/model.ts @@ -0,0 +1,5 @@ +export interface Tag { + name: string + slug: string + url: string +} diff --git a/src/entities/user/api.ts b/src/entities/user/api.ts new file mode 100644 index 000000000..93d616ed9 --- /dev/null +++ b/src/entities/user/api.ts @@ -0,0 +1,13 @@ +import ApiClient from "../../shared/api/apiClient.ts" +import { User, Users } from "./model.ts" + +export const fetchUsers = () => { + return ApiClient.get("/users", { + limit: 0, + select: ["username", "image"].join(","), + }) +} + +export const fetchUser = (userId: number) => { + return ApiClient.get(`/users/${userId}`) +} diff --git a/src/entities/user/model.ts b/src/entities/user/model.ts new file mode 100644 index 000000000..996f13215 --- /dev/null +++ b/src/entities/user/model.ts @@ -0,0 +1,78 @@ +interface Hair { + color: string + type: string +} + +interface Coordinates { + lat: number + lng: number +} + +interface Address { + address: string + city: string + state: string + stateCode: string + postalCode: string + coordinates: Coordinates + country: string +} + +interface Bank { + cardExpire: string + cardNumber: string + cardType: string + currency: string + iban: string +} + +interface Company { + department: string + name: string + title: string + address: Address +} + +interface Crypto { + coin: string + wallet: string + network: string +} + +export interface BaseUser { + id: number + username: string + image: string +} + +export interface User extends BaseUser { + firstName: string + lastName: string + maidenName: string + age: number + gender: string + email: string + phone: string + password: string + birthDate: string + bloodGroup: string + height: number + weight: number + eyeColor: string + hair: Hair + ip: string + address: Address + macAddress: string + university: string + bank: Bank + company: Company + ein: string + ssn: string + userAgent: string + crypto: Crypto + role: string +} + +export interface Users { + users: BaseUser[] +} diff --git a/src/features/comment/add-comment/ui/comment-add-dialog.tsx b/src/features/comment/add-comment/ui/comment-add-dialog.tsx new file mode 100644 index 000000000..550cce367 --- /dev/null +++ b/src/features/comment/add-comment/ui/comment-add-dialog.tsx @@ -0,0 +1,57 @@ +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../../../../shared/ui/dialog/dialog.tsx" +import { Textarea } from "../../../../shared/ui/textarea/textarea.tsx" +import { Button } from "../../../../shared/ui/button/button.tsx" +import { useComments } from "../../get-comments/context.tsx" +import { NewComment } from "../../../../entities/comment/model.ts" +import { useState } from "react" + +type CommentAddDialogProps = { + postId?: number | null + showAddCommentDialog: boolean + setShowAddCommentDialog: (showAddCommentDialog: boolean) => void +} + +export const CommentAddDialog = ({ + postId = null, + showAddCommentDialog, + setShowAddCommentDialog, +}: CommentAddDialogProps) => { + const { addComment } = useComments() + + const [newComment, setNewComment] = useState & { postId: number | null }>({ + body: "", + postId, + userId: 1, + }) + + // 댓글 추가 + const _addComment = async () => { + if (newComment.postId === null) return + + try { + await addComment(newComment as NewComment) + setShowAddCommentDialog(false) + setNewComment({ body: "", postId: null, userId: 1 }) + } catch (error) { + console.error("댓글 추가 오류:", error) + } + } + + return ( + + + + 새 댓글 추가 + +
+