Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"semi": false,
"printWidth": 120,
"printWidth": 80,
"tabWidth": 2,
"singleQuote": false,
"quoteProps": "consistent",
"trailingComma": "all",
"singleAttributePerLine": false
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
Expand Down
28 changes: 27 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,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 { useEffect, useState } from "react"
import { initializeApp } from "./app/api/initApp.ts"
// import PostsManagerPage from "./pages/_PostsManagerPage.tsx"
import { PostsManagerPage } from "./pages/PostsManagerPage.tsx"

const App = () => {
const [initialized, setInitialized] = useState(false)

useEffect(() => {
const init = async () => {
const success = await initializeApp()
setInitialized(success)
}
init()
}, [])

return (
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow container mx-auto px-4 py-8">
<PostsManagerPage />
{initialized && <PostsManagerPage />}
</main>
<Footer />
</div>
Expand Down
19 changes: 19 additions & 0 deletions src/app/api/initApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { postApi } from "@/entities/post/api/postApi"
import { usePostStore } from "@/entities/post/model/store"
import { userApi } from "@/entities/user/api/userApi"
import { useUserStore } from "@/entities/user/model/store"

export const initializeApp = async () => {
try {
const users = await userApi.fetchUsers()
const tags = await postApi.fetchTags()

useUserStore.getState().setUsers(users.users)
usePostStore.getState().setTags(tags)

return true
} catch (error) {
console.error("앱 초기화 에러: ", error)
return false
}
}
49 changes: 49 additions & 0 deletions src/entities/comment/api/commentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Comment } from "../model/type"

export const commentApi = {
fetchComments: async (postId: number) => {
const response = await fetch(`/api/comments/post/${postId}`)
const data = await response.json()

return data
},

addComment: async (newComment: Comment) => {
const response = await fetch("/api/comments/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newComment),
})
const data = await response.json()

return data
},

updateComment: async (commentId: number, body: string) => {
const response = await fetch(`/api/comments/${commentId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: body }),
})
const data = await response.json()

return data
},

deleteComment: async (commentId: number) => {
await fetch(`/api/comments/${commentId}`, {
method: "DELETE",
})
},

likeComment: async (commentId: number, currentLikes: number) => {
const response = await fetch(`/api/comments/${commentId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ likes: currentLikes + 1 }),
})
const data = await response.json()

return data
},
}
16 changes: 16 additions & 0 deletions src/entities/comment/model/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { create } from "zustand"
import { Comment } from "./type"

interface CommentStore {
comments: Comment[]
selectedComment: Comment | null

setComments: (comments: Comment[]) => void
}

export const useCommentStore = create<CommentStore>((set) => ({
comments: [],
selectedComment: null,

setComments: (comments) => set({ comments }),
}))
10 changes: 10 additions & 0 deletions src/entities/comment/model/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { User } from "@/entities/user/model/type"

export interface Comment {
id: number
body: string
postId: number
userId?: number
likes?: number
user?: User
}
47 changes: 47 additions & 0 deletions src/entities/comment/ui/CommentItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { HighlightText } from "@/shared/ui/HighlightText"
import { Comment } from "../model/type"
import { Button } from "@/shared/ui"
import { Edit2, ThumbsUp, Trash2 } from "lucide-react"

interface CommentItemProps {
comment: Comment
searchQuery: string
onLike?: () => void
onEdit?: () => void
onDelete?: () => void
}

export const CommentItem = ({
comment,
searchQuery,
onLike,
onEdit,
onDelete,
}: CommentItemProps) => {
return (
<div className="flex items-center justify-between text-sm border-b pb-1">
<div className="flex items-center space-x-2 overflow-hidden">
<span className="font-medium truncate">{comment.user?.username}:</span>
<span className="truncate">
<HighlightText text={comment.body} highlight={searchQuery} />
</span>
</div>
<div className="flex items-center space-x-1">
{onLike && (
<Button variant="ghost" size="sm" onClick={onLike}>
<ThumbsUp className="w-3 h-3" />
<span className="ml-1 text-xs">{comment.likes}</span>
</Button>
)}
{onEdit && (
<Button variant="ghost" size="sm" onClick={onEdit}>
<Edit2 className="w-3 h-3" />
</Button>
)}
<Button variant="ghost" size="sm" onClick={onDelete}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
)
}
54 changes: 54 additions & 0 deletions src/entities/comment/ui/CommentList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Plus } from "lucide-react"
import { CommentItem } from "./CommentItem"
import { Button } from "@/shared/ui"
import { Comment } from "../model/type"

interface CommentListProps {
comments: Comment[]
searchQuery?: string
onAddComment: () => void
onLikeComment: (commentId: number) => void
onEditComment: (comment: Comment) => void
onDeleteComment: (commentId: number) => void
}

export const CommentList = ({
comments,
searchQuery = "",
onAddComment,
onLikeComment,
onEditComment,
onDeleteComment,
}: CommentListProps) => {
return (
<div className="mt-2">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-semibold">댓글</h3>
<Button size="sm" onClick={onAddComment}>
<Plus className="w-3 h-3 mr-1" />
댓글 추가
</Button>
</div>
<div className="space-y-1">
{comments && comments.length > 0 ? (
comments.map((comment) => (
<CommentItem
key={comment.id}
comment={comment}
searchQuery={searchQuery}
onLike={
onLikeComment ? () => onLikeComment(comment.id) : undefined
}
onEdit={onEditComment ? () => onEditComment(comment) : undefined}
onDelete={
onDeleteComment ? () => onDeleteComment(comment.id) : undefined
}
/>
))
) : (
<div className="text-sm text-gray-500 py-2">댓글이 없습니다.</div>
)}
</div>
</div>
)
}
59 changes: 59 additions & 0 deletions src/entities/post/api/postApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Post } from "../model/type"

export const postApi = {
fetchPosts: async (limit: number, skip: number) => {
const response = await fetch(`/api/posts?limit=${limit}&skip=${skip}`)
const data = await response.json()

return data
},

fetchTags: async () => {
const response = await fetch("/api/posts/tags")
const data = await response.json()

return data
},

fetchPostsByTag: async (tag: string) => {
const response = await fetch(`/api/posts/tag/${tag}`)
const data = await response.json()

return data
},

searchPosts: async (searchQuery: string) => {
const response = await fetch(`/api/posts/search?q=${searchQuery}`)
const data = await response.json()

return data
},

addPost: async (newPost: Post) => {
const response = await fetch("/api/posts/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newPost),
})
const data = await response.json()

return data
},

updatePost: async (selectedPost: Post) => {
const response = await fetch(`/api/posts/${selectedPost.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(selectedPost),
})
const data = await response.json()

return data
},

deletePost: async (id: number) => {
await fetch(`/api/posts/${id}`, {
method: "DELETE",
})
},
}
Loading