Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
95 changes: 90 additions & 5 deletions src/apis/share.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
import client from './client';

//공유 게시글 상세 페이지 편지
interface Letter {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제 타입이랑 겹치진 않았지만 앞에 형용사? 같은게 붙어있으면 좋을거 같아용

Copy link
Collaborator Author

@tifsy tifsy Mar 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Letter -> ShareLetter 으로 수정했습니다!

id: number;
content: string;
writerZipCode: string;
receiverZipCode: string;
}

// 공유 게시글 목록 조회 타입
export interface SharePost {
writerZipCode: number;
receiverZipCode: number;
content: string;
createdAt: string;
active: boolean;
sharePostId: number;
sharePostContent: string;
letters: Letter[];
}

// 페이징 포함
export interface SharePostResponse {
content: SharePost[];
currentPage: number;
size: number;
totalElements: number;
totalPages: number;
}

// 편지 공유 수락 / 거절
export interface SharePostApproval {
shareProposalId: number;
status: 'APPROVED' | 'REJECTED';
sharePostId: number;
}

// 공유 게시글 목록 조회
export const getSharePostList = async (
page: number = 1,
size: number = 10,
): Promise<SharePostResponse> => {
try {
const response = await client.get('/api/share-posts', {
params: { page, size },
});
console.log(`🌟공유 게시글 목록`, response.data);

return response.data;
} catch (error) {
console.error('❌ 편지 공유 게시글 목록을 조회하던 중 에러가 발생했습니다', error);
throw new Error('편지 공유 게시글 목록 조회 실패');
}
};

// 공유 게시글 상세 조회
export const getSharePostDetail = async (sharePostId: number): Promise<SharePost> => {
try {
const response = await client.get(`/api/share-posts/${sharePostId}`);
console.log(`🔥공유 게시글 상세 데이터`, response.data);
return response.data.data;
} catch (error) {
console.error('❌ 편지 공유 게시글을 상세 조회하던 중 에러가 발생했습니다', error);
throw new Error('편지 공유 게시글 상세 조회 실패');
}
};

