Skip to content

Commit 70051d3

Browse files
authored
feat: 롤링페이퍼 배포된 api로 연결 수정 (#85)
* style:롤링페이퍼 공지 애니메이션 수정 * fix: 롤링페이퍼가 진행되지 않을 때 화면에 보여지지 않도록 처리 * feat: 새로운 롤링페이퍼 생성 api 연동 * feat: 롤링페이퍼 목록 조회 api 연동 * feat: 롤링페이퍼 삭제 api 연동 * feat: 롤링페이퍼 사용여부 변경 api 연동 * feat: 롤링페이퍼 코멘트 목록 조회 api 연동 * feat: 롤링페이퍼 등록 및 삭제 api 연동 - api 응답 구조 수정
1 parent dca90d6 commit 70051d3

File tree

12 files changed

+255
-70
lines changed

12 files changed

+255
-70
lines changed

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default tseslint.config(
3131
...reactHooks.configs.recommended.rules,
3232
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
3333
'@tanstack/query/exhaustive-deps': 'error',
34+
'@typescript-eslint/no-empty-object-type': off,
3435
'import/order': [
3536
'error',
3637
{

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import AdminPage from './pages/Admin';
88
import FilteredLetterManage from './pages/Admin/FilteredLetter';
99
import FilteringManage from './pages/Admin/Filtering';
1010
import ReportManage from './pages/Admin/Report';
11+
import AdminRollingPaper from './pages/Admin/RollingPaper';
1112
import AuthCallbackPage from './pages/Auth';
1213
import Home from './pages/Home';
1314
import Landing from './pages/Landing';
@@ -69,6 +70,7 @@ const App = () => {
6970
<Route path="report" element={<ReportManage />} />
7071
<Route path="badwords" element={<FilteringManage />} />
7172
<Route path="filtered-letter" element={<FilteredLetterManage />} />
73+
<Route path="rolling-paper" element={<AdminRollingPaper />} />
7274
</Route>
7375
</Route>
7476
</Routes>

src/apis/rolling.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,50 @@ export const deleteRollingPaperComment = async (commentId: string | number) => {
3636
throw error;
3737
}
3838
};
39+
40+
export const postNewRollingPaper = async (title: string) => {
41+
try {
42+
const {
43+
data: { data },
44+
} = await client.post('/api/admin/event-posts', { title });
45+
return data;
46+
} catch (error) {
47+
console.error(error);
48+
throw error;
49+
}
50+
};
51+
52+
export const getRollingPaperList = async (): Promise<RollingPaperList> => {
53+
try {
54+
const {
55+
data: { data },
56+
} = await client.get('/api/admin/event-posts');
57+
return data;
58+
} catch (error) {
59+
console.error(error);
60+
throw error;
61+
}
62+
};
63+
64+
export const deleteRollingPaper = async (eventPostId: number | string) => {
65+
try {
66+
const { data } = await client.delete(`/api/admin/event-posts/${eventPostId}`);
67+
return data;
68+
} catch (error) {
69+
console.error(error);
70+
throw error;
71+
}
72+
};
73+
74+
export const patchRollingPaper = async (eventPostId: number | string) => {
75+
try {
76+
const {
77+
data: { data },
78+
} = await client.patch(`/api/admin/event-posts/${eventPostId}/status`);
79+
console.log(data);
80+
return data;
81+
} catch (error) {
82+
console.error(error);
83+
throw error;
84+
}
85+
};

src/components/NoticeRollingPaper.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
11
import { useQuery } from '@tanstack/react-query';
2+
import { useEffect, useRef, useState } from 'react';
23
import { Link } from 'react-router';
34
import { twMerge } from 'tailwind-merge';
45

56
import { getCurrentRollingPaper } from '@/apis/rolling';
67
import { NoticeIcon } from '@/assets/icons';
78

89
const NoticeRollingPaper = () => {
9-
const { data } = useQuery({
10+
const { data, error } = useQuery({
1011
queryKey: ['notice-rolling-paper'],
1112
queryFn: () => getCurrentRollingPaper(),
1213
});
1314

15+
const [activeAnimate, setActiveAnimate] = useState(false);
16+
const containerRef = useRef<HTMLDivElement>(null);
17+
const textRef = useRef<HTMLParagraphElement>(null);
18+
19+
useEffect(() => {
20+
if (data?.title) {
21+
const containerElement = containerRef.current;
22+
const element = textRef.current;
23+
24+
if (containerElement && element) {
25+
const textWidth = element.scrollWidth;
26+
const containerWidth = containerElement.offsetWidth;
27+
28+
if (textWidth > containerWidth) {
29+
const animationDuration = (textWidth / 10) * 0.3;
30+
const totalDuration = Math.max(animationDuration, 10);
31+
document.documentElement.style.setProperty('--marquee-duration', `${totalDuration}s`);
32+
33+
setActiveAnimate(true);
34+
} else {
35+
setActiveAnimate(false);
36+
}
37+
}
38+
}
39+
}, [data?.title]);
40+
1441
const noticeText = data?.title;
1542

43+
if (error || !noticeText) return null;
44+
1645
return (
1746
<Link to={`/board/rolling/${data?.eventPostId}`}>
1847
<article
@@ -23,8 +52,13 @@ const NoticeRollingPaper = () => {
2352
)}
2453
>
2554
<NoticeIcon className="h-6 w-6 shrink-0 text-gray-50" />
26-
<div className="w-full overflow-hidden">
27-
<p className="body-sb animate-marquee whitespace-nowrap">{noticeText}</p>
55+
<div ref={containerRef} className="w-full overflow-hidden whitespace-nowrap">
56+
<p
57+
ref={textRef}
58+
className={twMerge('body-sb inline-block', activeAnimate && 'animate-marquee')}
59+
>
60+
{noticeText}
61+
</p>
2862
</div>
2963
</article>
3064
</Link>

src/pages/Admin/RollingPaper.tsx

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1+
import { useQuery } from '@tanstack/react-query';
12
import { useState } from 'react';
23

3-
import { AddIcon, AlarmIcon, DeleteIcon } from '@/assets/icons';
4+
import { getRollingPaperList } from '@/apis/rolling';
5+
import { AddIcon, AlarmIcon } from '@/assets/icons';
46

57
import AddRollingPaperModal from './components/AddRollingPaperModal';
68
import PageTitle from './components/AdminPageTitle';
9+
import RollingPaperItem from './components/RollingPaperItem';
710
import WrapperFrame from './components/WrapperFrame';
811
import WrapperTitle from './components/WrapperTitle';
912

1013
export default function AdminRollingPaper() {
1114
const [activeModal, setActiveModal] = useState(false);
15+
const { data, isLoading, isSuccess } = useQuery({
16+
queryKey: ['admin-rolling-paper'],
17+
queryFn: getRollingPaperList,
18+
});
1219

1320
return (
1421
<>
@@ -26,55 +33,32 @@ export default function AdminRollingPaper() {
2633
롤링페이퍼 생성
2734
</button>
2835
</section>
29-
<table className="mt-5 table-auto">
30-
<thead>
31-
<tr className="bg-primary-3 border-gray-40 h-14 border-b">
32-
<th className="w-14 text-center">ID</th>
33-
<th className="text-left">제목</th>
34-
<th className="w-30 text-center">쌓인 편지 수</th>
35-
<th className="w-30 text-center">상태</th>
36-
<th></th>
37-
</tr>
38-
</thead>
39-
<tbody>
40-
<tr className="border-gray-40 h-14 border-b">
41-
<td className="w-14 text-center">1</td>
42-
<td className="text-left">
43-
침수 피해를 복구중인 포스코 임직원 분들에게 응원의 메시지를 보내주세요!
44-
</td>
45-
<td className="w-30 text-center">12</td>
46-
<td className="text-center">
47-
<span className="rounded-full border border-amber-500 bg-amber-100/70 px-4 py-1.5 whitespace-nowrap text-amber-500">
48-
진행 중
49-
</span>
50-
</td>
51-
<td></td>
52-
</tr>
53-
<tr className="border-gray-40 h-14 border-b">
54-
<td className="w-14 text-center">2</td>
55-
<td className="text-left">
56-
침수 피해를 복구중인 포스코 임직원 분들에게 응원의 메시지를 보내주세요!
57-
</td>
58-
<td className="w-30 text-center">12</td>
59-
<td className="w-30 px-2 text-center">
60-
<button
61-
type="button"
62-
className="hover:bg-gray-10 text-gray-60 rounded-md px-3 py-1 hover:text-black"
63-
>
64-
진행하기
65-
</button>
66-
</td>
67-
<td>
68-
<button
69-
type="button"
70-
className="text-gray-60 flex items-center justify-center p-1 hover:text-black"
71-
>
72-
<DeleteIcon className="h-5 w-5" />
73-
</button>
74-
</td>
75-
</tr>
76-
</tbody>
77-
</table>
36+
{isLoading && <p className="mt-20 text-center">Loading...</p>}
37+
{isSuccess && (
38+
<>
39+
<table className="mt-5 table-auto">
40+
<thead>
41+
<tr className="bg-primary-3 border-gray-40 h-14 border-b">
42+
<th className="w-14 text-center">ID</th>
43+
<th className="text-left">제목</th>
44+
<th className="w-30 text-center">상태</th>
45+
<th className="w-6"></th>
46+
</tr>
47+
</thead>
48+
<tbody>
49+
{data.content.map((rollingPaper) => (
50+
<RollingPaperItem key={rollingPaper.eventPostId} information={rollingPaper} />
51+
))}
52+
</tbody>
53+
</table>
54+
{data.content.length === 0 && (
55+
<span className="my-10 text-center text-gray-50">
56+
아직 생성된 롤링페이퍼가 없어요
57+
</span>
58+
)}
59+
</>
60+
)}
61+
{/* TODO: 페이지네이션 적용 */}
7862
</WrapperFrame>
7963
</>
8064
);

src/pages/Admin/components/AddRollingPaperModal.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query';
12
import { ChangeEvent, FormEvent, useState } from 'react';
23

4+
import { postNewRollingPaper } from '@/apis/rolling';
35
import ModalOverlay from '@/components/ModalOverlay';
46

57
interface AddRollingPaperModalProps {
@@ -9,6 +11,21 @@ interface AddRollingPaperModalProps {
911
export default function AddRollingPaperModal({ onClose }: AddRollingPaperModalProps) {
1012
const [title, setTitle] = useState('');
1113
const [error, setError] = useState('');
14+
const queryClient = useQueryClient();
15+
16+
const { mutate } = useMutation({
17+
mutationFn: () => postNewRollingPaper(title),
18+
onSuccess: () => {
19+
setTitle('');
20+
setError('');
21+
onClose();
22+
// TODO: 페이지네이션 적용 후, 현재 page에 대한 캐싱 날리는 방식으로 변경
23+
queryClient.invalidateQueries({ queryKey: ['admin-rolling-paper'] });
24+
},
25+
onError: () => {
26+
setError('편지 작성에 실패했어요. 다시 시도해주세요.');
27+
},
28+
});
1229

1330
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
1431
setTitle(e.target.value);
@@ -21,7 +38,7 @@ export default function AddRollingPaperModal({ onClose }: AddRollingPaperModalPr
2138
return;
2239
}
2340

24-
console.log(title);
41+
mutate();
2542
};
2643

2744
return (
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query';
2+
import { AxiosError } from 'axios';
3+
4+
import { deleteRollingPaper, patchRollingPaper } from '@/apis/rolling';
5+
import { DeleteIcon } from '@/assets/icons';
6+
7+
interface RollingPaperItemProps {
8+
information: AdminRollingPaperInformation;
9+
}
10+
11+
export default function RollingPaperItem({ information }: RollingPaperItemProps) {
12+
const queryClient = useQueryClient();
13+
14+
const { mutate: deleteMutate } = useMutation({
15+
mutationFn: () => deleteRollingPaper(information.eventPostId),
16+
onSuccess: () => {
17+
// TODO: 페이지네이션 적용 후, 현재 page에 대한 캐싱 날리는 방식으로 변경
18+
queryClient.invalidateQueries({ queryKey: ['admin-rolling-paper'] });
19+
},
20+
onError: (err) => {
21+
console.error(err);
22+
},
23+
});
24+
25+
const { mutate: toggleStatus } = useMutation({
26+
mutationFn: () => patchRollingPaper(information.eventPostId),
27+
onSuccess: () => {
28+
// TODO: 기존 데이터 수정하는 방식으로 ㄱㄱㄱㄱㄱㄱㄱ
29+
// 일단 임시로 캐싱 날리기
30+
queryClient.invalidateQueries({ queryKey: ['admin-rolling-paper'] });
31+
},
32+
onError: (err: AxiosError<{ code: string; message: string }>) => {
33+
if (err.response?.data.code === 'EVENT-004') {
34+
alert(err.response.data.message);
35+
}
36+
console.error(err);
37+
},
38+
});
39+
40+
// TODO: 진짜 삭제하겠냐고 물어보기
41+
return (
42+
<tr className="border-gray-40 h-14 border-b">
43+
<td className="w-14 text-center">{information.eventPostId}</td>
44+
<td className="text-left">
45+
<div>
46+
{information.used && (
47+
<span className="mr-2 rounded-full border border-amber-500 bg-amber-100/70 px-3 py-0.5 whitespace-nowrap text-amber-500">
48+
진행 중
49+
</span>
50+
)}
51+
{information.title}
52+
</div>
53+
</td>
54+
<td className="text-center">
55+
<button
56+
type="button"
57+
className="hover:bg-gray-10 text-gray-60 rounded-md px-3 py-1 hover:text-black"
58+
onClick={() => toggleStatus()}
59+
>
60+
{information.used ? '중단하기' : '진행하기'}
61+
</button>
62+
</td>
63+
<td className="w-6">
64+
{!information.used && (
65+
<button
66+
type="button"
67+
className="text-gray-60 flex items-center justify-center p-1 hover:text-black"
68+
onClick={() => deleteMutate()}
69+
>
70+
<DeleteIcon className="h-5 w-5" />
71+
</button>
72+
)}
73+
</td>
74+
</tr>
75+
);
76+
}

src/pages/RollingPaper/components/CommentDetailModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const CommentDetailModal = ({ comment, isWriter, onClose, onDelete }: CommentDet
2020

2121
<MemoWrapper className="mt-1 flex max-h-1/2 w-78 overflow-y-auto px-5 text-black">
2222
<div className="z-1 flex flex-col gap-3">
23-
<p className="body-r leading-[26px]">{comment.content}</p>
23+
<p className="body-r leading-[26px] whitespace-pre-wrap">{comment.content}</p>
2424
<p className="body-m place-self-end">From. {comment.zipCode}</p>
2525
</div>
2626
</MemoWrapper>

0 commit comments

Comments
 (0)