Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#165 feat connect alarm api #221

Merged
merged 9 commits into from
Jan 27, 2024
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
98 changes: 87 additions & 11 deletions public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,83 @@
function parseNestedJson(jsonString) {
try {
let formattedString = jsonString.trim().replace(/\n/g, '').replace(/ {2}/g, '');
let outerJson = JSON.parse(formattedString);
let innerJsonString = outerJson.body;
let correctedInnerJsonString = innerJsonString.replace(/\\"/g, '"');
let innerJson = JSON.parse(correctedInnerJsonString);
return innerJson;
} catch (e) {
return `Error parsing JSON: ${e.message}`;
}
}

function createNotificationBody(data) {
if (!data || !data.spaceEventInfo || !data.memberEventInfo) {
return '필요한 데이터가 누락되었습니다.';
}

const spaceTitle = data.spaceEventInfo.spaceTitle ? data.spaceEventInfo.spaceTitle : '여행지 미정';
const memberNickname = data.memberEventInfo.memberNickname;
let notificationBody = '';

switch (data.eventType) {
case 'VOTE_CREATED':
if (!data.voteEventInfo) {
return `[${spaceTitle}] ${memberNickname} 님이 새로운 투표를 생성했습니다.`;
}
// eslint-disable-next-line no-case-declarations
const voteTitle = data.voteEventInfo.voteTitle;
notificationBody = `[${spaceTitle}] ${memberNickname} 님이 새로운 투표 “${voteTitle}”를 생성했습니다.`;
break;
case 'VOTE_DONE':
if (!data.voteEventInfo) {
return `[${spaceTitle}] 투표가 최종 확정되었습니다.`;
}
// eslint-disable-next-line no-case-declarations
const voteTitleDone = data.voteEventInfo.voteTitle;
notificationBody = `[${spaceTitle}] “${voteTitleDone}” 투표가 최종 확정되었습니다.`;
break;
case 'VOTE_DELETED':
if (!data.voteEventInfo) {
return `[${spaceTitle}] ${memberNickname} 님이 투표를 삭제했습니다.`;
}
// eslint-disable-next-line no-case-declarations
const voteTitleDeleted = data.voteEventInfo.voteTitle;
notificationBody = `[${spaceTitle}] ${memberNickname} 님이 “${voteTitleDeleted}”의 투표를 삭제했습니다.`;
break;
case 'MEMBER_INVITED':
notificationBody = `[${spaceTitle}] 에 ${memberNickname} 님이 초대되었습니다.`;
break;
case 'MEMBER_EXIT':
notificationBody = `[${spaceTitle}] ${memberNickname} 님이 여행 스페이스를 나갔습니다.`;
break;
case 'NEW_JOURNEY_ADDED':
// eslint-disable-next-line no-case-declarations
const voteTitleCandidateAdd = data.voteEventInfo.voteTitle;
notificationBody = `[${spaceTitle}] ${voteTitleCandidateAdd} 의 후보가 일정에 추가 되었습니다.`;
break;
case 'CANDIDATE_ADDED':
// eslint-disable-next-line no-case-declarations
const voteTitleAdd = data.voteEventInfo.voteTitle;
notificationBody = `[${spaceTitle}] ${memberNickname} 님이 "${voteTitleAdd}"에 새로운 후보를 등록했습니다.`;
break;
case 'SPACE_LOCATION_CHANGED':
notificationBody = `여행지가 [${data.spaceEventInfo.oldTitle || '여행지 미정'}]에서 [${
data.spaceEventInfo.spaceTitle
}]으로 변경되었습니다.`;
break;
case 'SPACE_SCHEDULE_CHANGED':
notificationBody = `여행날짜가 [${data.spaceEventInfo.oldDates || '날짜 미정'}]에서 [${
data.spaceEventInfo.changeDate
}]으로 변경되었습니다.`;
break;
default:
notificationBody = '알 수 없는 이벤트입니다.';
}

return notificationBody;
}

self.addEventListener('install', function () {
console.log('[FCM SW] 설치중..');
self.skipWaiting();
Expand All @@ -9,20 +89,16 @@ self.addEventListener('activate', function () {

self.addEventListener('push', function (e) {
if (!e.data.json()) return;

const resultData = e.data.json().notification;
// 필수! 알람 제목
const notificationTitle = resultData.title;
const resultDataString = typeof resultData === 'string' ? resultData : JSON.stringify(resultData);
const parsedResultData = parseNestedJson(resultDataString);
console.log(parsedResultData);

const notificationTitle = 'TRIPVOTE';
const notificationOptions = {
//필수! 알람 설명
body: resultData.body,
//필수! 알람 이미지(프로필, 로고)
icon: resultData.image,
//필수! 알람 분류태그 (전체, 투표, 나가기...)
tag: resultData.tag,
...resultData,
body: createNotificationBody(parsedResultData),
...parsedResultData,
};
console.log('출력');

self.registration.showNotification(notificationTitle, notificationOptions);
});
24 changes: 24 additions & 0 deletions src/api/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,27 @@ export const GetNotification = async () => {
console.log('[notification]알림내용을 가져오지 못했습니다');
}
};

export const GetAlarm = async () => {
try {
const response = await axios.get('/api/notifications', {withCredentials: true});
return response;
} catch (error) {
if (axios.isAxiosError(error)) {
console.log(error);
return error.response;
}
}
};

export const PostReadAlarm = async (notiId: number) => {
try {
const response = await axios.patch(`/api/notifications/${notiId}/read`, {withCredentials: true});
return response;
} catch (error) {
if (axios.isAxiosError(error)) {
console.log(error);
return error.response;
}
}
};
4 changes: 2 additions & 2 deletions src/components/Alarm/Alarm.module.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
@use "@/sass" as *;
@use '@/sass' as *;

.page {
position: absolute;
top: 0;
width: 100%;
height: 100vh;
z-index: 9;
z-index: 11;

.slide {
position: absolute;
Expand Down
3 changes: 1 addition & 2 deletions src/components/Alarm/Alarm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ function Alarm({isAlarmOpen, alarmClose}: AlarmProps) {
useEffect(() => {
if (isAlarmOpen === true) {
localStorage.removeItem('news');
console.log('비움');
}
}, [isAlarmOpen]);

Expand All @@ -69,7 +68,7 @@ function Alarm({isAlarmOpen, alarmClose}: AlarmProps) {
}}
>
<Back alarmClose={alarmClose} />
<TabCapsule />
<TabCapsule isAlarmOpen={isAlarmOpen} />
</Slide>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "@/sass" as *;
@use '@/sass' as *;

.page {
height: calc(100vh - 86px);
Expand Down
19 changes: 11 additions & 8 deletions src/components/Alarm/TabCapsule/Content/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import styles from "./Content.module.scss";
import styles from './Content.module.scss';

import formatTimeAgo from "@/utils/formatTimeAgo";
import DefaultProfile from '@/assets/profile_default.svg?react';
import formatTimeAgo from '@/utils/formatTimeAgo';

import { ContentProps } from "@/types/alarm";
import {ContentProps} from '@/types/alarm';

function Content({ contents }: ContentProps) {
function Content({contents}: ContentProps) {
return (
<div className={styles.page}>
{contents.map((content, index) => (
<div key={index} className={styles.container}>
<img src={content.url} className={styles.container__thumbnail} />
{content.url ? (
<img src={content.url} className={styles.container__thumbnail} />
) : (
<DefaultProfile className={styles.container__thumbnail} />
)}
<div className={styles.container__wrapper}>
<p className={styles.container__wrapper__title}>{content.title}</p>
<p className={styles.container__wrapper__time}>
{formatTimeAgo(new Date(content.time))}
</p>
<p className={styles.container__wrapper__time}>{formatTimeAgo(new Date(content.time))}</p>
</div>
</div>
))}
Expand Down
64 changes: 19 additions & 45 deletions src/components/Alarm/TabCapsule/TabCapsule.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,30 @@
import {Tab, TabIndicator, TabList, TabPanel, TabPanels, Tabs} from '@chakra-ui/react';
import {useNavigate} from 'react-router-dom';

import styles from './TabCapsule.module.scss';

import {useGetAlarm} from '@/hooks/Notification/useNotification';

import {PostReadAlarm} from '@/api/notification';
import {parsingAlarmTravel} from '@/utils/parsingAlarm';

import Content from './Content/Content';

const AllContents = [
{
url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg',
title: `[강릉 여행]강자밭님이 "밥집 어디갈꺼야" 투표를 만들었어요.`,
time: '2024-01-11T23:09:00',
},
{
url: 'https://github.com/Strong-Potato/TripVote-FE/assets/120024673/089a8673-405e-4a06-b173-4cf3e985b466',
title: '개인정보 보호정책이 변경되었습니다.',
time: '2024-01-11T20:00:00',
},
{
url: 'https://github.com/Strong-Potato/TripVote-FE/assets/120024673/089a8673-405e-4a06-b173-4cf3e985b466',
title: '개인정보 보호정책이 변경되었습니다.',
time: '2024-01-10T10:00:00',
},
{
url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg',
title: `[강릉 여행]강자밭님이 "카페 어디갈꺼야" 투표를 만들었어요.`,
time: '2024-01-03T12:00:00',
},
{
url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg',
title: `[강릉 여행]강자밭님이 "숙소 어디갈꺼야" 투표를 만들었어요.`,
time: '2023-12-31T12:00:00',
},
];
function TabCapsule({isAlarmOpen}: {isAlarmOpen: boolean}) {
const navigate = useNavigate();
const {data: Alarm} = useGetAlarm(isAlarmOpen);
localStorage.removeItem('news');

Alarm && PostReadAlarm(Alarm?.data.data.notificationDetail[0].id);
if (Alarm?.status === 403 || Alarm?.status === 401) {
navigate('/auth/login', {
replace: true,
});
}

const SpaceTravelContents = [
{
url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg',
title: `[강릉 여행]강자밭님이 "밥집 어디갈꺼야" 투표를 만들었어요.`,
time: '2024-01-11T23:05:00',
},
{
url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg',
title: `[강릉 여행]강자밭님이 "카페 어디갈꺼야" 투표를 만들었어요.`,
time: '2024-01-03T12:00:00',
},
{
url: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg',
title: `[강릉 여행]강자밭님이 "숙소 어디갈꺼야" 투표를 만들었어요.`,
time: '2023-12-31T12:00:00',
},
];
const AllContents = parsingAlarmTravel(Alarm?.data.data.notificationDetail);
const SpaceTravelContents = parsingAlarmTravel(Alarm?.data.data.notificationDetail);

function TabCapsule() {
return (
<Tabs isFitted variant='unstyled'>
<TabList>
Expand Down
14 changes: 11 additions & 3 deletions src/components/Home/TabBar/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@ import {Link} from 'react-router-dom';

import styles from './TabBar.module.scss';

function TabBar() {
import {useGetAlarm} from '@/hooks/Notification/useNotification';

import {Alarmprop} from '@/types/alarm';

function TabBar({onAlarmOpen}: Alarmprop) {
const news = localStorage.getItem('news');
const {data: Alarm} = useGetAlarm(true);
if (Alarm?.data.data.notificationDetail[0].isRead === false) {
localStorage.setItem('news', 'true');
}

return (
<div className={styles.container}>
<div className={styles.icons}>
<Link to='/search'>
<IoSearchSharp />
</Link>
<Link to='/alarm' className={styles.icons__wrapper}>
<button onClick={onAlarmOpen} className={styles.icons__wrapper}>
<AiOutlineBell />
{news && <div className={styles.icons__wrapper__eclips} />}
</Link>
</button>
</div>
</div>
);
Expand Down
12 changes: 6 additions & 6 deletions src/components/Modal/Invitation/Invitation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {InvitationProps} from '@/types/Invitation';

function Invitation({inviteCode, modal}: InvitationProps) {
const [isFull, setIsFull] = useRecoilState(isFullMember);
const [, , removeCookie] = useCookies(['join_space_token']);
const [, setcookie] = useCookies(['join_space_token']);
const {data} = useGetMyInfo(true);
const navigate = useNavigate();
const parsedInviteCode = parseInviteCode(inviteCode);
Expand All @@ -33,10 +33,10 @@ function Invitation({inviteCode, modal}: InvitationProps) {
const handleJoin = async () => {
const response = await postJoin(parsedInviteCode!.space_id);
if (response.status === 200) {
removeCookie('join_space_token', {path: '/'});
setcookie('join_space_token', '');
navigate(`/trip/${parsedInviteCode!.space_id}`);
} else if (response.status === 400) {
removeCookie('join_space_token', {path: '/'});
setcookie('join_space_token', '');
modal(false);
setIsFull(true);
}
Expand All @@ -62,7 +62,7 @@ function Invitation({inviteCode, modal}: InvitationProps) {
className={styles.wrapperInvalidButton__confirm}
onClick={() => {
modal(false);
removeCookie('join_space_token', {path: '/'});
setcookie('join_space_token', '');
}}
>
확인
Expand All @@ -87,7 +87,7 @@ function Invitation({inviteCode, modal}: InvitationProps) {
className={styles.wrapperButton__cancel}
onClick={() => {
modal(false);
removeCookie('join_space_token', {path: '/'});
setcookie('join_space_token', '');
}}
>
취소
Expand All @@ -114,7 +114,7 @@ function Invitation({inviteCode, modal}: InvitationProps) {
className={styles.wrapperButton__cancel}
onClick={() => {
modal(false);
removeCookie('join_space_token', {path: '/'});
setcookie('join_space_token', '');
}}
>
취소
Expand Down
2 changes: 1 addition & 1 deletion src/components/SideBar/TravelList/TravelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function TravelList({isSideOpen}: TravelListProp) {
{spaces.data.spaces.map((item, index) => (
<li key={index}>
<button className={styles.travelSpaceList__items__item} onClick={() => navigate(`/trip/${item.id}`)}>
<p className={styles.travelSpaceList__items__item__name}>{item.city}</p>
<p className={styles.travelSpaceList__items__item__name}>{item.city ? item.city : `여행지 미정`}</p>
<p className={styles.travelSpaceList__items__item__date}>
{item.startDate && item.endDate ? setSpaceDate(item.startDate, item.endDate) : '날짜 미정'}
</p>
Expand Down
Loading
Loading