From acd007fb7101ba8ce5c7167ad4e4ba6ffcc20bb8 Mon Sep 17 00:00:00 2001 From: zerosoul Date: Fri, 4 Mar 2022 10:15:30 +0800 Subject: [PATCH] feat: refactor the cache --- package.json | 1 + src/app/cache.js | 70 +++++++++ src/app/config.js | 3 + src/app/services/auth.js | 4 +- src/app/services/base.query.js | 134 +++++++++--------- src/app/services/channel.js | 15 +- src/app/services/contact.js | 15 +- src/app/services/server.js | 7 +- src/app/slices/auth.data.js | 33 +++-- src/app/slices/channels.js | 18 ++- src/app/slices/contacts.js | 17 ++- src/app/slices/message.channel.js | 24 ++-- src/app/slices/message.pending.js | 4 + src/app/slices/message.user.js | 14 +- src/app/slices/visit.mark.js | 34 +++++ src/app/store.js | 89 +++++++++--- src/common/component/AvatarUploader.js | 43 ++++-- src/common/component/NotificationHub/index.js | 119 +++++++++------- .../component/Setting/LogoutConfirmModal.js | 58 +++++++- src/common/component/Setting/Overview.js | 21 ++- src/routes/chat/ChannelChat/index.js | 4 +- src/routes/chat/DMChat/index.js | 8 +- src/routes/chat/index.js | 18 +-- src/routes/home/index.js | 12 +- src/routes/home/usePreload.js | 65 +++++---- src/routes/index.js | 116 +++++++-------- src/routes/login/SolidLoginButton.js | 3 +- yarn.lock | 14 ++ 28 files changed, 622 insertions(+), 341 deletions(-) create mode 100644 src/app/cache.js create mode 100644 src/app/slices/visit.mark.js diff --git a/package.json b/package.json index 9056ca65..32c3f9d1 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "fs-extra": "^10.0.1", "html-webpack-plugin": "^5.5.0", "linkify-it": "^3.0.3", + "localforage": "^1.10.0", "mini-css-extract-plugin": "^2.5.3", "react": "^17.0.2", "react-dev-utils": "^12.0.0", diff --git a/src/app/cache.js b/src/app/cache.js new file mode 100644 index 00000000..74b05081 --- /dev/null +++ b/src/app/cache.js @@ -0,0 +1,70 @@ +import { useState } from "react"; +import localforage from "localforage"; +import { useDispatch } from "react-redux"; +import { initChannelMsg } from "./slices/message.channel"; +import { initUserMsg } from "./slices/message.user"; +import { setChannels } from "./slices/channels"; +import { setMark } from "./slices/visit.mark"; +import { KEY_UID } from "./config"; +const initCache = () => { + const uid = localStorage.getItem(KEY_UID) || ""; + const name = `cache_db_${uid}`; + // const channels = localforage.createInstance({ + // name, + // storeName: "channels", + // description: "channel list", + // }); + // const contacts = localforage.createInstance({ + // name, + // storeName: "contacts", + // description: "contact list", + // }); + // const msgs_user = localforage.createInstance({ + // name, + // storeName: "msgs_dm", + // description: "DM message list", + // }); + // const msgs_channel = localforage.createInstance({ + // name, + // storeName: "msgs_channel", + // description: "channel message list", + // }); + window.CACHE = localforage.createInstance({ + name, + storeName: "data", + description: "rustchat cache data by uid", + }); +}; +export const useRehydrate = () => { + const [iterated, setIterated] = useState(false); + const dispatch = useDispatch(); + const rehydrate = async () => { + const res = await window.CACHE.iterate((data, key) => { + switch (key) { + case "visitMark": + dispatch(setMark(data)); + break; + case "channels": + dispatch(setChannels(data)); + break; + // case "contacts": + // dispatch(initChannelMsg(data)); + // break; + case "channelMessage": + dispatch(initChannelMsg(data)); + break; + case "userMessage": + dispatch(initUserMsg(data)); + break; + + default: + break; + } + }); + setIterated(true); + console.log("iterate", res); + }; + return { rehydrate, cacheFirst: iterated }; +}; + +export default initCache; diff --git a/src/app/config.js b/src/app/config.js index b58d3d97..ae212670 100644 --- a/src/app/config.js +++ b/src/app/config.js @@ -10,5 +10,8 @@ export const googleClientID = "418687074928-naojba82n9ktf0rkvnqoor4nhr54ql1b.apps.googleusercontent.com"; // "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com"; export const tokenHeader = "X-API-Key"; +export const KEY_TOKEN = "RUSTCHAT_TOKEN"; +export const KEY_REFRESH_TOKEN = "RUSTCHAT_REFRESH_TOKEN"; +export const KEY_UID = "RUSTCHAT_CURR_UID"; export default BASE_URL; diff --git a/src/app/services/auth.js b/src/app/services/auth.js index 5f41dae4..3134fbfe 100644 --- a/src/app/services/auth.js +++ b/src/app/services/auth.js @@ -33,11 +33,11 @@ export const authApi = createApi({ }), // 获取openid getOpenid: builder.mutation({ - query: ({ issuer_url, redirect_uri }) => ({ + query: ({ issuer, redirect_uri }) => ({ url: "/token/openid/authorize", method: "POST", body: { - issuer_url, + issuer, redirect_uri, }, }), diff --git a/src/app/services/base.query.js b/src/app/services/base.query.js index 5d0ea715..847e2611 100644 --- a/src/app/services/base.query.js +++ b/src/app/services/base.query.js @@ -1,73 +1,79 @@ -import { fetchBaseQuery } from '@reduxjs/toolkit/query'; -import toast from 'react-hot-toast'; -import { updateToken, clearAuthData } from '../slices/auth.data'; -import BASE_URL, { tokenHeader } from '../config'; -const whiteList = ['login', 'checkInviteTokenValid']; +import { fetchBaseQuery } from "@reduxjs/toolkit/query"; +import toast from "react-hot-toast"; +import { updateToken, clearAuthData } from "../slices/auth.data"; +import BASE_URL, { tokenHeader } from "../config"; +const whiteList = ["login", "checkInviteTokenValid", "getServer", "getOpenid"]; const baseQuery = fetchBaseQuery({ - baseUrl: BASE_URL, - prepareHeaders: (headers, { getState, endpoint }) => { - console.log('req', endpoint); - const { token } = getState().authData; - if (token && !whiteList.includes(endpoint)) { - headers.set(tokenHeader, token); - } - // 发送channel msg (临时举措) - // if (endpoint == "sendChannelMsg") { - // headers.set("Content-Type", "text/plain"); - // } - return headers; - } + baseUrl: BASE_URL, + prepareHeaders: (headers, { getState, endpoint }) => { + console.log("req", endpoint); + const { token } = getState().authData; + if (token && !whiteList.includes(endpoint)) { + headers.set(tokenHeader, token); + } + // 发送channel msg (临时举措) + // if (endpoint == "sendChannelMsg") { + // headers.set("Content-Type", "text/plain"); + // } + return headers; + }, }); const baseQueryWithReauth = async (args, api, extraOptions) => { - let result = await baseQuery(args, api, extraOptions); - if (result.error) { - console.log('api error', result.error); - switch (result.error.originalStatus) { - case 404: - { - toast.error('Request Not Found'); - } - break; - case 500: - { - toast.error(result.error.data || 'server error'); - } - break; - case 401: - { - // try to get a new token with refreshToken - const { token, refreshToken } = api.getState().authData; - const refreshResult = await baseQuery( - { - url: '/token/renew', - method: 'POST', - body: { - token, - refresh_token: refreshToken - } - }, - api, - extraOptions - ); - console.log({ refreshResult }); - if (refreshResult.data) { - // store the new token - api.dispatch(updateToken(refreshResult.data)); - // retry the initial query - result = await baseQuery(args, api, extraOptions); - } else { - toast.error('token expired, please login again'); - api.dispatch(clearAuthData()); - } - } + let result = await baseQuery(args, api, extraOptions); + if (result?.error) { + console.log("api error", result.error.originalStatus, api.endpoint); + switch (result.error.originalStatus || result.error.status) { + case 404: + { + toast.error("Request Not Found"); + } + break; + case 500: + { + toast.error(result.error.data || "server error"); + } + break; + case 401: + { + if (api.endpoint === "renew") { + toast.error("token expired, please login again"); + api.dispatch(clearAuthData()); + location.href = "/#/login"; + return; + } + // try to get a new token with refreshToken + const { token, refreshToken } = api.getState().authData; + const refreshResult = await baseQuery( + { + url: "/token/renew", + method: "POST", + body: { + token, + refresh_token: refreshToken, + }, + }, + api, + extraOptions + ); + console.log({ refreshResult }); + if (refreshResult.data) { + // store the new token + api.dispatch(updateToken(refreshResult.data)); + // retry the initial query + result = await baseQuery(args, api, extraOptions); + } else { + toast.error("token expired, please login again"); + api.dispatch(clearAuthData()); + } + } - break; + break; - default: - break; + default: + break; + } } - } - return result; + return result; }; export default baseQueryWithReauth; diff --git a/src/app/services/channel.js b/src/app/services/channel.js index 33c89891..c3b933f7 100644 --- a/src/app/services/channel.js +++ b/src/app/services/channel.js @@ -1,5 +1,4 @@ import { createApi } from "@reduxjs/toolkit/query/react"; -import { REHYDRATE } from "redux-persist"; import toast from "react-hot-toast"; import baseQuery from "./base.query"; import { ContentTypes } from "../config"; @@ -12,11 +11,6 @@ import { export const channelApi = createApi({ reducerPath: "channel", baseQuery, - extractRehydrationInfo(action, { reducerPath }) { - if (action.type === REHYDRATE) { - return action.payload ? action.payload[reducerPath] : undefined; - } - }, refetchOnFocus: true, endpoints: (builder) => ({ getChannels: builder.query({ @@ -84,11 +78,12 @@ export const channelApi = createApi({ }; dispatch(addPendingMessage({ type: "channel", msg: tmpMsg })); try { - const { - data: { gid, ...rest }, - } = await queryFulfilled; + const { data: server_mid } = await queryFulfilled; + console.log("channel server mid", server_mid); // 此处的id,是指给谁发的 - dispatch(addChannelMsg({ id: gid, ...rest, unread: false })); + dispatch( + addChannelMsg({ ...tmpMsg, mid: server_mid, unread: false }) + ); dispatch(removePendingMessage({ id, mid, type: "channel" })); } catch { console.log("channel message send failed"); diff --git a/src/app/services/contact.js b/src/app/services/contact.js index 92c39666..40fc8e21 100644 --- a/src/app/services/contact.js +++ b/src/app/services/contact.js @@ -1,5 +1,4 @@ import { createApi } from "@reduxjs/toolkit/query/react"; -import { REHYDRATE } from "redux-persist"; import toast from "react-hot-toast"; import baseQuery from "./base.query"; @@ -14,11 +13,6 @@ import { export const contactApi = createApi({ reducerPath: "contact", baseQuery, - extractRehydrationInfo(action, { reducerPath }) { - if (action.type === REHYDRATE) { - return action.payload ? action.payload[reducerPath] : undefined; - } - }, endpoints: (builder) => ({ getContacts: builder.query({ query: () => ({ url: `user` }), @@ -96,9 +90,13 @@ export const contactApi = createApi({ }; dispatch(addPendingMessage({ type: "user", msg: tmpMsg })); try { - const { data } = await queryFulfilled; + // 走sse推送 + const { data: server_mid } = await queryFulfilled; + // console.log("wtf", wtf); // 此处的id,是指给谁发的 - dispatch(addUserMsg({ id, ...data, unread: false })); + dispatch( + addUserMsg({ id, ...tmpMsg, mid: server_mid, unread: false }) + ); dispatch(removePendingMessage({ id, mid, type: "user" })); } catch { toast.error("Send Message Failed"); @@ -115,6 +113,7 @@ export const { useUpdateInfoMutation, useUpdateAvatarMutation, useGetContactsQuery, + useLazyGetContactsQuery, useSendMsgMutation, useRegisterMutation, } = contactApi; diff --git a/src/app/services/server.js b/src/app/services/server.js index f8c49901..42736f1e 100644 --- a/src/app/services/server.js +++ b/src/app/services/server.js @@ -1,17 +1,11 @@ import { createApi } from "@reduxjs/toolkit/query/react"; import BASE_URL from "../config"; -// import { REHYDRATE } from 'redux-persist'; import baseQuery from "./base.query"; export const serverApi = createApi({ reducerPath: "server", baseQuery, - // extractRehydrationInfo(action, { reducerPath }) { - // if (action.type === REHYDRATE) { - // return action.payload ? action.payload[reducerPath] : undefined; - // } - // }, endpoints: (builder) => ({ getServer: builder.query({ query: () => ({ url: `admin/system/organization` }), @@ -78,6 +72,7 @@ export const { useGetAgoraConfigQuery, useUpdateAgoraConfigMutation, useGetServerQuery, + useLazyGetServerQuery, useUpdateServerMutation, useUpdateLogoMutation, } = serverApi; diff --git a/src/app/slices/auth.data.js b/src/app/slices/auth.data.js index 92a2855d..837496c8 100644 --- a/src/app/slices/auth.data.js +++ b/src/app/slices/auth.data.js @@ -1,12 +1,10 @@ import { createSlice } from "@reduxjs/toolkit"; -import BASE_URL from "../config"; +import BASE_URL, { KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from "../config"; import { getNonNullValues } from "../../common/utils"; const initialState = { user: null, - usersVersion: null, - afterMid: null, - token: null, - refreshToken: null, + token: localStorage.getItem(KEY_TOKEN), + refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN), }; const authDataSlice = createSlice({ name: "authData", @@ -17,6 +15,14 @@ const authDataSlice = createSlice({ state.user = user; state.token = token; state.refreshToken = refresh_token; + // set local data + localStorage.setItem(KEY_TOKEN, token); + localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token); + localStorage.setItem(KEY_UID, user.uid); + }, + setUserData(state, action) { + const user = action.payload; + state.user = user; }, updateLoginedUserByLogs(state, action) { const logs = action.payload; @@ -44,29 +50,26 @@ const authDataSlice = createSlice({ state.user = null; state.token = null; state.refreshToken = null; + // remove local data + localStorage.removeItem(KEY_TOKEN); + localStorage.removeItem(KEY_REFRESH_TOKEN); + localStorage.removeItem(KEY_UID); }, updateToken(state, action) { const { token, refresh_token } = action.payload; console.log("refresh token"); state.token = token; state.refreshToken = refresh_token; - }, - setUsersVersion(state, action) { - const { version } = action.payload; - state.usersVersion = version; - }, - setAfterMid(state, action) { - const { mid } = action.payload; - state.afterMid = mid; + localStorage.setItem(KEY_TOKEN, token); + localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token); }, }, }); export const { updateToken, setAuthData, + setUserData, clearAuthData, - setUsersVersion, - setAfterMid, updateLoginedUserByLogs, } = authDataSlice.actions; export default authDataSlice.reducer; diff --git a/src/app/slices/channels.js b/src/app/slices/channels.js index a5f41f4e..5df92004 100644 --- a/src/app/slices/channels.js +++ b/src/app/slices/channels.js @@ -4,15 +4,20 @@ const channelsSlice = createSlice({ name: `channels`, initialState, reducers: { + clearChannels() { + return initialState; + }, setChannels(state, action) { console.log("set channels store", state); const chs = action.payload || []; - return Object.fromEntries( - chs.map((c) => { - const { gid, ...rest } = c; - return [gid, rest]; - }) - ); + return Array.isArray(chs) + ? Object.fromEntries( + chs.map((c) => { + const { gid, ...rest } = c; + return [gid, rest]; + }) + ) + : chs; }, updateChannel(state, action) { @@ -41,6 +46,7 @@ const channelsSlice = createSlice({ }, }); export const { + clearChannels, setChannels, addChannel, deleteChannel, diff --git a/src/app/slices/contacts.js b/src/app/slices/contacts.js index 70819bbd..97c96238 100644 --- a/src/app/slices/contacts.js +++ b/src/app/slices/contacts.js @@ -5,6 +5,9 @@ const contactsSlice = createSlice({ name: `contacts`, initialState, reducers: { + clearContacts() { + return initialState; + }, setContacts(state, action) { console.log("set Contacts store", state); const contacts = action.payload || []; @@ -21,8 +24,8 @@ const contactsSlice = createSlice({ case "update": { const vals = getNonNullValues(rest); - console.log("update vals", vals); const curr = state.find(({ uid: id }) => id == uid); + console.log("update vals", vals, curr); if (curr) { Object.keys(vals).forEach((k) => { curr[k] = vals[k]; @@ -32,7 +35,14 @@ const contactsSlice = createSlice({ break; case "create": { - state.push({ uid, ...rest }); + const idx = state.findIndex((o) => { + return o.uid == uid; + }); + if (idx > -1) { + state.splice(idx, 1, { uid, ...rest }); + } else { + state.push({ uid, ...rest }); + } } break; case "delete": @@ -56,7 +66,7 @@ const contactsSlice = createSlice({ onlines.forEach((item) => { const { uid, online = false } = item; const curr = state.find(({ uid: id }) => id == uid); - console.log("update user status", curr, online); + // console.log("update user status", curr, online); if (curr) { curr.online = online; } @@ -65,6 +75,7 @@ const contactsSlice = createSlice({ }, }); export const { + clearContacts, setContacts, removeContact, updateUsersByLogs, diff --git a/src/app/slices/message.channel.js b/src/app/slices/message.channel.js index 4d6d302b..97a8e670 100644 --- a/src/app/slices/message.channel.js +++ b/src/app/slices/message.channel.js @@ -1,15 +1,18 @@ import { createSlice } from "@reduxjs/toolkit"; import { isObjectEqual } from "../../common/utils"; -const initialState = { - // accessLogs: {}, - pendingMsgs: [], -}; +const initialState = {}; const channelMsgSlice = createSlice({ name: "channelMessage", initialState, reducers: { + clearChannelMsg() { + return initialState; + }, + initChannelMsg(state, action) { + return action.payload; + }, addChannelMsg(state, action) { const { id, @@ -48,22 +51,13 @@ const channelMsgSlice = createSlice({ obj.unread = false; }); }, - addPendingMsg(state, action) { - state.pendingMsgs.push(action.payload); - }, - removePendingMsg(state, action) { - const timestamp = action.payload; - state.pendingMsgs = state.pendingMsgs.filter( - (m) => m.timestamp != timestamp - ); - }, }, }); export const { + clearChannelMsg, + initChannelMsg, clearChannelMsgUnread, setChannelMsgRead, addChannelMsg, - addPendingMsg, - removePendingMsg, } = channelMsgSlice.actions; export default channelMsgSlice.reducer; diff --git a/src/app/slices/message.pending.js b/src/app/slices/message.pending.js index 39632d00..65fee8f0 100644 --- a/src/app/slices/message.pending.js +++ b/src/app/slices/message.pending.js @@ -8,6 +8,9 @@ const pendingMessageSlice = createSlice({ name: "pendingMessage", initialState, reducers: { + clearPendingMsg() { + return initialState; + }, addPendingMessage(state, action) { const { type = "user", msg } = action.payload; const { id, mid } = msg; @@ -23,6 +26,7 @@ const pendingMessageSlice = createSlice({ }, }); export const { + clearPendingMsg, addPendingMessage, removePendingMessage, } = pendingMessageSlice.actions; diff --git a/src/app/slices/message.user.js b/src/app/slices/message.user.js index 106089ba..bd3c1253 100644 --- a/src/app/slices/message.user.js +++ b/src/app/slices/message.user.js @@ -6,6 +6,12 @@ const userMsgSlice = createSlice({ name: "userMessage", initialState, reducers: { + clearUserMsg() { + return initialState; + }, + initUserMsg(state, action) { + return action.payload; + }, addUserMsg(state, action) { const { id, @@ -44,5 +50,11 @@ const userMsgSlice = createSlice({ }, }, }); -export const { addUserMsg, setUserMsgRead, removeMsg } = userMsgSlice.actions; +export const { + clearUserMsg, + initUserMsg, + addUserMsg, + setUserMsgRead, + removeMsg, +} = userMsgSlice.actions; export default userMsgSlice.reducer; diff --git a/src/app/slices/visit.mark.js b/src/app/slices/visit.mark.js new file mode 100644 index 00000000..6ae8f817 --- /dev/null +++ b/src/app/slices/visit.mark.js @@ -0,0 +1,34 @@ +import { createSlice } from "@reduxjs/toolkit"; +// import { KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from "../config"; +const initialState = { + usersVersion: null, + afterMid: null, +}; +const visitMarkSlice = createSlice({ + name: "visitMark", + initialState, + reducers: { + clearMark() { + return initialState; + }, + setMark(state, action) { + const mark = action.payload; + return mark; + }, + setUsersVersion(state, action) { + const { version } = action.payload; + state.usersVersion = version; + }, + setAfterMid(state, action) { + const { mid } = action.payload; + state.afterMid = mid; + }, + }, +}); +export const { + setMark, + setUsersVersion, + setAfterMid, + clearMark, +} = visitMarkSlice.actions; +export default visitMarkSlice.reducer; diff --git a/src/app/store.js b/src/app/store.js index bf37844c..715f893e 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -1,38 +1,81 @@ -import { configureStore, combineReducers } from "@reduxjs/toolkit"; +import { + configureStore, + combineReducers, + createListenerMiddleware, +} from "@reduxjs/toolkit"; import { setupListeners } from "@reduxjs/toolkit/query"; import authDataReducer from "./slices/auth.data"; +import visitMarkReducer from "./slices/visit.mark"; import uiReducer from "./slices/ui"; import channelsReducer from "./slices/channels"; +import contactsReducer from "./slices/contacts"; +import pendingMsgReducer from "./slices/message.pending"; import channelMsgReducer from "./slices/message.channel"; import userMsgReducer from "./slices/message.user"; import { authApi } from "./services/auth"; import { contactApi } from "./services/contact"; import { channelApi } from "./services/channel"; import { serverApi } from "./services/server"; +const getCacheKey = (action = "") => { + const [type] = action.split("/"); + const keys = [ + "visitMark", + "channels", + "contacts", + "userMessage", + "channelMessage", + // "pendingMessage", + ]; + const [tmp = ""] = keys.filter((k) => k == type); + return tmp; +}; +const reducer = combineReducers({ + ui: uiReducer, + visitMark: visitMarkReducer, + contacts: contactsReducer, + channels: channelsReducer, + pendingMessage: pendingMsgReducer, + userMessage: userMsgReducer, + channelMessage: channelMsgReducer, + authData: authDataReducer, + [authApi.reducerPath]: authApi.reducer, + [contactApi.reducerPath]: contactApi.reducer, + [channelApi.reducerPath]: channelApi.reducer, + [serverApi.reducerPath]: serverApi.reducer, +}); +// Create the middleware instance and methods +const listenerMiddleware = createListenerMiddleware(); -const getStore = () => { - const reducer = combineReducers({ - ui: uiReducer, - channels: channelsReducer, - userMsg: userMsgReducer, - channelMsg: channelMsgReducer, - authData: authDataReducer, - [authApi.reducerPath]: authApi.reducer, - [contactApi.reducerPath]: contactApi.reducer, - [channelApi.reducerPath]: channelApi.reducer, - [serverApi.reducerPath]: serverApi.reducer, - }); - const store = configureStore({ - reducer, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat( +// Add one or more listener entries that look for specific actions. +// They may contain any sync or async logic, similar to thunks. +listenerMiddleware.startListening({ + predicate: (action, currentState, previousState) => { + const { type = "" } = action; + + return !!getCacheKey(type); + // console.log("listener predicate", action, currentState, previousState); + // return true; + }, + effect: async (action, listenerApi) => { + const { type = "" } = action; + const key = getCacheKey(type); + console.log("listener effect", key, listenerApi.getState(), window.CACHE); + if (key && window.CACHE) { + await window.CACHE.setItem(key, listenerApi.getState()[key]); + } + }, +}); +const store = configureStore({ + reducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware() + .concat( authApi.middleware, contactApi.middleware, channelApi.middleware, serverApi.middleware - ), - }); - setupListeners(store.dispatch); - return store; -}; -export default getStore; + ) + .prepend(listenerMiddleware.middleware), +}); +setupListeners(store.dispatch); +export default store; diff --git a/src/common/component/AvatarUploader.js b/src/common/component/AvatarUploader.js index 89ac405c..86d5cfa4 100644 --- a/src/common/component/AvatarUploader.js +++ b/src/common/component/AvatarUploader.js @@ -57,7 +57,12 @@ const StyledWrapper = styled.div` right: 0; } `; -export default function AvatarUploader({ url = "", name = "", uploadImage }) { +export default function AvatarUploader({ + url = "", + name = "", + uploadImage, + disabled = false, +}) { const [uploading, setUploading] = useState(false); const [currSrc, setCurrSrc] = useState(""); useEffect(() => { @@ -80,21 +85,29 @@ export default function AvatarUploader({ url = "", name = "", uploadImage }) {
avatar -
{uploading ? `Uploading` : `Change Avatar`}
- + {!disabled && ( + <> +
+ {uploading ? `Uploading` : `Change Avatar`} +
+ + + )}
- icon + {!disabled && ( + icon + )}
); } diff --git a/src/common/component/NotificationHub/index.js b/src/common/component/NotificationHub/index.js index 49e1ae95..2bc9040e 100644 --- a/src/common/component/NotificationHub/index.js +++ b/src/common/component/NotificationHub/index.js @@ -17,10 +17,9 @@ import { import { updateToken, clearAuthData, - setUsersVersion, - setAfterMid, updateLoginedUserByLogs, } from "../../../app/slices/auth.data"; +import { setUsersVersion, setAfterMid } from "../../../app/slices/visit.mark"; import { addChannelMsg } from "../../../app/slices/message.channel"; import { addUserMsg } from "../../../app/slices/message.user"; @@ -161,50 +160,74 @@ const NotificationHub = ({ usersVersion = 0, afterMid = 0 }) => { dispatch(deleteChannel(data.gid)); break; case "chat": - // console.log("chat data", data); - if (data.gid) { - // channel msg - const { gid, ...rest } = data; - console.log("compare", rest, currUser, rest.from_uid != currUser.uid); - const isSelf = rest.from_uid == currUser.uid; - dispatch( - addChannelMsg({ - id: gid, - // 自己发的 就不用标记未读 - unread: !isSelf, - ...rest, - }) - ); - // group message notification - if (!isSelf) { - showNotification({ - body: rest.content, - data: { - path: `/chat/channel/${gid}`, - }, - }); - } - } else { - // user msg - const isSelf = data.from_uid == currUser.uid; - dispatch( - addUserMsg({ - id: data.from_uid, - unread: !isSelf, - ...data, - }) - ); - if (!isSelf) { - showNotification({ - body: data.content, - data: { - path: `/chat/dm/${data.from_uid}`, - }, - }); + { + // console.log("chat data", data); + const { + detail: { target }, + } = data; + const { + created_at, + mid, + from_uid, + detail: { content, content_type, expires_in, type }, + } = data; + if (typeof target.gid !== "undefined") { + // channel msg + const gid = target.gid; + const isSelf = from_uid == currUser.uid; + dispatch( + addChannelMsg({ + id: gid, + from_uid, + // 自己发的 就不用标记未读 + unread: !isSelf, + created_at, + mid, + content, + content_type, + expires_in, + type, + }) + ); + // group message notification + if (!isSelf) { + showNotification({ + body: content, + data: { + path: `/chat/channel/${gid}`, + }, + }); + } + } else { + // user msg + const isSelf = data.from_uid == currUser.uid; + + dispatch( + addUserMsg({ + // 此处需要特别注意 + id: isSelf ? target.uid : from_uid, + from_uid: from_uid, + unread: !isSelf, + created_at, + mid, + content, + content_type, + expires_in, + type, + }) + ); + if (!isSelf) { + showNotification({ + body: data.content, + data: { + path: `/chat/dm/${data.from_uid}`, + }, + }); + } } + // 更新after_mid + dispatch(setAfterMid({ mid: data.mid })); } - // 更新after_mid - dispatch(setAfterMid({ mid: data.mid })); break; default: @@ -214,7 +237,7 @@ const NotificationHub = ({ usersVersion = 0, afterMid = 0 }) => { }; return null; }; -// function compareToken(prevHub, nextHub) { -// return prevHub.token === nextHub.token; -// } -export default React.memo(NotificationHub); +function compareToken(prevHub, nextHub) { + return prevHub.token === nextHub.token; +} +export default React.memo(NotificationHub, compareToken); diff --git a/src/common/component/Setting/LogoutConfirmModal.js b/src/common/component/Setting/LogoutConfirmModal.js index 6dace4ee..4883b589 100644 --- a/src/common/component/Setting/LogoutConfirmModal.js +++ b/src/common/component/Setting/LogoutConfirmModal.js @@ -1,14 +1,20 @@ // import React from "react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import styled from "styled-components"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; - -import { clearAuthData } from "../../../app/slices/auth.data"; import { toggleSetting } from "../../../app/slices/ui"; +import { clearAuthData } from "../../../app/slices/auth.data"; +import { clearMark } from "../../../app/slices/visit.mark"; +import { clearChannels } from "../../../app/slices/channels"; +import { clearContacts } from "../../../app/slices/contacts"; +import { clearChannelMsg } from "../../../app/slices/message.channel"; +import { clearUserMsg } from "../../../app/slices/message.user"; +import { clearPendingMsg } from "../../../app/slices/message.pending"; // import BASE_URL from "../../app/config"; import { useLazyLogoutQuery } from "../../../app/services/auth"; import Button from "../styled/Button"; +import Checkbox from "../styled/Checkbox"; const StyledConfirm = styled.div` padding: 32px; filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25)); @@ -20,12 +26,26 @@ const StyledConfirm = styled.div` color: #374151; margin-bottom: 16px; } - .desc { + .desc, + .clear { font-weight: normal; font-size: 14px; line-height: 20px; color: #6b7280; - margin-bottom: 64px; + margin-bottom: 12px; + } + .clear { + display: flex; + justify-content: flex-end; + align-items: center; + .txt { + cursor: pointer; + color: orange; + margin-right: 12px; + } + input { + cursor: pointer; + } } .btns { width: 100%; @@ -37,24 +57,48 @@ const StyledConfirm = styled.div` `; import Modal from "../Modal"; export default function LogoutConfirmModal({ closeModal }) { + const [clearLocal, setClearLocal] = useState(false); const dispatch = useDispatch(); const navigate = useNavigate(); const [logout, { isLoading, isSuccess }] = useLazyLogoutQuery(); const handleLogout = () => { logout(); }; + const handleCheck = (evt) => { + setClearLocal(evt.target.checked); + }; useEffect(() => { if (isSuccess) { - dispatch(toggleSetting()); + if (clearLocal) { + console.log("clear all store"); + dispatch(clearMark()); + dispatch(clearChannelMsg()); + dispatch(clearUserMsg()); + dispatch(clearChannels()); + dispatch(clearContacts()); + dispatch(clearPendingMsg()); + } dispatch(clearAuthData()); + // closeModal(); + dispatch(toggleSetting()); navigate("/login"); } - }, [isSuccess]); + }, [isSuccess, clearLocal]); return (

Log Out

Are you sure want to log out this account?

+
+ + +
- - + )}