diff --git a/src/apis/client.ts b/src/apis/client.ts index c2b5f85..d2c900a 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -1,5 +1,21 @@ import axios from 'axios'; -export const client = axios.create({ +const client = axios.create({ baseURL: import.meta.env.VITE_API_URL, }); + +// client.interceptors.request.use( +// (config) => { +// const token = localStorage.getItem('authToken'); +// if (token) { +// config.headers['Authorization'] = `Bearer ${token}`; +// } +// return config; +// }, +// (error) => { +// //TODO: 에러처리 +// return Promise.reject(error); +// }, +// ); + +export default client; diff --git a/src/apis/letterDetail.ts b/src/apis/letterDetail.ts index cefd391..7f0b061 100644 --- a/src/apis/letterDetail.ts +++ b/src/apis/letterDetail.ts @@ -1,4 +1,4 @@ -import { client } from './client'; +import client from './client'; const getLetter = async ( letterId: string, diff --git a/src/apis/mailBox.ts b/src/apis/mailBox.ts new file mode 100644 index 0000000..7c8b120 --- /dev/null +++ b/src/apis/mailBox.ts @@ -0,0 +1,31 @@ +import client from './client'; + +export const getMailbox = async () => { + try { + const response = await client.get('/api/mailbox'); + if (!response) throw new Error('error while fetching mailbox data'); + return response.data; + } catch (error) { + console.error(error); + } +}; + +export const getMailboxDetail = async (id: number) => { + try { + const response = await client.get(`/api/mailbox/${id}`); + if (!response) throw new Error('error while fetching mailbox detail data'); + return response.data; + } catch (error) { + console.error(error); + } +}; + +export const postMailboxDisconnect = async (id: number) => { + try { + const response = await client.post(`/api/mailbox/${id}/disconnect`); + if (!response) throw new Error('error while disconnecting mailbox'); + return response; + } catch (error) { + console.error(error); + } +}; diff --git a/src/apis/share.ts b/src/apis/share.ts new file mode 100644 index 0000000..447037a --- /dev/null +++ b/src/apis/share.ts @@ -0,0 +1,21 @@ +import client from './client'; + +export const postShareProposals = async ( + letterIds: number[], + requesterId: number, + recipientId: number, + message: string, +) => { + try { + const response = await client.post('/api/share-proposals', { + letterIds: letterIds, + requesterId: requesterId, + recipientId: recipientId, + message: message, + }); + if (!response) throw new Error('error while fetching mailbox data'); + return response; + } catch (error) { + console.error(error); + } +}; diff --git a/src/apis/write.ts b/src/apis/write.ts index b8e950a..bcecde9 100644 --- a/src/apis/write.ts +++ b/src/apis/write.ts @@ -1,4 +1,4 @@ -import { client } from './client'; +import client from './client'; const postLetter = async ( data: LetterRequest, diff --git a/src/assets/images/window-disabled.png b/src/assets/images/window-disabled.png new file mode 100644 index 0000000..b3d2ca6 Binary files /dev/null and b/src/assets/images/window-disabled.png differ diff --git a/src/pages/Admin/Report.tsx b/src/pages/Admin/Report.tsx index 36b8040..48f85bb 100644 --- a/src/pages/Admin/Report.tsx +++ b/src/pages/Admin/Report.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; -import { client } from '@/apis/client'; +import client from '@/apis/client'; import { AlarmIcon } from '@/assets/icons'; import DetailFrame from './components/DetailFrame'; diff --git a/src/pages/LetterBoardDetail/index.tsx b/src/pages/LetterBoardDetail/index.tsx index 35abe1c..137eb67 100644 --- a/src/pages/LetterBoardDetail/index.tsx +++ b/src/pages/LetterBoardDetail/index.tsx @@ -1,14 +1,13 @@ import { useState } from 'react'; import { useLocation } from 'react-router'; - import { twMerge } from 'tailwind-merge'; +import BlurImg from '@/assets/images/landing-blur.png'; import ReportModal from '@/components/ReportModal'; import Header from './components/Header'; import Letter from './components/Letter'; -import BlurImg from '@/assets/images/landing-blur.png'; const DUMMY_LETTER = { receiver: '12E21', content: diff --git a/src/pages/LetterBox/components/LetterBoxItem.tsx b/src/pages/LetterBox/components/LetterBoxItem.tsx index f626837..8f7d13e 100644 --- a/src/pages/LetterBox/components/LetterBoxItem.tsx +++ b/src/pages/LetterBox/components/LetterBoxItem.tsx @@ -1,7 +1,7 @@ -import { Link } from 'react-router'; +import { useNavigate } from 'react-router'; import { twMerge } from 'tailwind-merge'; - interface LetterBoxItemProps { + boxId: number; zipCode: string; letterCount: number; isChecked?: boolean; @@ -9,46 +9,58 @@ interface LetterBoxItemProps { } const LetterBoxItem = ({ + boxId, zipCode, letterCount, isChecked = false, isClosed = false, }: LetterBoxItemProps) => { + const navigate = useNavigate(); + const handleClickItem = (id: number) => { + navigate(`${id}`, { + state: { + id, + zipCode, + isClosed, + }, + }); + }; return ( - -
-
-

handleClickItem(boxId)} + > +

+

+ {zipCode} +

+ {isClosed ? ( +
+ ) : ( +
- {zipCode} -

- {isClosed ? ( -
- ) : ( -
-

{letterCount}통

-
- )} -
-
-
-
-
-
-
- +

{letterCount}통

+ + )} + +
+
+
+
+
+ ); }; diff --git a/src/pages/LetterBox/index.tsx b/src/pages/LetterBox/index.tsx index 8f8d328..a7aa199 100644 --- a/src/pages/LetterBox/index.tsx +++ b/src/pages/LetterBox/index.tsx @@ -1,39 +1,81 @@ +import { useEffect, useState } from 'react'; + +import { getMailbox } from '@/apis/mailBox'; import DoorImg from '@/assets/images/door.png'; +import ClosedWindowImg from '@/assets/images/window-disabled.png'; import PageTitle from '@/components/PageTitle'; import { chunkBox } from '@/utils/chunkBox'; import LetterBoxItem from './components/LetterBoxItem'; -const DUMMY_COUNT = 200; - +interface LetterBoxData { + letterMatchingId: number; + oppositeZipCode: string; + active: boolean; + oppositeRead: boolean; + // totalLetters: number; +} const LetterBoxPage = () => { + const [letterBox, setLetterBox] = useState([]); + + const fetchMailLists = async () => { + try { + const response = await getMailbox(); + if (!response) throw new Error(); + const data: LetterBoxData[] = response.data; + // 정렬? + setLetterBox(data); + } catch (error) { + console.error(error); + } + }; + useEffect(() => { + fetchMailLists(); + }, []); + return (
내 편지함

- 나와 연락한 사람들 {DUMMY_COUNT} + 나와 연락한 사람들 {letterBox?.length}

- {chunkBox( - Array.from({ length: 12 }).map((_, index) => ( - - )), - ).map((row, index) => ( -
- {row} -
- ))} + {letterBox.length > 0 ? ( + chunkBox( + letterBox.map((data: LetterBoxData, index) => ( + + )), + ).map((row, index) => + row.length === 3 ? ( +
+ {row} +
+ ) : ( +
+ {row} + 닫힌 문 이미지 + {row.length === 1 && ( + 닫힌 문 이미지 + )} +
+ ), + ) + ) : ( +

아직 주고 받은 편지가 없어요

+ )}
- + 닫힌 문 이미지 출입문 이미지 - + 닫힌 문 이미지
diff --git a/src/pages/LetterBoxDetail/components/InformationTooltip.tsx b/src/pages/LetterBoxDetail/components/InformationTooltip.tsx index eacfd3c..820652f 100644 --- a/src/pages/LetterBoxDetail/components/InformationTooltip.tsx +++ b/src/pages/LetterBoxDetail/components/InformationTooltip.tsx @@ -24,7 +24,7 @@ const InformationTooltip = () => {
diff --git a/src/pages/LetterBoxDetail/components/LetterPreview.tsx b/src/pages/LetterBoxDetail/components/LetterPreview.tsx index 4dbf628..1650c97 100644 --- a/src/pages/LetterBoxDetail/components/LetterPreview.tsx +++ b/src/pages/LetterBoxDetail/components/LetterPreview.tsx @@ -1,4 +1,4 @@ -import { Link } from 'react-router'; +import { useNavigate } from 'react-router'; import ListItemWrapper from '@/components/ListItemWrapper'; @@ -10,6 +10,8 @@ interface LetterPreviewProps { checked: boolean; isShareMode?: boolean; onToggle: () => void; + isClosed: boolean; + zipCode: string; } const LetterPreview = ({ id, @@ -19,7 +21,22 @@ const LetterPreview = ({ checked, isShareMode = false, onToggle, + isClosed, + zipCode, }: LetterPreviewProps) => { + // 차단된 편지인경우 편지 보내기 disable + const navigate = useNavigate(); + + const handleItemClick = (id: number) => { + navigate(`/letter/${id}`, { + state: { + id, + isClosed, + zipCode, + }, + }); + }; + if (isShareMode) return ( @@ -41,12 +58,10 @@ const LetterPreview = ({ ); return ( - - -

{date}

-

{title}

-
- + handleItemClick(id)}> +

{date}

+

{title}

+
); }; diff --git a/src/pages/LetterBoxDetail/index.tsx b/src/pages/LetterBoxDetail/index.tsx index d7c484b..7962789 100644 --- a/src/pages/LetterBoxDetail/index.tsx +++ b/src/pages/LetterBoxDetail/index.tsx @@ -1,5 +1,8 @@ -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router'; +import { getMailboxDetail, postMailboxDisconnect } from '@/apis/mailBox'; +import { postShareProposals } from '@/apis/share'; import ConfirmModal from '@/components/ConfirmModal'; import MessageModal from '@/components/MessageModal'; import PageTitle from '@/components/PageTitle'; @@ -7,48 +10,43 @@ import PageTitle from '@/components/PageTitle'; import InformationTooltip from './components/InformationTooltip'; import LetterPreview from './components/LetterPreview'; -const DUMMY_ZIP_CODE = '12E12'; -const DUMMY_LETTER_LIST = [ - { - id: 1, - date: '2025.01.01', - title: '이건 받은 편지 입니다.이건 받은 편지 입니다.이건 받은 편지 입니다.', - isSend: false, - }, - { - id: 2, - date: '2025.01.01', - title: '이건 보낸 편지입니다.', - isSend: true, - }, - { - id: 3, - date: '2025.01.01', - title: '이건 받은 편지 입니다.이건 받은 편지 입니다.이건 받은 편지 입니다.', - isSend: false, - }, - { - id: 4, - date: '2025.01.01', - title: '이건 받은 편지 입니다.이건 받은 편지 입니다.이건 받은 편지 입니다.', - isSend: false, - }, - { - id: 5, - date: '2025.01.01', - title: '이건 보낸 편지입니다.', - isSend: true, - }, -]; - const LetterBoxDetailPage = () => { - //const { id } = useParams(); + interface MailBoxDetailProps { + letterId: number; + title: string; + myLetter: boolean; + active: boolean; + // createdAt: date; + } + + const location = useLocation(); + const userInfo = { ...location.state }; + + const [mailLists, setMailLists] = useState([]); + const [isShareMode, setShareMode] = useState(false); - const [isOpenUnconnectModal, setIsOpenUnconnectModal] = useState(false); + const [isOpenDisConnectModal, setIsOpenDisConnectModal] = useState(false); const [isOpenShareModal, setIsOpenShareModal] = useState(false); const [selected, setSelected] = useState([]); const [shareComment, setShareComment] = useState(''); + const navigate = useNavigate(); + + const fetchData = async () => { + try { + const response = await getMailboxDetail(userInfo.id || ''); + if (!response) throw new Error('LetterBoxDetailPage, fetchData error'); + setMailLists(response.data); + console.log(response.data); + } catch (error) { + console.error(error); + } + }; + + useEffect(() => { + fetchData(); + }, []); + const toggleShareMode = () => { if (isShareMode) { setSelected([]); @@ -68,16 +66,41 @@ const LetterBoxDetailPage = () => { setShareComment(e.target.value); }; + const handleDisconnect = async () => { + try { + const response = await postMailboxDisconnect(userInfo.id); + if (!response) throw new Error('letterBoxDetail, disconnecting failed'); + console.log(response); + navigate(-1); + } catch (error) { + console.error(error); + } + }; + + const handleShare = async () => { + try { + // TODO: myId -> 전역객체에서 가져오기 + const response = await postShareProposals(selected, 1, userInfo.id, shareComment); + if (!response) throw new Error(response); + console.log(response); + } catch (error) { + console.error(error); + } + }; + return ( <> - {isOpenUnconnectModal && ( + {isOpenDisConnectModal && ( setIsOpenUnconnectModal(false)} - onConfirm={() => setIsOpenUnconnectModal(false)} + onCancel={() => setIsOpenDisConnectModal(false)} + onConfirm={() => { + setIsOpenDisConnectModal(false); + handleDisconnect(); + }} /> )} {isOpenShareModal && ( @@ -88,10 +111,15 @@ const LetterBoxDetailPage = () => { completeText="동의 요청 보내기" inputValue={shareComment} onInputChange={handleChangeComment} - onCancel={() => setIsOpenShareModal(false)} + onCancel={() => { + setIsOpenShareModal(false); + setShareComment(''); + }} onComplete={() => { setIsOpenShareModal(false); + handleShare(); toggleShareMode(); + setShareComment(''); }} >

상대방 동의 후에 게시글이 업로드 됩니다.

@@ -101,36 +129,41 @@ const LetterBoxDetailPage = () => { {isShareMode ? '게시판에 올릴 편지를 선택해주세요' - : `${DUMMY_ZIP_CODE}님과 주고 받은 편지`} + : `${userInfo.zipCode}님과 주고 받은 편지`}
-

주고 받은 편지 {DUMMY_LETTER_LIST.length}

+

주고 받은 편지 {mailLists.length}

- - {!isShareMode && } + {!userInfo.isClosed && ( + + )} + {!isShareMode && !userInfo.isClosed && }
- {DUMMY_LETTER_LIST.map((letter) => ( + {mailLists.map((letter) => ( toggleSelected(letter.id)} + isClosed={userInfo.isClosed} + onToggle={() => toggleSelected(letter.letterId)} + zipCode={userInfo.zipCode} /> ))}
- {!isShareMode && ( + {!isShareMode && !userInfo.isClosed && ( diff --git a/src/pages/Onboarding/SetZipCode.tsx b/src/pages/Onboarding/SetZipCode.tsx index e71b876..b941f78 100644 --- a/src/pages/Onboarding/SetZipCode.tsx +++ b/src/pages/Onboarding/SetZipCode.tsx @@ -1,10 +1,9 @@ -import React from 'react'; import Spinner from './components/Spinner'; const SetZipCode = ({ setIsZipCodeSet, }: { - setIsZipCodeSet: React.Dispatch>; + setIsZipCodeSet: React.Dispatch>; }) => { const DUMMY_ZIPCODE = '122A2'; diff --git a/src/pages/Onboarding/UserInteraction.tsx b/src/pages/Onboarding/UserInteraction.tsx index 6ab062e..a7fee26 100644 --- a/src/pages/Onboarding/UserInteraction.tsx +++ b/src/pages/Onboarding/UserInteraction.tsx @@ -1,21 +1,21 @@ -import envelope from '@/assets/images/closed-letter.png'; -import envelopeFront from '@/assets/images/opened-letter-front.png'; -import envelopeTop from '@/assets/images/envelope-pink-back-top.png'; - import { useState, useRef, useEffect } from 'react'; -import { twMerge } from 'tailwind-merge'; import { Link } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import envelope from '@/assets/images/closed-letter.png'; +import envelopeTop from '@/assets/images/envelope-pink-back-top.png'; +import envelopeFront from '@/assets/images/opened-letter-front.png'; export default function UserInteraction() { const imgRef = useRef(null); const [imgPos, setImgPos] = useState<{ top: number; width: number }>({ top: 0, width: 0 }); - const [imgToBottom, setImgToBottom] = useState(false); + const [imgToBottom, setImgToBottom] = useState(false); - const [startAnimation, setStartAnimation] = useState(false); - const [openAnimation, setOpenAnimation] = useState(false); - const [letterOutAnimation, setLetterOutAnimation] = useState(false); - const [envelopeOut, setEnvelopeOut] = useState(false); - const [finishAnimation, setFinishAnimation] = useState(false); + const [startAnimation, setStartAnimation] = useState(false); + const [openAnimation, setOpenAnimation] = useState(false); + const [letterOutAnimation, setLetterOutAnimation] = useState(false); + const [envelopeOut, setEnvelopeOut] = useState(false); + const [finishAnimation, setFinishAnimation] = useState(false); const handleLetterClick = () => { if (imgRef.current) { diff --git a/src/pages/Onboarding/components/Spinner.tsx b/src/pages/Onboarding/components/Spinner.tsx index d100bf4..c4b9295 100644 --- a/src/pages/Onboarding/components/Spinner.tsx +++ b/src/pages/Onboarding/components/Spinner.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; + import { ELEMENTS } from '../constants/index'; interface SpinnerProps { diff --git a/src/pages/Onboarding/index.tsx b/src/pages/Onboarding/index.tsx index 1bc4afe..14b839c 100644 --- a/src/pages/Onboarding/index.tsx +++ b/src/pages/Onboarding/index.tsx @@ -1,9 +1,10 @@ import { useState } from 'react'; + import SetZipCode from './SetZipCode'; import UserInteraction from './UserInteraction'; const OnboardingPage = () => { - const [isZipCodeSet, setIsZipCodeSet] = useState(false); + const [isZipCodeSet, setIsZipCodeSet] = useState(false); return (
diff --git a/src/styles/utilities.css b/src/styles/utilities.css index b5c36e0..5a3b206 100644 --- a/src/styles/utilities.css +++ b/src/styles/utilities.css @@ -99,5 +99,4 @@ .animate-envelopeOut { animation: envelopeOut 2s ease-in-out forwards; } - }