// 공유 요청 보내기
export const postShareProposals = async (
letterIds: number[],
requesterId: number,
Expand All @@ -9,13 +76,31 @@ export const postShareProposals = async (
try {
const response = await client.post('/api/share-proposals', {
letterIds: letterIds,
requesterId: requesterId,
recipientId: recipientId,
message: message,
requesterId,
recipientId,
message,
});
if (!response) throw new Error('error while fetching mailbox data');
return response;
return response.data;
} catch (error) {
console.error('❌ 공유 요청 보내기 중 에러가 발생했습니다', error);
throw new Error('공유 요청 실패');
}
};

// 편지 공유 수락 / 거절
export const postShareProposalApproval = async (
shareProposalId: number,
action: 'approve' | 'reject',
): Promise<SharePostApproval> => {
try {
const response = await client.patch(`/api/share-proposal/${shareProposalId}/${action}`);
return response.data;
} catch (error) {
console.error(error);
console.error(
`❌ 편지 공유 ${action === 'approve' ? '수락' : '거절'} 중 에러가 발생했습니다`,
error,
);
throw new Error(`편지 공유 ${action === 'approve' ? '수락' : '거부'} 실패`);
}
};
2 changes: 1 addition & 1 deletion src/pages/Home/components/GoToLetterBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import goToLetterBoard from '@/assets/images/go-to-letter-board.png';

const GoToLetterBoard = () => {
return (
<div className="absolute bottom-48 left-[calc(var(--vh)*36)] z-9 flex w-full">
<div className="absolute bottom-48 left-[calc(var(--vh)*33)] z-9 flex w-full">
<div className="text-left">
<p className="text-gray-60 body-r mb-1 ml-2">게시판</p>
<Link to="/board/letter">
Expand Down
9 changes: 5 additions & 4 deletions src/pages/Home/components/GoToLetterBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { Link } from 'react-router';

import goToLetterBoxNewLetters from '@/assets/images/go-to-letter-box-new-letters.png';
import goToLetterBox from '@/assets/images/go-to-letter-box.png';
import { useIncomingLettersStore } from '@/stores/incomingLettersStore';

const GoToLetterBox = () => {
//TODO : hasNewLetters 전역으로 상태 관리하기
const hasNewLetters = true;
const { arrivedCount } = useIncomingLettersStore();

return (
<div className="absolute bottom-10 left-5 z-9 flex w-fit">
<div className="absolute bottom-15 left-5 z-9 flex">
<div className="text-left">
<p className="text-gray-60 body-r mb-1 ml-2">내 편지함</p>
<Link to="/letter/box">
<img
src={hasNewLetters ? goToLetterBoxNewLetters : goToLetterBox}
src={arrivedCount ? goToLetterBoxNewLetters : goToLetterBox}
alt="go to letter box"
className="w-[206.5px]"
/>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/components/HomeRight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const HomeRight = () => {
const { arrivedCount, fetchIncomingLetters } = useIncomingLettersStore();
useEffect(() => {
fetchIncomingLetters();
}, []);
}, [fetchIncomingLetters]);

return (
<div className="flex h-screen w-full max-w-150 min-w-[300px] flex-shrink-0 grow snap-start flex-col items-center overflow-x-hidden pt-5">
Expand Down
8 changes: 1 addition & 7 deletions src/pages/Home/components/NewLetterModal.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { useEffect } from 'react';

import { useIncomingLettersStore } from '@/stores/incomingLettersStore';

//TODO: 내 편지함 상세 조회에서 해당 편지를 조회하면 arrivedCount가 1 감소하도록
const NewLetterModal = () => {
const { arrivedCount, fetchIncomingLetters } = useIncomingLettersStore();

useEffect(() => {
fetchIncomingLetters();
}, []);
const { arrivedCount } = useIncomingLettersStore();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거 저번에 민하님께서 말씀하셨던 내용인데
이렇게 비구조할당으로 전역변수를 가져오면 저 store에 있는 객체를 전부 다 가져오는거여서 다른 컴포넌트에서 store변수가 변경되어도 이 컴포넌트가 렌더링이 되는 문제가 있어요!
그래서
const arrivedCount = useIncomingLettersStore(state => state.arrivedCount)
이렇게 필요한 변수만 개별로 불러오는게 권장된다고 하네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 selector으로 필요한 값만 가져오는 게 좋겠네요! 수정했습니다 🙆🏻‍♀️


return (
<p className="text-gray-60 body-b absolute top-30 mb-10 w-fit animate-pulse rounded-full bg-white px-6 py-4">
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Home/components/ShowIncomingLettersModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router';

import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper';
import ModalOverlay from '@/components/ModalOverlay';
import { useIncomingLettersStore } from '@/stores/incomingLettersStore';
Expand All @@ -22,7 +23,7 @@ const ShowIncomingLettersModal = ({ onClose }: ShowIncomingLettersModalProps) =>

useEffect(() => {
fetchIncomingLetters();
});
}, []);

return (
<ModalOverlay closeOnOutsideClick onClose={onClose}>
Expand Down
50 changes: 33 additions & 17 deletions src/pages/Home/components/ShowShareAccessModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router';

import { getSharePostDetail, getSharePostList } from '@/apis/share';
import { SharePostResponse } from '@/apis/share';
import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper';
import ModalOverlay from '@/components/ModalOverlay';

Expand All @@ -9,21 +11,35 @@ interface ShowShareAccessModalProps {
onClose: () => void;
}

const DUMMY_SHARE_ACCESS = [
{ id: 1, zip_code: '235EA' },
{ id: 2, zip_code: '711PO' },
{ id: 3, zip_code: '105CF' },
{ id: 4, zip_code: '299EB' },
];

const ShowShareAccessModal = ({ onClose }: ShowShareAccessModalProps) => {
const navigate = useNavigate();

const handleNavigation = (accessId: number) => {
navigate(`/board/letter/${accessId}`, {
state: { isShareLetterPreview: true },
});
const [sharePosts, setSharePosts] = useState<SharePostResponse>();

useEffect(() => {
const fetchPosts = async () => {
try {
const data = await getSharePostList(1, 10);
setSharePosts(data);
} catch (error) {
console.error('❌ 게시글 목록을 불러오는 데 실패했습니다.', error);
}
};

fetchPosts();
}, []);

const handleNavigation = async (sharePostId: number) => {
try {
const postDetail = await getSharePostDetail(sharePostId);
navigate(`/board/letter/${sharePostId}`, {
state: { postDetail, isShareLetterPreview: true },
});
} catch (error) {
console.error('❌ 게시글 상세 페이지로 이동하는 데에 실패했습니다.', error);
}
};

return (
<ModalOverlay closeOnOutsideClick onClose={onClose}>
<div className="flex h-full flex-col items-center justify-center">
Expand All @@ -39,14 +55,14 @@ const ShowShareAccessModal = ({ onClose }: ShowShareAccessModalProps) => {
허락 여부를 체크해주세요!
</p>
</div>
<div className="mt-6 flex w-[251px] flex-col gap-[10px]">
{DUMMY_SHARE_ACCESS.map((access) => (
<div className="mt-6 flex max-h-60 min-h-auto w-[251px] flex-col gap-[10px] overflow-y-scroll [&::-webkit-scrollbar]:hidden">
{sharePosts?.content.map((post) => (
<button
className="text-gray-80 body-m flex h-10 w-full items-center justify-between gap-1 rounded-lg bg-white p-3"
key={access.id}
onClick={() => handleNavigation(access.id)}
key={post.sharePostId}
onClick={() => handleNavigation(post.sharePostId)}
>
<p>{access.zip_code}님의 공유 요청</p>
<p>{post.writerZipCode}님의 공유 요청</p>
</button>
))}
</div>
Expand Down
14 changes: 7 additions & 7 deletions src/pages/LetterBoardDetail/components/Letter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import MemoWrapper from '@/components/MemoWrapper';

interface LetterProps {
letter: {
receiver: string;
receiverZipCode: string;
content: string;
sender: string;
writerZipCode: string;
};
isSender?: boolean;
isWriter?: boolean;
}

const Letter = ({ letter, isSender = false }: LetterProps) => {
const Letter = ({ letter, isWriter = false }: LetterProps) => {
return (
<MemoWrapper isSender={isSender}>
<MemoWrapper isSender={isWriter}>
<div className="flex flex-col gap-2 text-black">
<p className="body-sb">To. {letter.receiver}</p>
<p className="body-sb">To. {letter.receiverZipCode}</p>
<p className="body-r leading-[26px] whitespace-pre-wrap">{letter.content}</p>
<p className="body-m place-self-end">From. {letter.sender}</p>
<p className="body-m place-self-end">From. {letter.writerZipCode}</p>
</div>
</MemoWrapper>
);
Expand Down
Loading