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: 1 addition & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Route, Routes } from 'react-router';

import { useServerSentEvents } from './hooks/useServerSentEvents';
import useViewport from './hooks/useViewport';
import Layout from './layouts/Layout';
import MobileLayout from './layouts/MobileLayout';
Expand Down Expand Up @@ -30,7 +29,6 @@ import WritePage from './pages/Write';

const App = () => {
useViewport();
useServerSentEvents();

return (
<Routes>
Expand All @@ -39,10 +37,10 @@ const App = () => {
<Route path="landing" element={<Landing />} />
<Route path="*" element={<NotFoundPage />} />
<Route path="auth-callback" element={<AuthCallbackPage />} />
<Route index element={<Home />} />
<Route path="onboarding" element={<OnboardingPage />} />

<Route element={<PrivateRoute />}>
<Route index element={<Home />} />
<Route path="letter">
<Route element={<Layout />}>
<Route path="random" element={<RandomLettersPage />} />
Expand Down
15 changes: 1 addition & 14 deletions src/apis/write.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// import { AxiosResponse } from 'axios';
import client from './client';

const postLetter = async (data: LetterRequest) => {
Expand Down Expand Up @@ -33,7 +32,6 @@ const getPrevLetter = async (letterId: string) => {
}
};

// 임시저장 최초 생성
const postTemporarySave = async (data: TemporaryRequest) => {
try {
const res = client.post(`/api/letters/temporary-save`, data);
Expand All @@ -44,15 +42,4 @@ const postTemporarySave = async (data: TemporaryRequest) => {
}
};

// 임시저장 수정
const PatchTemporarySave = async (data: TemporaryRequest) => {
try {
const res = client.post(`/api/letters/temporary-save`, data);
if (!res) throw new Error('편지 임시저장과정에서 오류가 발생했습니다.');
return res;
} catch (error) {
console.error(error);
}
};

export { postLetter, postFirstReply, getPrevLetter, postTemporarySave, PatchTemporarySave };
export { postLetter, postFirstReply, getPrevLetter, postTemporarySave };
16 changes: 16 additions & 0 deletions src/components/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import useToastStore from '@/stores/toastStore';
import ToastItem from './ToastItem';

interface Toast {}
export default function Toast({}: Toast) {
const toastObjects = useToastStore((state) => state.toastObjects);

if (toastObjects.length <= 0) return;
return (
<>
{toastObjects.map((toastObj, index) => (
<ToastItem toastObj={toastObj} index={index} key={index} />
))}
</>
);
}
54 changes: 54 additions & 0 deletions src/components/ToastItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import useToastStore from '@/stores/toastStore';
import { useEffect } from 'react';
import { twMerge } from 'tailwind-merge';

interface ToastObj {
time: number;
toastType: 'Warning' | 'Success' | 'Error' | 'Info';
position: 'Top' | 'Bottom';
title: string;
onClick?: () => void;
}
export default function ToastItem({ toastObj, index }: { toastObj: ToastObj; index: number }) {
const setToastUnActive = useToastStore((state) => state.setToastUnActive);

const TOAST_DESIGN = {
Warning: { style: 'bg-primary-4', imoji: '⚠️' },
Success: { style: 'bg-[#38d9a9] text-[#FFFFFF]', imoji: '✅' },
Error: { style: 'bg-[#FFDCD8] text-[#FF0000]', imoji: '🚨' },
Info: { style: 'bg-[#FFFFFF]', imoji: '📫' },
};

const TOAST_POSITION = {
Top: 'top-20',
Bottom: 'bottom-20',
};

const animation = `toast-blink ${toastObj.time}s ease-in-out forwards`;
const toastStyle = twMerge(
'fixed bottom-20 left-1/2 z-40 flex h-[40px] max-w-150 min-w-[335px] w-[85%] -translate-1/2 items-center justify-center rounded-2xl caption-sb',
Copy link
Collaborator

Choose a reason for hiding this comment

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

100%로 해도 될것 같아용! 양옆 마진 20씩해서?
이건 같이 보면서 수정할까용?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

헉 그런 방법이
바로 적용하겠습니다!!!🥕🥕🥕

TOAST_POSITION[toastObj.position],
TOAST_DESIGN[toastObj.toastType].style,
);

const activeTime = toastObj.time * 1000;
useEffect(() => {
const closeToast = setTimeout(() => {
setToastUnActive(index);
}, activeTime);

return () => clearTimeout(closeToast);
});
return (
<div
className={toastStyle}
style={{ animation: animation }}
onClick={() => {
setToastUnActive(index);
if (toastObj.onClick) toastObj.onClick();
}}
>
{`${TOAST_DESIGN[toastObj.toastType].imoji} ${toastObj.title} ${TOAST_DESIGN[toastObj.toastType].imoji}`}
</div>
);
}
27 changes: 17 additions & 10 deletions src/hooks/useServerSentEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
import { useEffect, useRef } from 'react';

import useAuthStore from '@/stores/authStore';
import useToastStore from '@/stores/toastStore';
import { useNavigate } from 'react-router';

export const useServerSentEvents = () => {
const accessToken = useAuthStore.getState().accessToken;
const navigate = useNavigate();

const accessToken = useAuthStore((state) => state.accessToken);
const sourceRef = useRef<EventSourcePolyfill | null>(null);

const setToastActive = useToastStore((state) => state.setToastActive);

useEffect(() => {
if (!accessToken) {
console.log('로그인 정보 확인불가');
Expand All @@ -27,18 +33,20 @@ export const useServerSentEvents = () => {

sourceRef.current.onmessage = (event) => {
console.log(event);
console.log('알림 전송');
console.log('알림 수신');
setToastActive({
toastType: 'Info',
title: '새 알림이 도착했어요!',
position: 'Top',
time: 5,
onClick: () => navigate('/mypage/notifications'),
});
};

sourceRef.current.addEventListener('notification', (event) => {
console.log(event);
console.log('알림 전송 dd');
});

sourceRef.current.onerror = (error) => {
console.log(error);
console.log('에러 발생함');
sourceRef.current?.close();
closeSSE();
// 재연결 로직 추가 가능
setTimeout(connectSSE, 5000); // 5초 후 재연결 시도
};
Expand All @@ -55,11 +63,10 @@ export const useServerSentEvents = () => {
};
}, [accessToken]);

// 바깥으로 보낼 closeSSE 함수
const closeSSE = () => {
sourceRef.current?.close();
sourceRef.current = null;
};

return { closeSSE };
// return { closeSSE };
};
10 changes: 9 additions & 1 deletion src/layouts/PrivateRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { useEffect, useState } from 'react';
import { useNavigate, Outlet } from 'react-router';

import useAuthStore from '@/stores/authStore';
import { useServerSentEvents } from '@/hooks/useServerSentEvents';
import Toast from '@/components/Toast';

export default function PrivateRoute() {
useServerSentEvents();
const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
const navigate = useNavigate();
const [shouldRender, setShouldRender] = useState(false);
Expand All @@ -20,5 +23,10 @@ export default function PrivateRoute() {
return null;
}

return <Outlet />;
return (
<>
<Outlet />
<Toast />
</>
);
}
36 changes: 36 additions & 0 deletions src/pages/Notifications/components/SendingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import LetterWrapper from '@/components/LetterWrapper';
import ModalOverlay from '@/components/ModalOverlay';
import { useNavigate } from 'react-router';

export default function SendingModal({
isOpenSendingModal,
setIsOpenSendingModal,
}: {
isOpenSendingModal: boolean;
setIsOpenSendingModal: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const navigate = useNavigate();
if (!isOpenSendingModal) return null;
const onClose = () => {
setIsOpenSendingModal(false);
};
return (
<>
<ModalOverlay closeOnOutsideClick onClose={onClose}>
<LetterWrapper className="w-77">
<div className="caption-r flex flex-col gap-2">
<h2 className="body-b mb-3">편지 도착</h2>
<span>편지는 작성된 시점으로 1시간 이후에 도착합니다.</span>
<span>남은시간은 홈 화면의 편지 도착 시간 버튼을 눌러 확인 가능합니다.</span>
<button
className="body-b mt-3 flex items-center justify-center"
onClick={() => navigate('/')}
>
홈 화면으로 이동 {'>'}
</button>
</div>
</LetterWrapper>
</ModalOverlay>
</>
);
}
15 changes: 9 additions & 6 deletions src/pages/Notifications/components/WarningModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import ModalOverlay from '@/components/ModalOverlay';

interface WarningModalProps {
isOpen: boolean;
adminText: string;
reportContent: string;
onClose: () => void;
}

const WarningModal = ({ isOpen, adminText, onClose }: WarningModalProps) => {
const WarningModal = ({ isOpen, reportContent, onClose }: WarningModalProps) => {
const divideContents = reportContent.split('§');
if (!isOpen) return null;

return (
<ModalOverlay closeOnOutsideClick onClose={onClose}>
<article
Expand All @@ -21,15 +21,18 @@ const WarningModal = ({ isOpen, adminText, onClose }: WarningModalProps) => {
>
<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">{adminText}</p>

<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 mb-5 text-black">{divideContents[0]}</p>

<h2 className="body-sb mb-1.5 text-gray-100">현재 경고 누적</h2>
<p className="caption-r mb-5 text-black">{`${divideContents[1]} 회`}</p>

<h2 className="body-sb mb-1.5 text-gray-100">경고 규칙</h2>
<p className="caption-r text-black">
1회 경고: 주의 안내
Expand Down
1 change: 1 addition & 0 deletions src/pages/Notifications/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const NOTIFICATION_ICON: Record<
string,
React.ComponentType<React.SVGProps<SVGSVGElement>>
> = {
SENDING: EnvelopeIcon,
LETTER: EnvelopeIcon,
REPORT: SirenFilledIcon,
SHARE: BoardIcon,
Expand Down
26 changes: 22 additions & 4 deletions src/pages/Notifications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,29 @@ import PageTitle from '@/components/PageTitle';

import NotificationItem from './components/NotificationItem';
import WarningModal from './components/WarningModal';
import SendingModal from './components/SendingModal';

const NotificationsPage = () => {
const navigate = useNavigate();

const [noti, setNoti] = useState<Noti[]>([]);

const [isOpenWarningModal, setIsOpenWarningModal] = useState(false);
const [isOpenSendingModal, setIsOpenSendingModal] = useState(false);

const [adminText, setAdmintext] = useState<string>('');
const [reportContent, setReportContent] = useState<string>('');

// MEMO : 편지 데이터 전송중 데이터도 추가될건데 나중에 데이터 추가되면 코드 업데이트 하긔
const handleClickItem = (alarmType: string, content?: string | number) => {
if (alarmType === 'SENDING') {
setIsOpenSendingModal(true);
}
if (alarmType === 'LETTER') {
navigate(`/letter/${content}`);
}
if (alarmType === 'REPORT') {
setIsOpenWarningModal(true);
if (typeof content === 'string') setAdmintext(content);
if (typeof content === 'string') setReportContent(content);
}
if (alarmType === 'SHARE') {
navigate(`/board/letter/${content}`, { state: { isShareLetterPreview: true } });
Expand All @@ -50,7 +55,16 @@ const NotificationsPage = () => {

const handlePatchReadNotificationAll = async () => {
const res = await patchReadNotificationAll();
if (res?.status !== 200) {
if (res?.status === 200) {
setNoti((currentNoti) => {
return currentNoti.map((noti) => {
if (!noti.read) {
return { ...noti, read: true };
}
return noti;
});
});
} else {
console.log('모두 읽음처리 에러 발생');
}
};
Expand All @@ -63,9 +77,13 @@ const NotificationsPage = () => {
<>
<WarningModal
isOpen={isOpenWarningModal}
adminText={adminText}
reportContent={reportContent}
onClose={() => setIsOpenWarningModal(false)}
/>
<SendingModal
isOpenSendingModal={isOpenSendingModal}
setIsOpenSendingModal={setIsOpenSendingModal}
/>
<main className="flex grow flex-col items-center px-5 pt-20 pb-9">
<PageTitle className="mb-10">알림</PageTitle>
<button
Expand Down
Loading