+
관리자 코멘트
+
{adminText}
+
경고 안내
따사로운 서비스 이용을 위해, 부적절하다고 판단되는 편지는 반려하고 있어요. 서로를
존중하는 따뜻한 공간을 만들기 위해 협조 부탁드립니다.
+
경고 규칙
1회 경고: 주의 안내
diff --git a/src/pages/Notifications/constants/index.ts b/src/pages/Notifications/constants/index.ts
index 1134831..0b09832 100644
--- a/src/pages/Notifications/constants/index.ts
+++ b/src/pages/Notifications/constants/index.ts
@@ -4,7 +4,8 @@ export const NOTIFICATION_ICON: Record<
string,
React.ComponentType>
> = {
- letter: EnvelopeIcon,
- warning: SirenFilledIcon,
- board: BoardIcon,
+ LETTER: EnvelopeIcon,
+ REPORT: SirenFilledIcon,
+ SHARE: BoardIcon,
+ POSTED: BoardIcon,
};
diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx
index bef3d9c..d2bc5de 100644
--- a/src/pages/Notifications/index.tsx
+++ b/src/pages/Notifications/index.tsx
@@ -1,50 +1,93 @@
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router';
+import { getTimeLines, patchReadNotification, patchReadNotificationAll } from '@/apis/notification';
import PageTitle from '@/components/PageTitle';
import NotificationItem from './components/NotificationItem';
import WarningModal from './components/WarningModal';
-const DUMMY_NOTI = [
- { id: 1, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: false },
- { id: 2, type: 'warning', message: '따숨님, 욕설로 인해 경고를 받으셨어요.', isRead: false },
- { id: 3, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: false },
- { id: 4, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: true },
- { id: 5, type: 'letter', message: '12E31님이 편지를 보냈습니다.', isRead: false },
- { id: 6, type: 'board', message: '12E31님과의 대화가 게시판에 공유되었어요.', isRead: false },
- {
- id: 7,
- type: 'board',
- message: '12E31님과의 게시글에 대한 공유요청을 보냈어요.',
- isRead: false,
- },
-];
-
const NotificationsPage = () => {
+ const navigate = useNavigate();
+
+ const [noti, setNoti] = useState([]);
+
const [isOpenWarningModal, setIsOpenWarningModal] = useState(false);
- const handleClickItem = (type: string) => {
- if (type === 'warning') {
+ const [adminText, setAdmintext] = useState('');
+
+ // MEMO : 편지 데이터 전송중 데이터도 추가될건데 나중에 데이터 추가되면 코드 업데이트 하긔
+ const handleClickItem = (alarmType: string, content?: string | number) => {
+ if (alarmType === 'LETTER') {
+ navigate(`/letter/${content}`);
+ }
+ if (alarmType === 'REPORT') {
setIsOpenWarningModal(true);
+ if (typeof content === 'string') setAdmintext(content);
+ }
+ if (alarmType === 'SHARE') {
+ navigate(`/board/letter/${content}`, { state: { isShareLetterPreview: true } });
+ }
+ if (alarmType === 'POSTED') {
+ navigate(`/board/letter/${content}`);
}
};
+ const handleGetTimeLines = async () => {
+ const res = await getTimeLines();
+ if (res?.status === 200) {
+ console.log(res);
+ setNoti(res.data.data.content);
+ }
+ };
+
+ const handlePatchReadNotification = async (timelineId: number) => {
+ const res = await patchReadNotification(timelineId);
+ if (res?.status !== 200) {
+ console.log('읽음처리 에러 발생');
+ }
+ };
+
+ const handlePatchReadNotificationAll = async () => {
+ const res = await patchReadNotificationAll();
+ if (res?.status !== 200) {
+ console.log('모두 읽음처리 에러 발생');
+ }
+ };
+
+ useEffect(() => {
+ handleGetTimeLines();
+ }, []);
+
return (
<>
- setIsOpenWarningModal(false)} />
+ setIsOpenWarningModal(false)}
+ />
알림
-
diff --git a/src/pages/LetterBoardDetail/index.tsx b/src/pages/LetterBoardDetail/index.tsx
index 3f8292d..f106647 100644
--- a/src/pages/LetterBoardDetail/index.tsx
+++ b/src/pages/LetterBoardDetail/index.tsx
@@ -25,69 +25,85 @@ const LetterBoardDetailPage = ({ confirmDisabled }: ShareLetterPreviewProps) =>
const [isLike, setIsLike] = useState(false);
const isWriter = false;
const [activeReportModal, setActiveReportModal] = useState(false);
+ const sharePostId: string = location.pathname.split('/')[3];
+ // const location = useLocation();
+ const navigate = useNavigate();
+ // const isShareLetterPreview = location.state?.isShareLetterPreview || false;
+ const isShareLetterPreview = false;
+ const [postDetail, setPostDetail] = useState();
+
+ const postLike = async () => {
+ try {
+ const response = await postSharePostLike(sharePostId);
+ if (!response) throw new Error('error while fetching like count');
+ console.log('✅ 편지 좋아요 추가됨:', response);
+ } catch (error) {
+ console.error('❌ 편지 좋아요 추가 중 에러가 발생했습니다', error);
+ throw new Error('편지 좋아요 추가 실패');
+ }
+ };
const handleToggleLike = () => {
setLikeCount((prev) => prev + (isLike ? -1 : 1));
setIsLike((prev) => !prev);
+ postLike();
};
- const location = useLocation();
- const navigate = useNavigate();
+ const handleProposalApproval = async (
+ action: 'approve' | 'reject',
+ shareProposalId: number = location.state?.postDetail?.sharePostId,
+ ) => {
+ try {
+ const result = await postShareProposalApproval(shareProposalId, action);
+ console.log(`✅ 편지 공유 ${action === 'approve' ? '수락' : '거절'}됨:`, result);
- const isShareLetterPreview = location.state?.isShareLetterPreview || false;
- const [postDetail, setPostDetail] = useState();
+ navigate('/');
+ } catch (error) {
+ console.error(error);
+ }
+ };
useEffect(() => {
- const { sharePostId } = location.state.postDetail;
- const fetchPostDetail = async (postId: number) => {
+ const fetchPostDetail = async (postId: string) => {
try {
- console.log('sharePostId:', postId);
-
const data = await getSharePostDetail(postId);
-
setPostDetail(data);
} catch (error) {
console.error('❌ 공유 게시글 상세 조회에 실패했습니다.', error);
}
};
- const fetchLikeCounts = async (postId: number) => {
+ const fetchLikeCounts = async (postId: string) => {
try {
const response = await getSharePostLikeCount(postId);
if (!response) throw new Error('error while fetching like count');
- console.log(response);
- setLikeCount(response.data.likeCount);
+ console.log('✅ 편지 좋아요 갯수:', response);
+ setLikeCount(response.likeCount);
+ setIsLike(response.liked);
} catch (error) {
console.error('❌ 편지 좋아요 갯수를 가져오는 중 에러가 발생했습니다', error);
throw new Error('편지 좋아요 갯수 가져오기 실패');
}
};
- if (location.state?.postDetail) {
- fetchPostDetail(sharePostId);
- fetchLikeCounts(sharePostId);
- } else {
- console.warn('postDetail not found in location.state');
- }
- }, [location.state]);
-
- const handleProposalApproval = async (
- action: 'approve' | 'reject',
- shareProposalId: number = location.state?.postDetail?.sharePostId,
- ) => {
- try {
- const result = await postShareProposalApproval(shareProposalId, action);
- console.log(`✅ 편지 공유 ${action === 'approve' ? '수락' : '거절'}됨:`, result);
-
- navigate('/');
- } catch (error) {
- console.error(error);
- }
- };
+ // if (location.state?.postDetail) {
+ fetchPostDetail(sharePostId);
+ fetchLikeCounts(sharePostId);
+ // } else {
+ // console.warn('postDetail not found in location.state');
+ // }
+ // }, [location.state]);
+ }, []);
return (
<>
- {activeReportModal && setActiveReportModal(false)} />}
+ {activeReportModal && (
+ setActiveReportModal(false)}
+ />
+ )}
{
try {
const response = await getMySharePostList();
if (!response) throw new Error('게시글 목록을 불러오는데 실패했습니다.');
- console.log(response);
- return response.data;
+ console.log('myPostList', response);
+ return response.data as SharePost[];
} catch (e) {
console.error(e);
}
@@ -43,19 +43,20 @@ const MyBoardPage = () => {
내가 올린 게시물
{isLoading ? (
loading
- ) : (
+ ) : postLists && postLists?.length > 0 ? (
- {postLists.map((item, index) => (
+ {postLists?.map((item, index) => (
))}
+ ) : (
+ 게시글이 없습니다.
)}
diff --git a/src/stores/myPageStore.ts b/src/stores/myPageStore.ts
index 61b34ac..59730d0 100644
--- a/src/stores/myPageStore.ts
+++ b/src/stores/myPageStore.ts
@@ -1,6 +1,6 @@
import { create } from 'zustand';
-import { fetchMyPageInfo } from '@/apis/mypage';
+import { fetchMyPageInfo } from '@/apis/myPage';
interface MyPageDataStore {
zipCode: string;
From 62d67cd5234fe5fe6e6ed17a6b36485ad0f838c9 Mon Sep 17 00:00:00 2001
From: wldnjs990 <139528356+wldnjs990@users.noreply.github.com>
Date: Fri, 7 Mar 2025 22:29:31 +0900
Subject: [PATCH 05/37] =?UTF-8?q?feat=20:=20=EC=9E=AC=EC=82=AC=EC=9A=A9=20?=
=?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80?=
=?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84=20(#92)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/pages/Admin/Report.tsx | 58 ++++--------
.../Admin/components/PagenationNavigation.tsx | 93 +++++++++++++++++++
2 files changed, 111 insertions(+), 40 deletions(-)
create mode 100644 src/pages/Admin/components/PagenationNavigation.tsx
diff --git a/src/pages/Admin/Report.tsx b/src/pages/Admin/Report.tsx
index 0838c4c..dc52d8c 100644
--- a/src/pages/Admin/Report.tsx
+++ b/src/pages/Admin/Report.tsx
@@ -5,6 +5,7 @@ import { AlarmIcon } from '@/assets/icons';
import AdminPageTitle from './components/AdminPageTitle';
import ListHeaderFrame from './components/ListHeaderFrame';
+import PagenationNavigation from './components/PagenationNavigation';
import ReportDetailModal from './components/ReportDetailModal';
import ReportHandlingModal from './components/ReportHandlingModal';
import ReportListItem from './components/ReportListItem';
@@ -19,28 +20,33 @@ export default function ReportManage() {
currentPage: '1',
totalPages: '0',
});
+
const [selectedReport, setSelectReport] = useState(null);
const [selectedReportId, setSelectedReportId] = useState(null);
- // const [allReports, setAllReports] = useState();
-
const [reportQueryString, setReportQueryString] = useState({
reportType: null,
status: 'PENDING',
page: '1',
- size: '3',
+ size: '2',
});
+
const handleGetReports = async (reportQueryString: ReportQueryString) => {
const res = await getReports(reportQueryString);
if (res?.status === 200) {
- console.log(res.data.data.content);
- setReports(res.data.data.content);
+ const data = res.data.data;
+ setReports(data.content);
setReportPages(() => ({
- currentPage: res.data.data.currentPage,
- totalPages: res.data.data.totalPages,
+ currentPage: data.currentPage,
+ totalPages: data.totalPages,
}));
}
};
+
+ const handleNowPage = (page: string) => {
+ setReportQueryString((cur) => ({ ...cur, page: page }));
+ };
+
useEffect(() => {
handleGetReports(reportQueryString);
}, [reportQueryString]);
@@ -68,39 +74,11 @@ export default function ReportManage() {
setSelectReport={setSelectReport}
/>
))}
-
-
- {
- const nowPage = Number(reportQueryString.page);
- if (nowPage > 1) {
- const newPage = (nowPage - 1).toString();
- setReportQueryString((cur) => ({ ...cur, page: newPage }));
- }
- }}
- >
- 뒤
-
-
- {reportPages.currentPage}/{reportPages.totalPages}
-
- {
- const nowPage = Number(reportQueryString.page);
- const totalPage = Number(reportPages.totalPages);
- if (nowPage < totalPage) {
- const newPage = (nowPage + 1).toString();
- console.log(newPage);
- setReportQueryString((cur) => ({ ...cur, page: newPage }));
- }
- }}
- >
- 앞
-
-
-
+
{detailModalOpen && (
diff --git a/src/pages/Admin/components/PagenationNavigation.tsx b/src/pages/Admin/components/PagenationNavigation.tsx
new file mode 100644
index 0000000..350ffdd
--- /dev/null
+++ b/src/pages/Admin/components/PagenationNavigation.tsx
@@ -0,0 +1,93 @@
+import { useState } from 'react';
+import { twMerge } from 'tailwind-merge';
+
+interface PagenationNavigation {
+ totalPage: number;
+ buttonLength: number;
+ handlePageNumberButtonClick: (page: string) => void;
+}
+export default function PagenationNavigation({
+ totalPage,
+ buttonLength,
+ handlePageNumberButtonClick,
+}: PagenationNavigation) {
+ const totalSection = Math.ceil(totalPage / buttonLength) - 1;
+ const [nowSection, setNowSection] = useState(0);
+ const [nowPageNumberAt, setNowPageNumberAt] = useState(1);
+
+ // 네비게이션 시작점, 끝점
+ const navigationRange = {
+ start: nowSection * buttonLength + 1,
+ end: nowSection * buttonLength + buttonLength,
+ };
+
+ // 페이지 버튼 배열
+ const pageNumberButtonArray = Array.from(
+ { length: navigationRange.end - navigationRange.start + 1 },
+ (_, index) => navigationRange.start + index,
+ );
+
+ // 페이지 버튼 클릭시 해당 번호값이 파라미터에 담김
+ const handlePageButtonClick = (page: number) => {
+ const pageString = page.toString();
+ handlePageNumberButtonClick(pageString);
+ setNowPageNumberAt(page);
+ };
+
+ const handlePrevButtonClick = () => {
+ if (nowSection > 0) {
+ const prev = (nowSection - 1) * buttonLength + buttonLength;
+ setNowSection((cur) => cur - 1);
+ handlePageButtonClick(prev);
+ }
+ };
+
+ const handleNextButtonClick = () => {
+ if (nowSection < totalSection) {
+ const next = (nowSection + 1) * buttonLength + 1;
+ setNowSection((cur) => cur + 1);
+ handlePageButtonClick(next);
+ }
+ };
+
+ const buttonStyle = 'border bg-white px-2 py-1 disabled:bg-gray-20';
+
+ return (
+
+
+ {
+ handlePrevButtonClick();
+ }}
+ >
+ prev
+
+ {pageNumberButtonArray.map((num) => {
+ if (totalPage < num) return null;
+ return (
+ {
+ handlePageButtonClick(num);
+ }}
+ >
+ {num}
+
+ );
+ })}
+ = totalSection}
+ onClick={() => {
+ handleNextButtonClick();
+ }}
+ >
+ next
+
+
+
+ );
+}
From 266d0e5483ab5c5a3e49a3d21b3f0510ceec350e Mon Sep 17 00:00:00 2001
From: "Seungyeon Han (Tiffany)"
<125551867+tiffanyhansy@users.noreply.github.com>
Date: Fri, 7 Mar 2025 23:10:23 +0900
Subject: [PATCH 06/37] =?UTF-8?q?feat:=20=ED=8E=B8=EC=A7=80=20=EA=B3=B5?=
=?UTF-8?q?=EC=9C=A0=20=EC=9A=94=EC=B2=AD=20=EC=88=98=EC=8B=A0=20=EC=A1=B0?=
=?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#90)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: 편지 공유 요청 수신 조회 기능 구현
* refactor: incomingLetters.ts 코드 리팩토링
* refactor: incomingLettersStore.ts에서 필요 없는 필드 정리
* feat: 오고 있는 편지 도착까지 걸리는 시간 카운트다운 기능 구현
* design: 오고 있는 편지 모달에서 데이터가 없을 때 대체 텍스트 추가
* design: 임시저장된 편지 모달에서 데이터가 없을 때 대체 텍스트 추가
* design: 편지 공유 요청 수신 조회 모달에서 데이터가 없을 때 대체 텍스트 추가
---
src/apis/incomingLetters.ts | 10 +--
src/apis/share.ts | 24 ++++++-
src/pages/Home/components/ShowDraftModal.tsx | 30 ++++----
.../components/ShowIncomingLettersModal.tsx | 22 +++---
.../Home/components/ShowShareAccessModal.tsx | 70 ++++++++-----------
src/stores/incomingLettersStore.ts | 32 +++++----
6 files changed, 105 insertions(+), 83 deletions(-)
diff --git a/src/apis/incomingLetters.ts b/src/apis/incomingLetters.ts
index adde539..66a865e 100644
--- a/src/apis/incomingLetters.ts
+++ b/src/apis/incomingLetters.ts
@@ -1,13 +1,9 @@
import client from './client';
-export const getIncomingLetters = async (token: string) => {
+export const getIncomingLetters = async () => {
try {
- const { data } = await client.get('/api/letters?status=delivery', {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- });
- console.log('불러온 데이터', data);
+ const { data } = await client.get('/api/letters?status=delivery');
+ console.log('오고있는 편지 데이터', data);
return data;
} catch (error) {
console.error('❌오고 있는 편지 목록을 불러오던 중 에러 발생', error);
diff --git a/src/apis/share.ts b/src/apis/share.ts
index 6b60bbf..15c97d2 100644
--- a/src/apis/share.ts
+++ b/src/apis/share.ts
@@ -21,7 +21,7 @@ export interface SharePost {
letters: ShareLetter[];
}
-// 페이징 포함
+// 공유 게시글 목록 조회 - 페이징 포함
export interface SharePostResponse {
content: SharePost[];
currentPage: number;
@@ -30,6 +30,15 @@ export interface SharePostResponse {
totalPages: number;
}
+// 편지 공유 요청 수신 조회
+export interface ShareProposal {
+ shareProposalId: number;
+ requesterZipCode: string;
+ recipientZipCode: string;
+ message: string;
+ status: 'REJECTED' | 'APPROVED' | 'PENDING';
+}
+
// 편지 공유 수락 / 거절
export interface SharePostApproval {
shareProposalId: number;
@@ -84,6 +93,19 @@ export const postShareProposals = async (
}
};
+// 편지 공유 요청 수신 조회
+export const getShareProposalList = async () => {
+ try {
+ const response = await client.get('/api/share-proposals/inbox');
+ console.log(`🌟공유 요청 목록`, response.data);
+
+ return response.data.data;
+ } catch (error) {
+ console.error('❌ 편지 공유 요청을 조회하던 중 에러가 발생했습니다', error);
+ throw error;
+ }
+};
+
// 편지 공유 수락 / 거절
export const postShareProposalApproval = async (
shareProposalId: number,
diff --git a/src/pages/Home/components/ShowDraftModal.tsx b/src/pages/Home/components/ShowDraftModal.tsx
index 3fb7257..5be9e9e 100644
--- a/src/pages/Home/components/ShowDraftModal.tsx
+++ b/src/pages/Home/components/ShowDraftModal.tsx
@@ -60,22 +60,26 @@ const ShowDraftModal = ({ onClose }: ShowDraftModalProps) => {
로그아웃 시 임시 저장된 편지는 사라집니다
- {draftLetters.map((draft) => (
-
handleNavigation(draft.letterId)}
- >
-
{draft.title}
+ {draftLetters.length > 0 ? (
+ draftLetters.map((draft) => (
handleDeleteDraftLetters(draft.letterId)}
+ className="text-gray-80 body-m flex h-10 w-full items-center justify-between gap-1 rounded-lg bg-white p-3"
+ key={draft.letterId}
+ // onClick={() => handleNavigation(draft.letterId)}
>
-
+
{draft.title}
+
handleDeleteDraftLetters(draft.letterId)}
+ >
+
+
-
- ))}
+ ))
+ ) : (
+
작성 중인 편지가 없어요
+ )}
diff --git a/src/pages/Home/components/ShowIncomingLettersModal.tsx b/src/pages/Home/components/ShowIncomingLettersModal.tsx
index 219015b..27513c9 100644
--- a/src/pages/Home/components/ShowIncomingLettersModal.tsx
+++ b/src/pages/Home/components/ShowIncomingLettersModal.tsx
@@ -29,15 +29,19 @@ const ShowIncomingLettersModal = ({ onClose }: ShowIncomingLettersModalProps) =>
시간은 실제 시간을 기반으로 책정됩니다.