Skip to content

Commit 40459f7

Browse files
Merge pull request #59 from ShipFriend0516/feature/admin-feature
[Feature] 어드민 통계, 댓글 확인 기능 추가
2 parents e03de28 + 608655c commit 40459f7

File tree

15 files changed

+1161
-17
lines changed

15 files changed

+1161
-17
lines changed

README.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,46 @@
11
# TechBlog
2-
32

43
## 프로젝트 개요
54

65
NextJS를 기반으로 한 개인 기술 블로그와 포트폴리오 웹 애플리케이션입니다. 테크 관련 게시물을 작성하고 개인 프로젝트를 전시하는 공간으로 활용하는 프로젝트
76

8-
### 블로그
7+
### 블로그
8+
99
![image](https://github.com/user-attachments/assets/240cc1a5-5ad6-4921-bbef-ee1cd76fa379)
1010

11-
### 블로그 작성
11+
### 블로그 작성
12+
1213
![image](https://github.com/user-attachments/assets/4bb0c223-cfa2-4414-9a47-59883820d08b)
1314

14-
### 시리즈
15-
![image](https://github.com/user-attachments/assets/25d14078-8734-44a4-8943-aa7a2f70951f)
15+
### 시리즈
1616

17+
![image](https://github.com/user-attachments/assets/25d14078-8734-44a4-8943-aa7a2f70951f)
1718

1819
## 기술 스택
1920

2021
### Frontend
22+
2123
- **Framework**: [Next.js](https://nextjs.org/) (App Router)
2224
- **Language**: [TypeScript](https://www.typescriptlang.org/)
2325
- **Styling**: [TailwindCSS](https://tailwindcss.com/)
2426
- **State Management**: [Zustand](https://github.com/pmndrs/zustand)
25-
- **UI Components**:
27+
- **UI Components**:
2628
- [React Icons](https://react-icons.github.io/react-icons/)
2729
- [React Lottie Player](https://github.com/LottieFiles/react-lottie-player)
2830
- **Markdown Editor**: [@uiw/react-md-editor](https://uiwjs.github.io/react-md-editor/)
2931

3032
### Backend
33+
3134
- **Runtime**: [Node.js](https://nodejs.org/)
3235
- **Database**: [MongoDB](https://www.mongodb.com/) (with [Mongoose](https://mongoosejs.com/))
3336
- **Authentication**: [NextAuth.js](https://next-auth.js.org/)
3437
- **File Storage**: [Vercel Blob](https://vercel.com/docs/storage/vercel-blob)
3538
- **HTTP Client**: [Axios](https://axios-http.com/)
3639

3740
### DevOps & Testing
41+
3842
- **Testing Framework**: [Jest](https://jestjs.io/) with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
39-
- **Linting & Formatting**:
43+
- **Linting & Formatting**:
4044
- [ESLint](https://eslint.org/)
4145
- [Prettier](https://prettier.io/)
4246

@@ -63,3 +67,19 @@ TechBlog/
6367
├── .github/ # GitHub 워크플로우
6468
└── __test__/ # 테스트 파일
6569
```
70+
71+
## 필요한 환경변수
72+
73+
.env 파일에 필요한 환경변수는 다음과 같다.
74+
75+
```text
76+
1 GITHUB_ID=your_github_client_id
77+
2 GITHUB_SECRET=your_github_client_secret
78+
79+
4 NEXTAUTH_SECRET=your_nextauth_secret
80+
5 NEXTAUTH_URL=http://localhost:3000
81+
6 DB_URI=your_mongodb_connection_string
82+
7 NEXT_PUBLIC_DEPLOYMENT_URL=https://your-deployment-url.com
83+
8 NEXT_PUBLIC_URL=http://localhost:3000
84+
9 BLOB_READ_WRITE_TOKEN=your_vercel_blob_token
85+
```

app/admin/comments/page.tsx

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
import { useSession } from 'next-auth/react';
5+
import { useRouter } from 'next/navigation';
6+
import Link from 'next/link';
7+
import IssueCard from '@/app/entities/admin/comments/IssueCard';
8+
9+
interface GitHubUser {
10+
login: string;
11+
avatar_url: string;
12+
}
13+
14+
interface GitHubComment {
15+
id: number;
16+
user: GitHubUser;
17+
created_at: string;
18+
updated_at: string;
19+
body: string;
20+
html_url: string;
21+
}
22+
23+
interface GitHubIssue {
24+
id: number;
25+
number: number;
26+
title: string;
27+
html_url: string;
28+
state: string;
29+
comments: number;
30+
created_at: string;
31+
updated_at: string;
32+
user: GitHubUser;
33+
body?: string;
34+
}
35+
36+
interface IssueWithComments {
37+
issue: GitHubIssue;
38+
comments: GitHubComment[];
39+
}
40+
41+
const AdminCommentsPage = () => {
42+
const { status } = useSession();
43+
const router = useRouter();
44+
const [issuesWithComments, setIssuesWithComments] = useState<
45+
IssueWithComments[]
46+
>([]);
47+
const [loading, setLoading] = useState(true);
48+
const [error, setError] = useState<string | null>(null);
49+
50+
useEffect(() => {
51+
if (status === 'unauthenticated') {
52+
router.push('/admin');
53+
}
54+
}, [status, router]);
55+
56+
useEffect(() => {
57+
if (status === 'authenticated') {
58+
fetchComments();
59+
}
60+
}, [status]);
61+
62+
const fetchComments = async () => {
63+
try {
64+
setLoading(true);
65+
const response = await fetch('/api/admin/comments');
66+
const data = await response.json();
67+
68+
if (data.success) {
69+
setIssuesWithComments(data.data);
70+
} else {
71+
setError(data.error || '댓글을 불러올 수 없습니다.');
72+
}
73+
} catch (err) {
74+
setError('댓글을 불러오는 중 오류가 발생했습니다.');
75+
console.error(err);
76+
} finally {
77+
setLoading(false);
78+
}
79+
};
80+
81+
if (status === 'loading' || loading) {
82+
return (
83+
<div className="p-6 max-w-7xl mx-auto">
84+
<h1 className="text-3xl font-bold mb-6">댓글 관리</h1>
85+
<div className="text-center py-10">
86+
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
87+
<p className="mt-4 text-gray-600">댓글을 불러오는 중...</p>
88+
</div>
89+
</div>
90+
);
91+
}
92+
93+
if (error) {
94+
return (
95+
<div className="p-6 max-w-7xl mx-auto">
96+
<h1 className="text-3xl font-bold mb-6">댓글 관리</h1>
97+
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
98+
{error}
99+
</div>
100+
</div>
101+
);
102+
}
103+
104+
return (
105+
<div className="p-6 max-w-7xl mx-auto">
106+
<div className="flex justify-between items-center mb-6">
107+
<h1 className="text-3xl font-bold">댓글 관리</h1>
108+
<Link
109+
href="/admin"
110+
className="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-all"
111+
>
112+
대시보드로 돌아가기
113+
</Link>
114+
</div>
115+
116+
{issuesWithComments.length === 0 ? (
117+
<div className="bg-white rounded-lg shadow-md p-8 text-center">
118+
<p className="text-gray-500 text-lg">아직 댓글이 없습니다.</p>
119+
</div>
120+
) : (
121+
<div className="space-y-4">
122+
<div className="bg-blue-50 border border-blue-200 px-4 py-3 rounded-lg mb-4">
123+
<p className="text-blue-800">
124+
<strong>{issuesWithComments.length}</strong>개의 글에 댓글이
125+
달려있습니다.
126+
</p>
127+
</div>
128+
129+
{issuesWithComments.map(({ issue, comments }) => (
130+
<IssueCard key={issue.id} issue={issue} comments={comments} />
131+
))}
132+
</div>
133+
)}
134+
</div>
135+
);
136+
};
137+
138+
export default AdminCommentsPage;

app/admin/page.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import BubbleBackground from '@/app/entities/common/Background/BubbleBackground'
1313
import { useEffect } from 'react';
1414
import useToast from '@/app/hooks/useToast';
1515
import { FaBuffer } from 'react-icons/fa6';
16+
import RecentActivity from '@/app/entities/admin/dashboard/RecentActivity';
17+
import QuickStats from '@/app/entities/admin/dashboard/QuickStats';
18+
import DecryptedText from '../entities/bits/DecryptedText';
1619

1720
const AdminDashboard = () => {
1821
const { data: session } = useSession();
@@ -95,8 +98,22 @@ const AdminDashboard = () => {
9598
<div className="p-6 max-w-7xl mx-auto">
9699
<header className="mb-8 flex justify-between">
97100
<div>
98-
<h1 className="text-3xl font-bold mb-2">관리자 대시보드</h1>
99-
<p className=" text-default">{session.user?.name}님, 환영합니다</p>
101+
<h1 className="text-3xl font-bold mb-2">
102+
<DecryptedText
103+
text="관리자 대시보드"
104+
speed={60}
105+
revealDirection="start"
106+
animateOn="view"
107+
/>
108+
</h1>
109+
<p className=" text-default">
110+
<DecryptedText
111+
text={`${session.user?.name}님, 환영합니다`}
112+
speed={120}
113+
revealDirection="start"
114+
animateOn="view"
115+
/>
116+
</p>
100117
</div>
101118
<button
102119
className="right-0 px-4 py-1 bg-red-500 text-white rounded-md shadow-md hover:bg-red-700 transition-all"
@@ -126,12 +143,8 @@ const AdminDashboard = () => {
126143
</div>
127144

128145
<div className="mt-8 grid grid-cols-1 md:grid-cols-2 gap-6 text-black">
129-
<div className="bg-white p-6 rounded-lg shadow-md">
130-
<h3 className="text-xl font-semibold mb-4">최근 활동</h3>
131-
</div>
132-
<div className="bg-white p-6 rounded-lg shadow-md">
133-
<h3 className="text-xl font-semibold mb-4">빠른 통계</h3>
134-
</div>
146+
<RecentActivity />
147+
<QuickStats />
135148
</div>
136149
</div>
137150
);

0 commit comments

Comments
 (0)