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
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -35,7 +35,7 @@ const App = () => {
<Route path="letter/:id" element={<LetterBoardDetailPage />} />
<Route path="rolling/:id" element={<RollingPaperPage />} />
</Route>
<Route path="mypage" element={<Header />}>
<Route path="mypage" element={<Layout />}>
<Route index element={<MyPage />} />
<Route path="notifications" element={<NotificationsPage />} />
</Route>
Expand Down
8 changes: 8 additions & 0 deletions src/assets/icons/board.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/envelope.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/assets/icons/index.ts
Original file line number Diff line number Diff line change
@@ -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 };
8 changes: 8 additions & 0 deletions src/assets/icons/message.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/siren.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const ConfirmModal = ({
onCancel,
onConfirm,
}: ConfirmModalProps) => {
// TODO: 배경 이미지 삽입
// TODO: 전역 상태로 관리해야하는지 고민
return (
<ModalOverlay>
Expand Down
Empty file removed src/layouts/.gitkeep
Empty file.
19 changes: 9 additions & 10 deletions src/layouts/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { Outlet } from 'react-router';
import { Link } from 'react-router';

import { AlarmIcon, ArrowLeftIcon, PersonIcon } from '@/assets/icons';

const Header = () => {
// TODO: 뒤로 가기 버튼이 보이는 조건 추가
// TODO: 스크롤 발생 시, 어떻게 보여져야 하는지
return (
<>
<header className="sticky top-0 flex items-center justify-between p-5">
<ArrowLeftIcon className="h-6 w-6 text-white" />
<div className="flex items-center gap-3">
<header className="sticky top-0 flex h-16 items-center justify-between p-5">
<ArrowLeftIcon className="h-6 w-6 text-white" />
<div className="flex items-center gap-3">
<Link to="/mypage/notifications">
<AlarmIcon className="h-6 w-6 text-white" />
<PersonIcon className="h-6 w-6 text-white" />
</div>
</header>
<Outlet />
</>
</Link>
<PersonIcon className="h-6 w-6 text-white" />
</div>
</header>
);
};

Expand Down
14 changes: 14 additions & 0 deletions src/layouts/Layout.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 이렇게 레이아웃 나눈 거 좋아요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Outlet } from 'react-router';

import Header from './Header';

const Layout = () => {
return (
<>
<Header />
<Outlet />
</>
);
};

export default Layout;
2 changes: 1 addition & 1 deletion src/pages/MyPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const MyPage = () => {
onConfirm={() => setIsOpenModal(false)}
/>
)}
<main className="flex grow flex-col gap-12 px-5 pt-9 pb-6">
<main className="flex grow flex-col gap-12 px-5 pt-4 pb-6">
<h1 className="h2-b mx-auto flex gap-1.5">
{DUMMY_ZIP_CODE.split('').map((code, index) => (
<div
Expand Down
36 changes: 36 additions & 0 deletions src/pages/Notifications/components/NotificationItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { twMerge } from 'tailwind-merge';

import { NOTIFICATION_ICON } from '../constants';

interface NotificationItemProps {
type: string;
message: string;
isRead: boolean;
onClick: () => void;
}

const NotificationItem = ({ type, message, isRead, onClick }: NotificationItemProps) => {
const Icon = NOTIFICATION_ICON[type];

const handleClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
onClick();
};

return (
<article
className={twMerge(
'relative flex cursor-pointer items-center gap-3 overflow-hidden rounded-sm p-4',
type === 'warning' ? 'alarm-warning-bg' : 'alarm-default-bg',
)}
onClick={handleClick}
>
{isRead && <div className="absolute inset-0 z-10 bg-white/60" />}
<div className="absolute inset-0 bg-white/50 blur-xl" />
<Icon className="z-0 h-6 w-6 text-white" />
<p className="body-m text-gray-80 z-0">{message}</p>
</article>
);
};

export default NotificationItem;
58 changes: 58 additions & 0 deletions src/pages/Notifications/components/WarningModal.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

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

handleoutside 함수도 utils로 빼면 좋을 것 같아용

Original file line number Diff line number Diff line change
@@ -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<HTMLElement>(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 (
<ModalOverlay>
<article
ref={ref}
className={twMerge(
'relative w-77 overflow-hidden rounded-sm p-6',
'bg-accent-1 bg-[url("/src/assets/images/background-overlay.png")] bg-repeat bg-blend-overlay',
)}
>
<div className="absolute inset-0 h-full w-full bg-white/90 blur-[25px]" />
<div className="relative">
<h2 className="body-sb mb-1.5 text-gray-100">경고 안내</h2>
<p className="caption-r mb-5 text-black">
따사로운 서비스 이용을 위해, 부적절하다고 판단되는 편지는 반려하고 있어요. 서로를
존중하는 따뜻한 공간을 만들기 위해 협조 부탁드립니다.
</p>
<h2 className="body-sb mb-1.5 text-gray-100">경고 규칙</h2>
<p className="caption-r text-black">
1회 경고: 주의 안내
<br />
2회 경고: 7일 동안 서비스 이용 제한
<br />
3회 경고: 서비스 이용 불가능
</p>
</div>
</article>
</ModalOverlay>
);
};

export default WarningModal;
10 changes: 10 additions & 0 deletions src/pages/Notifications/constants/index.ts
Copy link
Collaborator

Choose a reason for hiding this comment

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

우와 이런식으로도 매핑이 가능하군요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { BoardIcon, EnvelopeIcon, SirenIcon } from '@/assets/icons';

export const NOTIFICATION_ICON: Record<
string,
React.ComponentType<React.SVGProps<SVGSVGElement>>
> = {
letter: EnvelopeIcon,
warning: SirenIcon,
board: BoardIcon,
};
52 changes: 51 additions & 1 deletion src/pages/Notifications/index.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>NotificationsPage</div>;
const [isOpenWarningModal, setIsOpenWarningModal] = useState(false);

const handleClickItem = (type: string) => {
if (type === 'warning') {
setIsOpenWarningModal(true);
}
};

return (
<>
<WarningModal isOpen={isOpenWarningModal} onClose={() => setIsOpenWarningModal(false)} />
<main className="flex grow flex-col items-center px-5 pt-4 pb-9">
<h1 className="text-gray-60 body-b mb-10 w-fit rounded-full bg-white px-6 py-4">알림</h1>
Copy link
Collaborator

Choose a reason for hiding this comment

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

제가 이거 공통 스타일로 만들어둘께용

<button type="button" className="body-sb text-gray-60 place-self-end">
모두 읽음
</button>
<ul className="mt-2 flex h-full w-full flex-col gap-2 pb-10">
{DUMMY_NOTI.map((notification) => (
<li key={notification.id}>
<NotificationItem
type={notification.type}
message={notification.message}
isRead={notification.isRead}
onClick={() => handleClickItem(notification.type)}
/>
</li>
))}
</ul>
</main>
</>
);
};

export default NotificationsPage;
14 changes: 14 additions & 0 deletions src/styles/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}