diff --git a/package.json b/package.json index a5dc725..c010efa 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^7.1.5", + "swiper": "^11.2.4", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.6", "zustand": "^5.0.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 329dd62..8732bcb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: react-router: specifier: ^7.1.5 version: 7.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + swiper: + specifier: ^11.2.4 + version: 11.2.4 tailwind-merge: specifier: ^3.0.1 version: 3.0.1 @@ -2105,6 +2108,10 @@ packages: svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + swiper@11.2.4: + resolution: {integrity: sha512-DTtglrsFfMYytid+oNy4QI3t2N2+XhhwSYbnyOhlwBmvY8Bkoj3ombK1/b80w8vDpQ+Lqlnbm+0737+i32MrcA==} + engines: {node: '>= 4.7.0'} + tailwind-merge@3.0.1: resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==} @@ -4304,6 +4311,8 @@ snapshots: svg-parser@2.0.4: {} + swiper@11.2.4: {} + tailwind-merge@3.0.1: {} tailwindcss@4.0.6: {} diff --git a/src/assets/icons/cloud.svg b/src/assets/icons/cloud.svg new file mode 100644 index 0000000..ac0df8d --- /dev/null +++ b/src/assets/icons/cloud.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/color-siren.svg b/src/assets/icons/color-siren.svg new file mode 100644 index 0000000..139be74 --- /dev/null +++ b/src/assets/icons/color-siren.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index e14f8ea..7983ffa 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -1,6 +1,8 @@ import AlarmIcon from './alarm.svg?react'; import ArrowLeftIcon from './arrow-left.svg?react'; import BoardIcon from './board.svg?react'; +import CloudIcon from './cloud.svg?react'; +import ColorSirenIcon from './color-siren.svg?react'; import DeleteIcon from './delete.svg?react'; import EnvelopeIcon from './envelope.svg?react'; import InformationIcon from './infromation.svg?react'; @@ -8,18 +10,28 @@ import LikeFilledIcon from './like-filled.svg?react'; import LikeOutlinedIcon from './like-outlined.svg?react'; import NoticeIcon from './notice.svg?react'; import PersonIcon from './person.svg?react'; +import RestartIcon from './restart.svg'; import SirenFilledIcon from './siren-filled.svg?react'; import SirenOutlinedIcon from './siren-outlined.svg?react'; +import SnowIcon from './snow.svg?react'; +import ThermostatIcon from './thermostat.svg?react'; +import WarmIcon from './warm.svg?react'; export { AlarmIcon, PersonIcon, ArrowLeftIcon, InformationIcon, - SirenFilledIcon, - SirenOutlinedIcon, EnvelopeIcon, BoardIcon, + RestartIcon, + CloudIcon, + SnowIcon, + ThermostatIcon, + WarmIcon, + ColorSirenIcon, + SirenFilledIcon, + SirenOutlinedIcon, NoticeIcon, LikeFilledIcon, LikeOutlinedIcon, diff --git a/src/assets/icons/restart.svg b/src/assets/icons/restart.svg new file mode 100644 index 0000000..b228ae2 --- /dev/null +++ b/src/assets/icons/restart.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/snow.svg b/src/assets/icons/snow.svg new file mode 100644 index 0000000..fe6746b --- /dev/null +++ b/src/assets/icons/snow.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/thermostat.svg b/src/assets/icons/thermostat.svg new file mode 100644 index 0000000..89d2b9e --- /dev/null +++ b/src/assets/icons/thermostat.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/warm.svg b/src/assets/icons/warm.svg new file mode 100644 index 0000000..5070cc4 --- /dev/null +++ b/src/assets/icons/warm.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/field-4.png b/src/assets/images/field-4.png index 8844d58..d920f40 100644 Binary files a/src/assets/images/field-4.png and b/src/assets/images/field-4.png differ diff --git a/src/pages/LetterDetail/index.tsx b/src/pages/LetterDetail/index.tsx index 98ef5b0..ac0f42c 100644 --- a/src/pages/LetterDetail/index.tsx +++ b/src/pages/LetterDetail/index.tsx @@ -1,5 +1,94 @@ +import { useEffect, useRef, useState } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import { CloudIcon, ColorSirenIcon, SnowIcon, ThermostatIcon, WarmIcon } from '@/assets/icons'; +import ReportModal from '@/components/ReportModal'; + const LetterDetailPage = () => { - return
LetterDetailPage
; + const DUMMY = { + title: '나에게 햄버거 햄부기우기우가 햄북스따스 함부르크 햄버거링고를 대령하거라 ', + text: '이 편지는 영국에서 최초로 시작되어 일년에 한바퀴를 돌면서 받는 사람에게 행운을 주었고 지금은 당신에게로 옮겨진 이 편지는 4일 안에 당신 곁을 떠나야 합니다. 이 편지를 포함해서 7통을 행운이 필요한 사람에게 보내 주셔야 합니다. 복사를 해도 좋습니다. 혹 미신이라 하실지 모르지만 사실입니다.영국에서 HGXWCH이라는 사람은 1930년에 이 편지를 받았습니다. 그는 비서에게 복사해서 보내라고 했습니다. 며칠 뒤에 복권이 당첨되어 20억을 받았습니다. 어떤 이는 이 편지를 받았으나 96시간 이내 자신의 손에서 떠나야 한다는 사실을 잊었습니다. 그는 곧 사직되었습니다. 나중에야 이 사실을 알고 7통의 편지를 보냈는데 다시 좋은 직장을 얻었습니다. 미국의 케네디 대통령은 이 편지를 받았지만 그냥 버렸습니다. 결국 9일 후 그는 암살당했습니다. 기억해 주세요. 이 편지를 보내면 7년의 행운이 있을 것이고 그렇지 않으면 3년의 불행이 있을 것입니다. ', + }; + const FONT = 'kobyo'; + const THEME = 'celebrate'; + const DEGREES = [ + { icon: , title: '따뜻해요' }, + { icon: , title: '그럭저럭' }, + { icon: , title: '앗! 차가워' }, + ]; + const [degreeModalOpen, setDegreeModalOpen] = useState(false); + const [reportModalOpen, setReportModalOpen] = useState(false); + + const degreeButtonRef = useRef(null); + const handleOutsideClick = (event: MouseEvent) => { + const target = event.target as Node; + if (!target || degreeButtonRef.current?.contains(target)) { + return; + } + setDegreeModalOpen(false); + }; + useEffect(() => { + document.body.addEventListener('click', handleOutsideClick); + + return () => { + document.body.removeEventListener('click', handleOutsideClick); + }; + }, []); + return ( + <> + {reportModalOpen && setReportModalOpen(false)} />} +
+
+ + + {degreeModalOpen && ( +
+ {DEGREES.map((degree, idx) => { + return ( + + ); + })} +
+ )} +
+
+ TO. 따숨이 + {DUMMY.title} +
+ + FROM. {'12E12'} + +
+ + ); }; export default LetterDetailPage; diff --git a/src/pages/RandomLetters/Matched.tsx b/src/pages/RandomLetters/Matched.tsx new file mode 100644 index 0000000..6d504fc --- /dev/null +++ b/src/pages/RandomLetters/Matched.tsx @@ -0,0 +1,20 @@ +import ResultLetter from '@/components/ResultLetter'; + +export default function Matched() { + return ( +
+
+

답장까지 남은 시간

+

+ {'00'} : {'00'} : {'00'} +

+
+ +
+ +
+
+ ); +} diff --git a/src/pages/RandomLetters/MatchingSelect.tsx b/src/pages/RandomLetters/MatchingSelect.tsx new file mode 100644 index 0000000..1410935 --- /dev/null +++ b/src/pages/RandomLetters/MatchingSelect.tsx @@ -0,0 +1,88 @@ +import { useState } from 'react'; +import { EffectCards } from 'swiper/modules'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import { twMerge } from 'tailwind-merge'; + +import 'swiper/swiper-bundle.css'; + +import { RestartIcon } from '@/assets/icons'; +import ResultLetter from '@/components/ResultLetter'; + +export default function MatchingSelect({ + setOpenModal, + setSelectedLetter, +}: { + setOpenModal: React.Dispatch>; + setSelectedLetter: React.Dispatch>; +}) { + const [selectedCategory, setSelectedCategory] = useState('전체'); + + const CATEGORY_LIST = ['전체', '위로와 공감', '축하와 응원', '고민 상담', '기타']; + const DUMMY_LIST: { stampName: Stamp; title: string }[] = [ + { stampName: '위로와 공감', title: '위로가 필요해요' }, + { stampName: '축하와 응원', title: '저에게 미움받을 용기를 주세요' }, + { stampName: '고민 상담', title: '삶이 무료해서 고민이에요' }, + { stampName: '기타', title: '어제 꾼 꿈이 신기했어요' }, + { stampName: '고민 상담', title: '삶이 유료해서 고민이에요' }, + { stampName: '축하와 응원', title: '어제 취업했어요!!!!' }, + { stampName: '축하와 응원', title: '어제 게임 신기록 세웠어요!!!!!' }, + { stampName: '기타', title: '기타는 핑거스타일이 멋있는거 같아요' }, + { stampName: '위로와 공감', title: '10년지기 친구가 이사를 가요' }, + { + stampName: '기타', + title: + '햄부기햄북 햄북어 햄북스딱스 함부르크햄부가우가 햄비기햄부거 햄부가티햄부기온앤 온 을 차려오거라.', + }, + ]; + + return ( + <> +
+ +
+ + {DUMMY_LIST.map((list, idx) => { + return ( + +
{ + setOpenModal(true); + setSelectedLetter(() => ({ stampName: list.stampName, title: list.title })); + }} + > + +
+
+ ); + })} +
+
+
+ +
+ {CATEGORY_LIST.map((category, idx) => { + return ( + + ); + })} +
+ + ); +} diff --git a/src/pages/RandomLetters/MatchingSelectModal.tsx b/src/pages/RandomLetters/MatchingSelectModal.tsx new file mode 100644 index 0000000..2fc083d --- /dev/null +++ b/src/pages/RandomLetters/MatchingSelectModal.tsx @@ -0,0 +1,47 @@ +import { useNavigate } from 'react-router'; + +import ModalOverlay from '@/components/ModalOverlay'; +import ResultLetter from '@/components/ResultLetter'; + +function MatchingSelectModal({ + setOpenModal, + selectedLetter, +}: { + setOpenModal: React.Dispatch>; + selectedLetter: SelectedLetter; +}) { + const navigate = useNavigate(); + return ( + +
+
+ 이 편지에 답장 하시겠어요? + 수락한 편지는 5분이 지나면 취소할 수 없습니다. +
+
+ +
+
+ + +
+
+
+ ); +} +export default MatchingSelectModal; diff --git a/src/pages/RandomLetters/index.tsx b/src/pages/RandomLetters/index.tsx index 90088e1..7f32d4f 100644 --- a/src/pages/RandomLetters/index.tsx +++ b/src/pages/RandomLetters/index.tsx @@ -1,5 +1,38 @@ +import { useState } from 'react'; + +import BackgroundBottom from '@/components/BackgroundBottom'; +import PageTitle from '@/components/PageTitle'; + +import Matched from './Matched'; +import MatchingSelect from './MatchingSelect'; +import MatchingSelectModal from './MatchingSelectModal'; + const RandomLettersPage = () => { - return
RandomLettersPage
; + const [openModal, setOpenModal] = useState(false); + const [matched, setMatched] = useState(false); + const [selectedLetter, setSelectedLetter] = useState({ + stampName: '기타', + title: 'error', + }); + + return ( + <> +
+ + {!matched ? '답장하고 싶은 편지를 선택해주세요!' : '이미 답장 중인 편지가 있어요!'} + + {!matched ? ( + + ) : ( + + )} + + {openModal && ( + + )} +
+ + ); }; export default RandomLettersPage; diff --git a/src/pages/Write/CategorySelect.tsx b/src/pages/Write/CategorySelect.tsx index a8a1855..9e80bd2 100644 --- a/src/pages/Write/CategorySelect.tsx +++ b/src/pages/Write/CategorySelect.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { Link } from 'react-router'; +import PageTitle from '@/components/PageTitle'; import useWrite from '@/stores/writeStore'; import CategoryList from './components/CategoryList'; @@ -19,7 +20,7 @@ export default function CategorySelect({ const stamp = useWrite((state) => state.stamp); return ( <> -
+
{!send && !prevLetter && ( - + {send || prevLetter ? '편지 작성이 완료 되었어요!' : '어떤 답장을 받고 싶나요?'} - + {/* 카테고리 선택 컴포넌트 */} {!send && !prevLetter && } @@ -65,13 +66,13 @@ export default function CategorySelect({ {send || prevLetter ? ( 홈으로 돌아가기 ) : (