diff --git a/package.json b/package.json index 3218e0b6..5dbd103b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@types/react-signature-canvas": "^1.0.2", "@types/react-transition-group": "^4.4.5", "@types/storybook-react-router": "^1.0.1", + "@types/throttle-debounce": "^5.0.0", "axios": "^0.27.2", "crypto-js": "^4.1.1", "dayjs": "^1.11.5", @@ -53,6 +54,7 @@ "styled-components": "^5.3.5", "styled-reset": "^4.4.1", "swiper": "6.8.4", + "throttle-debounce": "^5.0.0", "typescript": "^4.7.2", "web-vitals": "^2.1.4" }, diff --git a/src/components/blocks/home/NoFamily.tsx b/src/components/blocks/home/NoFamily.tsx index db095bf9..0c748e9e 100644 --- a/src/components/blocks/home/NoFamily.tsx +++ b/src/components/blocks/home/NoFamily.tsx @@ -64,7 +64,7 @@ function NoFamily({ variant = 'Home' }: NoFamilyProps) {

navigate('/mypage', { state: { prev: '/' } })} + onClick={() => navigate('/mypage', { state: { background: '/' } })} /> diff --git a/src/components/blocks/home/NotificationList.tsx b/src/components/blocks/home/NotificationList.tsx deleted file mode 100644 index ec8f6a2d..00000000 --- a/src/components/blocks/home/NotificationList.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import styled from 'styled-components'; -import { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { INotification } from '@lib/apis/notification/notificationDTO'; -import { ReactComponent as AlertDongil } from '@assets/icons/alertDongil.svg'; -import { ReactComponent as AlertNotice } from '@assets/icons/alertNotice.svg'; -import { ReactComponent as AlertLevel } from '@assets/icons/alertLevel.svg'; -import { ReactComponent as AlertFamily } from '@assets/icons/alertFamily.svg'; -import getTimeForToday from '@lib/utils/getTimeForToday'; -import notificationAPI from '@lib/apis/notification/notificationAPI'; - -const NotificationList = ({ - notifications, -}: { - notifications: INotification[]; -}) => { - const navigate = useNavigate(); - const icon = { - CHALLENGE: , - NOTICE: , - LEVEL: , - FAMILY: , - }; - const category = { - CHALLENGE: '돈길', - NOTICE: '공지', - LEVEL: '레벨', - FAMILY: '가족', - }; - - const onNotificationItemClick = useCallback( - async (id: number, isRead: boolean, url: string) => { - if (!isRead) { - await notificationAPI.patchNotificationIsRead(id); - } - url && navigate(url); - }, - [], - ); - - return ( - - {notifications.map((notification) => ( - - onNotificationItemClick( - notification.id, - notification.isRead, - notification.linkUrl, - ) - } - > - {icon[notification.notificationCategory]} -
-
-

{category[notification.notificationCategory]}

-

{getTimeForToday(notification.createdAt)}

-
-
-

{notification.title}

-

{notification.message}

-
-
-
- ))} -
- ); -}; - -export default NotificationList; - -const Wrapper = styled.div``; - -const Item = styled.div<{ isRead: boolean }>` - display: grid; - grid-template-columns: 48px auto; - grid-gap: 16px; - padding: 16px 18px 20px 18px; - background-color: ${({ isRead, theme }) => - isRead ? 'none' : theme.palette.main.yellow100}; - cursor: pointer; - .sub { - display: flex; - justify-content: space-between; - margin-bottom: 8px; - & > p:first-child { - ${({ theme }) => theme.typo.text.T_12_EB} - color: ${({ theme }) => theme.palette.greyScale.grey500}; - } - & > p:last-child { - ${({ theme }) => theme.typo.text.Alarm_T_12_R} - color: ${({ theme }) => theme.palette.greyScale.grey500}; - } - } - - .main { - ${({ theme }) => theme.typo.text.Alarm_T_14_R} - line-height: 150%; - color: ${({ theme }) => theme.palette.greyScale.black}; - } - - svg { - margin: auto 0px; - } -`; diff --git a/src/components/blocks/home/create/steps/Step5.tsx b/src/components/blocks/home/create/steps/Step5.tsx index 1e3e50f5..1bfd534c 100644 --- a/src/components/blocks/home/create/steps/Step5.tsx +++ b/src/components/blocks/home/create/steps/Step5.tsx @@ -2,6 +2,7 @@ import dayjs from 'dayjs'; import { useEffect, useState } from 'react'; import { useMutation, useQueryClient } from 'react-query'; import { useNavigate } from 'react-router-dom'; +import { debounce } from 'throttle-debounce'; import usePresignedUrl from '../utils/usePresignedUrl'; import ContractSheet from '@components/atoms/bottomSheets/contractSheet/ContractSheet'; import Signature from '@components/atoms/bottomSheets/contractSheet/Signature'; @@ -56,10 +57,10 @@ function Step5() { }, []); // 다음으로 버튼 클릭 - const onClickNextButton = () => { + const onClickNextButton = debounce(1000, () => { uploadS3(sign); mutatePostChallenge(createChallengePayload); - }; + }); return ( <> diff --git a/src/components/blocks/home/homeTemplate/FixedBar.tsx b/src/components/blocks/home/homeTemplate/FixedBar.tsx index 9dee6c0c..b93b336b 100644 --- a/src/components/blocks/home/homeTemplate/FixedBar.tsx +++ b/src/components/blocks/home/homeTemplate/FixedBar.tsx @@ -49,7 +49,16 @@ function FixedBar({ variant = 'Home' }: FixedBarProps) { hasMultipleKids={hasMultipleKids!} isAllRead={isAllRead!} > -
navigate('/notification')}> +
+ navigate('/notification', { + state: { + returnBackgroundTo: variant === 'Home' ? '/' : '/interest', + }, + }) + } + >
{headerText} diff --git a/src/components/blocks/home/notification/NotificationItem.tsx b/src/components/blocks/home/notification/NotificationItem.tsx new file mode 100644 index 00000000..b83517ca --- /dev/null +++ b/src/components/blocks/home/notification/NotificationItem.tsx @@ -0,0 +1,84 @@ +import styled from 'styled-components'; +import { INotification } from '@lib/apis/notification/notificationDTO'; +import { ReactComponent as AlertDongil } from '@assets/icons/alertDongil.svg'; +import { ReactComponent as AlertNotice } from '@assets/icons/alertNotice.svg'; +import { ReactComponent as AlertLevel } from '@assets/icons/alertLevel.svg'; +import { ReactComponent as AlertFamily } from '@assets/icons/alertFamily.svg'; +import getTimeForToday from '@lib/utils/getTimeForToday'; + +const icon = { + CHALLENGE: , + NOTICE: , + LEVEL: , + FAMILY: , +}; +const category = { + CHALLENGE: '돈길', + NOTICE: '공지', + LEVEL: '레벨', + FAMILY: '가족', +}; + +export interface NotificationItemProps { + notification: INotification; + isRead: boolean; + handleListItemClick: () => void; +} + +const NotificationItem = ({ + notification, + isRead, + handleListItemClick, +}: NotificationItemProps) => { + const { id, notificationCategory, createdAt, title, message } = notification; + return ( + + {icon[notificationCategory]} +
+
+

{category[notificationCategory]}

+

{getTimeForToday(createdAt)}

+
+
+

{title}

+

{message}

+
+
+
+ ); +}; + +export default NotificationItem; + +const Wrapper = styled.div<{ isRead: boolean }>` + display: grid; + grid-template-columns: 48px auto; + grid-gap: 16px; + padding: 16px 18px 20px 18px; + background-color: ${({ isRead, theme }) => + isRead ? 'none' : theme.palette.main.yellow100}; + cursor: pointer; + .sub { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + & > p:first-child { + ${({ theme }) => theme.typo.text.T_12_EB} + color: ${({ theme }) => theme.palette.greyScale.grey500}; + } + & > p:last-child { + ${({ theme }) => theme.typo.text.Alarm_T_12_R} + color: ${({ theme }) => theme.palette.greyScale.grey500}; + } + } + + .main { + ${({ theme }) => theme.typo.text.Alarm_T_14_R} + line-height: 150%; + color: ${({ theme }) => theme.palette.greyScale.black}; + } + + svg { + margin: auto 0px; + } +`; diff --git a/src/components/blocks/home/notification/NotificationList.tsx b/src/components/blocks/home/notification/NotificationList.tsx new file mode 100644 index 00000000..0434e64a --- /dev/null +++ b/src/components/blocks/home/notification/NotificationList.tsx @@ -0,0 +1,63 @@ +import { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import NotificationItem, { NotificationItemProps } from './NotificationItem'; +import { INotification } from '@lib/apis/notification/notificationDTO'; +import notificationAPI from '@lib/apis/notification/notificationAPI'; + +interface NotificationListProps { + notifications: INotification[]; +} + +const NotificationList = ({ notifications }: NotificationListProps) => { + return ( + <> + {notifications.map((notification) => ( + + {({ notification, handleListItemClick, isRead }) => ( + + )} + + ))} + + ); +}; + +export default NotificationList; + +interface NotificationListHeadlessProps { + notification: INotification; + children: (args: NotificationItemProps) => JSX.Element; +} + +const NotificationListHeadless = ({ + notification, + children, +}: NotificationListHeadlessProps) => { + const { isRead, id, linkUrl } = notification; + const [isReadClient, setIsReadClient] = useState(isRead); + const navigate = useNavigate(); + + const onNotificationItemClick = useCallback( + async (id: number, url: string) => { + if (!isReadClient) { + await notificationAPI.patchNotificationIsRead(id); + setIsReadClient(true); + } + url && navigate(url); + }, + [isReadClient], + ); + + return children({ + notification: notification, + handleListItemClick: () => onNotificationItemClick(id, linkUrl), + isRead: isReadClient, + }); +}; diff --git a/src/components/pages/Home/Create.tsx b/src/components/pages/Home/Create.tsx index 6604c7e1..139eb190 100644 --- a/src/components/pages/Home/Create.tsx +++ b/src/components/pages/Home/Create.tsx @@ -118,16 +118,16 @@ function Create() { step={step} skipSelectParents={status === 'success' && isAlone ? true : false} /> - - - - {status === 'success' && isAlone - ? title[step] - : title[step - 1]} - {renderContent(step)} - - - + {/* */} + + + {status === 'success' && isAlone + ? title[step] + : title[step - 1]} + {renderContent(step)} + + + {/* */} )} diff --git a/src/components/pages/Home/Notification.tsx b/src/components/pages/Home/Notification.tsx index 7f96ab5b..d10692bd 100644 --- a/src/components/pages/Home/Notification.tsx +++ b/src/components/pages/Home/Notification.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; import ForegroundTemplate from '@components/atoms/layout/ForegroundTemplate'; import { ReactComponent as Banki } from '@assets/icons/giveUpExceeded.svg'; import useInfiniteNotificationQuery from '@lib/hooks/queries/useInfiniteNotificationQuery'; -import NotificationList from '@components/blocks/home/NotificationList'; +import NotificationList from '@components/blocks/home/notification/NotificationList'; import LoadingSpinner from '@components/atoms/loaders/LoadingSpinner'; const Notification = () => { diff --git a/src/lib/hooks/globalErrorHandler/useAPIError.tsx b/src/lib/hooks/globalErrorHandler/useAPIError.tsx index 0275149d..dbdd6e42 100644 --- a/src/lib/hooks/globalErrorHandler/useAPIError.tsx +++ b/src/lib/hooks/globalErrorHandler/useAPIError.tsx @@ -58,7 +58,7 @@ const handle401default = () => { }; const handle500default = () => { toast.error( - '예상치 못한 서버 오류가 발생했어요. 오류가 반복되는 경우 앱을 종료하고 다시 실행해주세요.', + '예상치 못한 서버 오류가 발생했어요. 오류가 반복되면 앱을 종료하고 다시 실행해주세요.', ); }; diff --git a/src/lib/utils/getTimeForToday.ts b/src/lib/utils/getTimeForToday.ts index a9bbf706..15c8aa91 100644 --- a/src/lib/utils/getTimeForToday.ts +++ b/src/lib/utils/getTimeForToday.ts @@ -4,6 +4,7 @@ const getTimeForToday = (date: string) => { const betweenTime = Math.floor( (today.getTime() - timeValue.getTime()) / 1000 / 60, ); + console.log(date, timeValue.getTime()); if (betweenTime < 1) return '방금 전'; if (betweenTime < 60) { diff --git a/yarn.lock b/yarn.lock index a8e297f6..c4f77144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3708,6 +3708,11 @@ dependencies: "@types/jest" "*" +"@types/throttle-debounce@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-5.0.0.tgz#8208087f0af85107bcc681c50fa837fc9505483e" + integrity sha512-Pb7k35iCGFcGPECoNE4DYp3Oyf2xcTd3FbFQxXUI9hEYKUl6YX+KLf7HrBmgVcD05nl50LIH6i+80js4iYmWbw== + "@types/trusted-types@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" @@ -14487,6 +14492,11 @@ throat@^6.0.1: resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== +throttle-debounce@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" + integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg== + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"