From 292ebdbe3466acc2473c262cfda2c360f3f19011 Mon Sep 17 00:00:00 2001 From: WCY-dt <834421194@qq.com> Date: Tue, 7 May 2024 10:36:36 +0800 Subject: [PATCH] Refactor notification and use sessionstorage --- src/components/header.tsx | 8 ++-- src/components/linkCard.tsx | 8 ++-- src/contexts/context.tsx | 33 ++----------- src/hooks/notificationContext.tsx | 75 ++++++++++++++++++++++++++++++ src/popups/login.tsx | 12 +++-- src/popups/notification.tsx | 63 ------------------------- src/routers/router.tsx | 8 ++-- src/styles/popups/notification.css | 2 +- src/utils/randomColor.ts | 6 +-- typings.d.ts | 6 +-- 10 files changed, 109 insertions(+), 112 deletions(-) create mode 100644 src/hooks/notificationContext.tsx delete mode 100644 src/popups/notification.tsx diff --git a/src/components/header.tsx b/src/components/header.tsx index 59958f6..60ab15b 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useContext } from 'react'; import { Icon } from '@iconify/react'; import { Link } from 'react-router-dom'; +import { useNotification } from '../hooks/notificationContext'; import { AppContext } from '../contexts/context'; import Login from '../popups/login'; @@ -14,10 +15,11 @@ function Header() { showMobileMenu, setShowMobileMenu, routes, setToken, - dispatchNotification, searchTerm, setSearchTerm } = useContext(AppContext); + const setNotification = useNotification(); + const inputRef = useRef(null); useEffect(() => { if (inputRef.current) { @@ -44,8 +46,8 @@ function Header() { const onClickLogout = () => { setLogin(false); setToken(''); - localStorage.removeItem('token'); - dispatchNotification({ type: 'SUCCESS', message: 'Logout' }); + sessionStorage.removeItem('token'); + setNotification({ type: 'SUCCESS', message: 'logout' }); }; return ( diff --git a/src/components/linkCard.tsx b/src/components/linkCard.tsx index b4f734c..a2936fe 100644 --- a/src/components/linkCard.tsx +++ b/src/components/linkCard.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { Icon } from '@iconify/react'; import { v4 as uuidv4 } from 'uuid'; +import { useNotification } from '../hooks/notificationContext'; import { AppContext } from '../contexts/context'; import { getRandomColor, getContrastColor } from '../utils/randomColor'; import deleteCardHandler from '../services/deleteCardHandler'; @@ -16,7 +17,6 @@ function LinkCard({ item }: LinkCardProps) { const { isLogin, token, - dispatchNotification, setShowConfirm, setConfirmMessage, setConfirmAction, @@ -26,6 +26,8 @@ function LinkCard({ item }: LinkCardProps) { setEditType } = useContext(AppContext); + const setNotification = useNotification(); + const id = item.Id || 0; const backgroundColor = getRandomColor(item.Category); @@ -46,9 +48,9 @@ function LinkCard({ item }: LinkCardProps) { const deleteCardResult = await deleteCardHandler({ id, token }); if (deleteCardResult === true) { - dispatchNotification({ type: 'SUCCESS', message: 'Card delete' }); + setNotification({ type: 'SUCCESS', message: 'Card delete' }); } else { - dispatchNotification({ type: 'ERROR', message: 'Card delete' }); + setNotification({ type: 'ERROR', message: 'Card delete' }); } }; diff --git a/src/contexts/context.tsx b/src/contexts/context.tsx index 82be726..903ff47 100644 --- a/src/contexts/context.tsx +++ b/src/contexts/context.tsx @@ -1,25 +1,6 @@ -import React, { createContext, useState, useEffect, useReducer } from 'react'; +import React, { createContext, useState, useEffect } from 'react'; import { fetchCollection } from '../services/collectionFetcher'; -export type NotificationState = { - type: string; - message: string; -}; -export type NotificationAction = { - type: 'SUCCESS' | 'ERROR'; - message: string; -}; -const notificationReducer = (state: NotificationState, action: NotificationAction) => { - switch (action.type) { - case 'SUCCESS': - return { ...state, type: 'SUCCESS', message: action.message }; - case 'ERROR': - return { ...state, type: 'ERROR', message: action.message }; - default: - return state; - } -} - export const AppContext = createContext({ isLoading: false, setLoading: (_: boolean) => {}, @@ -35,9 +16,7 @@ export const AppContext = createContext({ setRoutes: (_: {[_: string]: React.ReactNode}) => {}, token: '', setToken: (_: string) => {}, - notification: { type: '', message: '' }, - dispatchNotification: (_: NotificationAction) => {}, - showConfirm: false, + showConfirm: false, setShowConfirm: (_: boolean) => {}, confirmMessage: '', setConfirmMessage: (_: string) => {}, @@ -63,7 +42,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const [needReload, setNeedReload] = useState(false); const [routes, setRoutes] = useState({}); const [token, setToken] = useState(''); - const [notification, dispatchNotification] = useReducer(notificationReducer, { type: '', message: '' }); const [showConfirm, setShowConfirm] = useState(false); const [confirmMessage, setConfirmMessage] = useState(''); const [confirmAction, setConfirmAction] = useState<() => void>(() => () => {}); @@ -80,14 +58,14 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { }, []); useEffect(() => { - if (localStorage.getItem('token')) { + if (sessionStorage.getItem('token')) { setLogin(true); - setToken(localStorage.getItem('token') as string); + setToken(sessionStorage.getItem('token') as string); } }, []); useEffect(() => { - localStorage.removeItem('colorMap'); + sessionStorage.removeItem('colorMap'); }, []); return ( @@ -99,7 +77,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { needReload, setReload: setNeedReload, routes, setRoutes, token, setToken, - notification, dispatchNotification, showConfirm, setShowConfirm, confirmMessage, setConfirmMessage, confirmAction, setConfirmAction, diff --git a/src/hooks/notificationContext.tsx b/src/hooks/notificationContext.tsx new file mode 100644 index 0000000..e6d9446 --- /dev/null +++ b/src/hooks/notificationContext.tsx @@ -0,0 +1,75 @@ +import React, { createContext, useState, useReducer, useEffect, useContext } from 'react'; + +import '../styles/popups/notification.css'; + +type NotificationAction = { + type: 'SUCCESS' | 'ERROR'; + message: string; +}; + +interface NotificationContextType { + notification: NotificationAction; + updateNotification: (action: NotificationAction) => void; +} + +const NotificationContext = createContext(null); + +const SUCCESS_MESSAGE_SUFFIX = ' successfully'; +const ERROR_MESSAGE_SUFFIX = ' failed, please try again'; + +const notificationReducer = (state: NotificationAction, action: NotificationAction) => { + switch (action.type) { + case 'SUCCESS': + return { ...action, message: action.message + SUCCESS_MESSAGE_SUFFIX } + case 'ERROR': + return { ...action, message: action.message + ERROR_MESSAGE_SUFFIX } + default: + return state; + } +}; + +const VISIBILITY_DELAY = 2000; + +export const NotificationProvider = ({ children }: { children: React.ReactNode }) => { + const [notification, dispatch] = useReducer(notificationReducer, { type: 'SUCCESS', message: '' }); + const [showNotification, setShowNotification] = useState(false); + + const updateNotification = (action: NotificationAction) => { + dispatch(action); + }; + + useEffect(() => { + let visibilityTimer: NodeJS.Timeout | null = null; + + if (notification.message) { + setShowNotification(true); + + visibilityTimer = setTimeout(() => { + setShowNotification(false); + }, VISIBILITY_DELAY); + } + + return () => { + if (visibilityTimer) { + clearTimeout(visibilityTimer); + } + }; + }, [notification]); + + return ( + + {children} +
+ {notification.message} +
+
+ ); +}; + +export const useNotification = () => { + const context = useContext(NotificationContext); + if (!context) { + throw new Error('useNotification must be used within a NotificationProvider'); + } + return context.updateNotification; +}; diff --git a/src/popups/login.tsx b/src/popups/login.tsx index 3d9e029..94e96d4 100644 --- a/src/popups/login.tsx +++ b/src/popups/login.tsx @@ -1,6 +1,7 @@ import React, { useContext } from 'react'; import { Icon } from '@iconify/react'; +import { useNotification } from '../hooks/notificationContext'; import { AppContext } from '../contexts/context'; import loginHandler from '../services/loginHandler'; @@ -11,10 +12,11 @@ function Login() { const { setShowLogin, setLogin, - token, setToken, - dispatchNotification + token, setToken } = useContext(AppContext); + const setNotification = useNotification(); + const onClickLogin = async () => { const username = (document.getElementById("username") as HTMLInputElement).value; const password = (document.getElementById("password") as HTMLInputElement).value; @@ -22,11 +24,11 @@ function Login() { if (loginResult === true) { setShowLogin(false); - localStorage.setItem('token', token); + sessionStorage.setItem('token', token); setLogin(true); - dispatchNotification({ type: 'SUCCESS', message: 'Login' }); + setNotification({ type: 'SUCCESS', message: 'login' }); } else { - dispatchNotification({ type: 'ERROR', message: 'Login' }); + setNotification({ type: 'ERROR', message: 'login' }); } }; diff --git a/src/popups/notification.tsx b/src/popups/notification.tsx deleted file mode 100644 index b5a7af0..0000000 --- a/src/popups/notification.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useState, useEffect, useContext, Dispatch, SetStateAction } from 'react'; - -import { AppContext, NotificationState } from '../contexts/context'; -import '../styles/popups/notification.css'; - -function useNotificationMessage(notification: NotificationState) { - const [notificationMessage, setNotificationMessage] = useState(null); - - useEffect(() => { - switch (notification.type) { - case 'SUCCESS': - setNotificationMessage(notification.message + ' successfully'); - break; - case 'ERROR': - setNotificationMessage(notification.message + ' failed, please try again'); - break; - default: - setNotificationMessage(null); - } - }, [notification]); - - return [notificationMessage, setNotificationMessage] as const; -} - -function useNotificationVisibility(notificationMessage: string | null, setNotificationMessage: Dispatch>) { - const [showNotification, setShowNotification] = useState(false); - - useEffect(() => { - if (notificationMessage) { - setShowNotification(true); - - const visibilityTimer = setTimeout(() => { - setShowNotification(false); - }, 2000); - const messageTimer = setTimeout(() => { - setNotificationMessage(null); - }, 4000); - - return () => { - clearTimeout(visibilityTimer); - clearTimeout(messageTimer); - }; - } - return; - }, [notificationMessage, setNotificationMessage]); - - return showNotification; -} - -function Notification() { - const { notification } = useContext(AppContext); - - const [notificationMessage, setNotificationMessage] = useNotificationMessage(notification); - const showNotification = useNotificationVisibility(notificationMessage, setNotificationMessage); - - return ( -
- {notificationMessage} -
- ); -} - -export default Notification; diff --git a/src/routers/router.tsx b/src/routers/router.tsx index 6f39efc..b8cd585 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -2,10 +2,11 @@ import React, { useContext } from 'react'; import { BrowserRouter } from 'react-router-dom'; import { AppContext, AppProvider } from '../contexts/context'; +import { NotificationProvider } from '../hooks/notificationContext'; import Header from '../components/header'; import Content from '../components/content'; import Footer from '../components/footer'; -import Notification from '../popups/notification'; +// import Notification from '../popups/notification'; import Confirm from '../popups/confirm'; import Edit from '../popups/edit'; import Overlay from '../popups/overlay'; @@ -15,7 +16,7 @@ import '../styles/popups/loading.css'; function Router() { - const { isLoading: isLoading } = useContext(AppContext); + const { isLoading } = useContext(AppContext); if (isLoading) { return ( @@ -25,16 +26,17 @@ function Router() { return ( +