diff --git a/src/App.tsx b/src/App.tsx index 0e05801..542309a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { Route, Routes } from 'react-router'; import useViewport from './hooks/useViewport'; -import Header from './layouts/Header'; +import Layout from './layouts/Layout'; import Home from './pages/Home'; import LetterBoardPage from './pages/LetterBoard'; import LetterBoardDetailPage from './pages/LetterBoardDetail'; @@ -35,7 +35,7 @@ const App = () => { } /> } /> - }> + }> } /> } /> diff --git a/src/assets/icons/board.svg b/src/assets/icons/board.svg new file mode 100644 index 0000000..11c2560 --- /dev/null +++ b/src/assets/icons/board.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/envelope.svg b/src/assets/icons/envelope.svg new file mode 100644 index 0000000..60281da --- /dev/null +++ b/src/assets/icons/envelope.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 4016f3f..bd9733e 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -1,5 +1,8 @@ import AlarmIcon from './alarm.svg?react'; import ArrowLeftIcon from './arrow-left.svg?react'; +import BoardIcon from './board.svg?react'; +import EnvelopeIcon from './envelope.svg?react'; import PersonIcon from './person.svg?react'; +import SirenIcon from './siren.svg?react'; -export { AlarmIcon, PersonIcon, ArrowLeftIcon }; +export { AlarmIcon, PersonIcon, ArrowLeftIcon, SirenIcon, EnvelopeIcon, BoardIcon }; diff --git a/src/assets/icons/message.svg b/src/assets/icons/message.svg new file mode 100644 index 0000000..dd01d4e --- /dev/null +++ b/src/assets/icons/message.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/siren.svg b/src/assets/icons/siren.svg new file mode 100644 index 0000000..f95ec1b --- /dev/null +++ b/src/assets/icons/siren.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx index 69e67b3..dd1336b 100644 --- a/src/components/ConfirmModal.tsx +++ b/src/components/ConfirmModal.tsx @@ -21,7 +21,6 @@ const ConfirmModal = ({ onCancel, onConfirm, }: ConfirmModalProps) => { - // TODO: 배경 이미지 삽입 // TODO: 전역 상태로 관리해야하는지 고민 return ( diff --git a/src/layouts/.gitkeep b/src/layouts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index 2b7ebd7..b98d4a6 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -1,4 +1,4 @@ -import { Outlet } from 'react-router'; +import { Link } from 'react-router'; import { AlarmIcon, ArrowLeftIcon, PersonIcon } from '@/assets/icons'; @@ -6,16 +6,15 @@ const Header = () => { // TODO: 뒤로 가기 버튼이 보이는 조건 추가 // TODO: 스크롤 발생 시, 어떻게 보여져야 하는지 return ( - <> -
- -
+
+ +
+ - -
-
- - + + +
+
); }; diff --git a/src/layouts/Layout.tsx b/src/layouts/Layout.tsx new file mode 100644 index 0000000..055901b --- /dev/null +++ b/src/layouts/Layout.tsx @@ -0,0 +1,14 @@ +import { Outlet } from 'react-router'; + +import Header from './Header'; + +const Layout = () => { + return ( + <> +
+ + + ); +}; + +export default Layout; diff --git a/src/pages/MyPage/index.tsx b/src/pages/MyPage/index.tsx index 9554805..5c3a4a6 100644 --- a/src/pages/MyPage/index.tsx +++ b/src/pages/MyPage/index.tsx @@ -29,7 +29,7 @@ const MyPage = () => { onConfirm={() => setIsOpenModal(false)} /> )} -
+

{DUMMY_ZIP_CODE.split('').map((code, index) => (
void; +} + +const NotificationItem = ({ type, message, isRead, onClick }: NotificationItemProps) => { + const Icon = NOTIFICATION_ICON[type]; + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onClick(); + }; + + return ( +
+ {isRead &&
} +
+ +

{message}

+
+ ); +}; + +export default NotificationItem; diff --git a/src/pages/Notifications/components/WarningModal.tsx b/src/pages/Notifications/components/WarningModal.tsx new file mode 100644 index 0000000..e378603 --- /dev/null +++ b/src/pages/Notifications/components/WarningModal.tsx @@ -0,0 +1,58 @@ +import { useEffect, useRef } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import ModalOverlay from '@/components/ModalOverlay'; + +interface WarningModalProps { + isOpen: boolean; + onClose: () => void; +} + +const WarningModal = ({ isOpen, onClose }: WarningModalProps) => { + const ref = useRef(null); + + useEffect(() => { + const handleOutsideClick = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) onClose(); + }; + + if (isOpen) document.addEventListener('click', handleOutsideClick); + + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( + +
+
+
+

경고 안내

+

+ 따사로운 서비스 이용을 위해, 부적절하다고 판단되는 편지는 반려하고 있어요. 서로를 + 존중하는 따뜻한 공간을 만들기 위해 협조 부탁드립니다. +

+

경고 규칙

+

+ 1회 경고: 주의 안내 +
+ 2회 경고: 7일 동안 서비스 이용 제한 +
+ 3회 경고: 서비스 이용 불가능 +

+
+
+
+ ); +}; + +export default WarningModal; diff --git a/src/pages/Notifications/constants/index.ts b/src/pages/Notifications/constants/index.ts new file mode 100644 index 0000000..5640733 --- /dev/null +++ b/src/pages/Notifications/constants/index.ts @@ -0,0 +1,10 @@ +import { BoardIcon, EnvelopeIcon, SirenIcon } from '@/assets/icons'; + +export const NOTIFICATION_ICON: Record< + string, + React.ComponentType> +> = { + letter: EnvelopeIcon, + warning: SirenIcon, + board: BoardIcon, +}; diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index 99de346..2248f21 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -1,5 +1,55 @@ +import { useState } from 'react'; + +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 = () => { - return
NotificationsPage
; + const [isOpenWarningModal, setIsOpenWarningModal] = useState(false); + + const handleClickItem = (type: string) => { + if (type === 'warning') { + setIsOpenWarningModal(true); + } + }; + + return ( + <> + setIsOpenWarningModal(false)} /> +
+

알림

+ +
    + {DUMMY_NOTI.map((notification) => ( +
  • + handleClickItem(notification.type)} + /> +
  • + ))} +
+
+ + ); }; export default NotificationsPage; diff --git a/src/styles/components.css b/src/styles/components.css index e45dfa1..d08e121 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -8,4 +8,18 @@ .secondary-btn { @apply bg-primary-4 rounded-lg text-gray-50; } + + .alarm-default-bg { + background: + url('/src/assets/images/background-overlay.png') repeat, + linear-gradient(180deg, #ffb5ac 0%, #ffc1ba 28%, #ffd3ce 98.5%); + background-blend-mode: overlay, normal, normal; + } + + .alarm-warning-bg { + background: + url('/src/assets/images/background-overlay.png') repeat, + linear-gradient(180deg, #fad446 0%, #f8de8c 28%, #ffeab8 98.5%); + background-blend-mode: overlay, normal, normal; + } }