Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions src/apis/incomingLetters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { client } from '@/apis/client';

export const fetchIncomingLettersApi = async (token: string) => {
try {
const { data } = await client.get('/api/letters?status=delivery', {
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log('불러온 데이터', data);
return data;
} catch (error) {
console.error('❌오고 있는 편지 목록을 불러오던 중 에러 발생', error);
throw error;
}
};
3 changes: 2 additions & 1 deletion src/pages/Home/components/FloatingLetters.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react';
import gsap from 'gsap';
import { useEffect, useRef } from 'react';

import letter1 from '@/assets/images/letter-1.png';
import letter2 from '@/assets/images/letter-2.png';
import letter3 from '@/assets/images/letter-3.png';
Expand Down
1 change: 1 addition & 0 deletions src/pages/Home/components/GoToLetterBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from 'react-router';

import goToLetterBoard from '@/assets/images/go-to-letter-board.png';

const GoToLetterBoard = () => {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Home/components/GoToLetterBox.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
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';

const GoToLetterBox = () => {
//TODO : hasNewLetters 전역으로 상태 관리하기
let hasNewLetters = true;
const hasNewLetters = true;
return (
<div className="absolute bottom-10 left-5 z-9 flex w-fit">
<div className="text-left">
Expand Down
1 change: 1 addition & 0 deletions src/pages/Home/components/GoToRandomLetter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from 'react-router';

import goToRandomLetter from '@/assets/images/go-to-random-letter.png';

const GoToRandomLetter = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/components/HomeBackgroundLeft.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
import homeLeftMountain from '@/assets/images/home-left-mountain.png';
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';

const HomeBackgroundLeft = () => {
return (
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Home/components/HomeBackgroundRightBottom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
import homeRightMountainBottom from '@/assets/images/home-right-mountain-bottom.png';
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';

const HomeBackgroundRightBottom = () => {
return (
<BackgroundImageWrapper
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/components/HomeBackgroundRightTop.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
import homeRightMountainTop from '@/assets/images/home-right-mountain-top.png';
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
const HomeBackgroundRightTop = () => {
return (
<BackgroundImageWrapper
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/components/HomeLeft.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import RandomCheer from './RandomCheer';
import GoToWrite from './GoToWrite';
import GoToRandomLetter from './GoToRandomLetter';
import GoToWrite from './GoToWrite';
import RandomCheer from './RandomCheer';

const HomeLeft = () => {
return (
Expand Down
14 changes: 10 additions & 4 deletions src/pages/Home/components/HomeRight.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { useEffect } from 'react';

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

import FloatingLetters from './FloatingLetters';
import GoToLetterBoard from './GoToLetterBoard';
import GoToLetterBox from './GoToLetterBox';
import NewLetterModal from './NewLetterModal';

const HomeRight = () => {
//TODO : hasNewLetters 전역으로 상태 관리할지
let hasNewLetters = true;
const { arrivedCount, fetchIncomingLetters } = useIncomingLettersStore();
useEffect(() => {
fetchIncomingLetters();
}, []);
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

Choose a reason for hiding this comment

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

새로 도착한 편지들의 유무/개수를 오고 있는 편지 조회쪽에서 받아와야 해서 전역으로 상태 관리하도록 하였습니다!


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">
{hasNewLetters && <FloatingLetters />}
{arrivedCount !== 0 && <FloatingLetters />}
<GoToLetterBox />
<GoToLetterBoard />
{hasNewLetters && <NewLetterModal />}
{arrivedCount !== 0 && <NewLetterModal />}
</div>
);
};
Expand Down
33 changes: 18 additions & 15 deletions src/pages/Home/components/LetterActions.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useState } from 'react';
import SendOutlinedIcon from '@mui/icons-material/SendOutlined';
import DriveFileRenameOutlineOutlinedIcon from '@mui/icons-material/DriveFileRenameOutlineOutlined';
import SendOutlinedIcon from '@mui/icons-material/SendOutlined';
import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined';
import ShowIncomingLettersModal from './ShowIncomingLettersModal';
import { useState } from 'react';

import ShowDraftModal from './ShowDraftModal';
import ShowIncomingLettersModal from './ShowIncomingLettersModal';
import ShowShareAccessModal from './ShowShareAccessModal';

const LetterActions = () => {
Expand All @@ -26,17 +27,19 @@ const LetterActions = () => {
},
];
return (
<div className="fixed top-24 z-31 mt-3 flex w-full max-w-150 justify-end pr-5">
<div className="flex flex-col gap-y-3">
{arr.map((item, index) => (
<button
key={index}
onClick={() => setActiveModal(item.title)}
className="flex h-12 w-12 items-center justify-center gap-[10px] rounded-full bg-white/40 text-gray-50 shadow-[inset_0_-2px_2px_0_rgba(208,169,14,0.30),_0_0px_4px_0_rgba(199,164,29,0.30)]"
>
{item.icon}
</button>
))}
<>
<div className="fixed top-24 z-26 mt-3 flex w-full max-w-150 justify-end pr-5">
<div className="z-42 flex flex-col gap-y-3">
{arr.map((item, index) => (
<button
key={index}
onClick={() => setActiveModal(item.title)}
className="flex h-12 w-12 items-center justify-center gap-[10px] rounded-full bg-white/40 text-gray-50 shadow-[inset_0_-2px_2px_0_rgba(208,169,14,0.30),_0_0px_4px_0_rgba(199,164,29,0.30)]"
>
{item.icon}
</button>
))}
</div>
</div>
{activeModal === 'incomingLetters' && (
<ShowIncomingLettersModal onClose={() => setActiveModal(null)} />
Expand All @@ -45,7 +48,7 @@ const LetterActions = () => {
{activeModal === 'shareAccess' && (
<ShowShareAccessModal onClose={() => setActiveModal(null)} />
)}
</div>
</>
);
};

Expand Down
7 changes: 0 additions & 7 deletions src/pages/Home/components/LetterPreview.tsx

This file was deleted.

13 changes: 10 additions & 3 deletions src/pages/Home/components/NewLetterModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { useState } from 'react';
import { useEffect } from 'react';

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

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

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

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">
{newLetterCount}통의 편지가 도착했어요!
{arrivedCount}통의 편지가 도착했어요!
</p>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/components/RandomCheer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const RandomCheer = () => {
const [randomCheer, setRandomCheer] = useState(getRandomCheer());

return (
<div className="z-30 flex flex-col items-end pr-20">
<div className="z-26 mr-20 flex flex-col items-end">
Copy link
Collaborator

Choose a reason for hiding this comment

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

테일윈드 z-index 기본 제공값이 0, 10, 20, 30, 40 ,50인걸로 알고 있어요! 그래서 26같은 인덱스는 중괄호 []를 묶어서 z-[26] 이렇게 사용했었는데 이렇게 설정해도 잘 작동되나용?(테일윈드 4.0업데이트 되고 바꼈을 수도 있어성..)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

tailwind 4.0 업데이트 후에는 1단위로도 지원한다고 합니다! 다만 무한정 지원하는 건 아니고 속성 별로 허용 범위가 있다고 하네요

<div
className="relative mb-3 w-fit rounded-lg border-1 border-white bg-white px-6 py-[7px] text-center"
onClick={() => setRandomCheer(getRandomCheer())}
Expand Down
35 changes: 22 additions & 13 deletions src/pages/Home/components/ShowIncomingLettersModal.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import React from 'react';

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';

interface ShowIncomingLettersModalProps {
children?: React.ReactNode;
onClose: () => void;
}

const DUMMY_INCOMING_LETTERS = [
{ id: 1, title: '취업 때문에 고민이 많아요!!', time: '12:00:00' },
{ id: 2, title: '배고파서 죽을 거 같아요 😭', time: '00:00:03' },
{ id: 3, title: '개발하니까 밖에 나갈 일이 없어서 너무 심심하고 피곤해요', time: '00:00:03' },
{ id: 4, title: '마라샹궈 먹고 싶어요', time: '00:00:03' },
];

const ShowIncomingLettersModal = ({ onClose }: ShowIncomingLettersModalProps) => {
const navigate = useNavigate();

const handleNavigation = (incomingId: number) => {
navigate(`/board/letter/${incomingId}`, {
state: { isShareLetterPreview: false },
});
};

const { data, fetchIncomingLetters } = useIncomingLettersStore();

useEffect(() => {
fetchIncomingLetters();
});

return (
<ModalOverlay closeOnOutsideClick onClose={onClose}>
<div className="flex h-full flex-col items-center justify-center">
Expand All @@ -28,14 +36,15 @@ const ShowIncomingLettersModal = ({ onClose }: ShowIncomingLettersModalProps) =>
<p className="body-sb text-gray-80">오고 있는 편지</p>
<p className="caption-r text-black">시간은 실제 시간을 기반으로 책정됩니다.</p>
</div>
<div className="mt-6 flex w-[251px] flex-col gap-[10px]">
{DUMMY_INCOMING_LETTERS.map((letter) => (
<div className="mt-6 flex max-h-60 min-h-auto w-[251px] flex-col gap-[10px] overflow-y-scroll [&::-webkit-scrollbar]:hidden">
{data.map((letter) => (
<div
className="text-gray-80 body-m flex h-10 w-full items-center justify-between gap-1 rounded-lg bg-white p-3"
key={letter.id}
key={letter.letterId}
onClick={() => handleNavigation(letter.letterId)}
>
<p className="truncate">{letter.title}</p>
<p>{letter.time}</p>
<p>{letter.remainingTime}</p>
</div>
))}
</div>
Expand Down
68 changes: 68 additions & 0 deletions src/stores/incomingLettersStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { create } from 'zustand';

import { fetchIncomingLettersApi } from '@/apis/incomingLetters';

interface IncomingLetters {
letterId: number;
title: string;
deliveryStartedAt: string;
deliveryCompletedAt: string;
remainingTime: string;
}

interface IncomingLettersStore {
data: IncomingLetters[];
arrivedCount: number;
message: string;
timestamp: string;
fetchIncomingLetters: () => void;
}

const formatTime = (time: number): string => (time < 10 ? `0${time}` : `${time}`);

const calculatingRemainingTime = (deliveryCompletedAt: string): string => {
const completedAt = new Date(deliveryCompletedAt).getTime();
const now = new Date().getTime();
const diff = completedAt - now;

if (diff <= 0) return '00:00:00';

const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);

return `${formatTime(hours)}:${formatTime(minutes)}:${formatTime(seconds)}`;
};

export const useIncomingLettersStore = create<IncomingLettersStore>((set) => ({
data: [],
arrivedCount: 0,
message: '',
timestamp: '',
fetchIncomingLetters: async () => {
try {
const token = localStorage.getItem('token') || '';
const data = await fetchIncomingLettersApi(token);

let arrivedCount = 0;
const updatedLetters = data.data.map((letter: IncomingLetters) => {
const remainingTime = calculatingRemainingTime(letter.deliveryCompletedAt);
if (remainingTime === '00:00:00') arrivedCount += 1; // 도착한 편지 카운트

return { ...letter, remainingTime };
});

const inProgressLetters = updatedLetters.filter(
(letter: IncomingLetters) => letter.remainingTime !== '00:00:00',
);
set({
data: inProgressLetters,
arrivedCount,
message: data.message,
timestamp: data.timestamp,
});
} catch (error) {
console.error('❌오고 있는 편지 목록을 불러오던 중 에러 발생', error);
}
},
}));