diff --git a/src/app/config.js b/src/app/config.ts similarity index 100% rename from src/app/config.js rename to src/app/config.ts diff --git a/src/app/services/auth.ts b/src/app/services/auth.ts index 2f78deda..aac8a9e3 100644 --- a/src/app/services/auth.ts +++ b/src/app/services/auth.ts @@ -3,6 +3,7 @@ import { nanoid } from "@reduxjs/toolkit"; import baseQuery from "./base.query"; import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data"; import BASE_URL, { KEY_DEVICE_KEY } from "../config"; +import { AuthData } from '../../types/auth'; const getDeviceId = () => { let d = localStorage.getItem(KEY_DEVICE_KEY); @@ -27,7 +28,7 @@ export const authApi = createApi({ device_token: "test" } }), - transformResponse: (data) => { + transformResponse: (data: AuthData) => { const { avatar_updated_at } = data.user; data.user.avatar = avatar_updated_at == 0 @@ -116,12 +117,10 @@ export const authApi = createApi({ }) }), getCredentials: builder.query({ - query: () => ({ - url: `/token/credentials` - }) + query: () => ({ url: "/token/credentials" }) }), logout: builder.query({ - query: () => ({ url: `token/logout` }), + query: () => ({ url: "token/logout" }), async onQueryStarted(params, { dispatch, queryFulfilled }) { try { await queryFulfilled; @@ -132,9 +131,7 @@ export const authApi = createApi({ } }), getInitialized: builder.query({ - query: () => ({ - url: `/admin/system/initialized` - }), + query: () => ({ url: "/admin/system/initialized" }), async onQueryStarted(params, { dispatch, queryFulfilled }) { try { const { data: isInitialized } = await queryFulfilled; diff --git a/src/app/services/base.query.js b/src/app/services/base.query.ts similarity index 99% rename from src/app/services/base.query.js rename to src/app/services/base.query.ts index b416d323..261d8596 100644 --- a/src/app/services/base.query.js +++ b/src/app/services/base.query.ts @@ -3,6 +3,7 @@ import toast from "react-hot-toast"; import dayjs from "dayjs"; import { updateToken, resetAuthData } from "../slices/auth.data"; import BASE_URL, { tokenHeader } from "../config"; + const whiteList = [ "login", "register", @@ -20,6 +21,7 @@ const whiteList = [ "getInitialized", "createAdmin" ]; + const baseQuery = fetchBaseQuery({ baseUrl: BASE_URL, prepareHeaders: (headers, { getState, endpoint }) => { @@ -31,6 +33,7 @@ const baseQuery = fetchBaseQuery({ return headers; } }); + let waitingForRenew = null; const baseQueryWithTokenCheck = async (args, api, extraOptions) => { if (waitingForRenew) { diff --git a/src/app/services/channel.js b/src/app/services/channel.ts similarity index 99% rename from src/app/services/channel.js rename to src/app/services/channel.ts index 0e24848d..a9ab990f 100644 --- a/src/app/services/channel.js +++ b/src/app/services/channel.ts @@ -9,6 +9,7 @@ import { removeChannelSession } from "../slices/message.channel"; import { removeReactionMessage } from "../slices/message.reaction"; import { onMessageSendStarted } from "./handlers"; import handleChatMessage from "../../common/hook/useStreaming/chat.handler"; + export const channelApi = createApi({ reducerPath: "channelApi", baseQuery, diff --git a/src/app/services/contact.js b/src/app/services/contact.ts similarity index 100% rename from src/app/services/contact.js rename to src/app/services/contact.ts diff --git a/src/app/slices/auth.data.ts b/src/app/slices/auth.data.ts index 258aa630..c7bc5e5f 100644 --- a/src/app/slices/auth.data.ts +++ b/src/app/slices/auth.data.ts @@ -1,5 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { KEY_EXPIRE, KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from '../config'; +import { AuthData, AuthToken } from '../../types/auth'; interface State { initialized: boolean; @@ -25,30 +26,6 @@ const emptyState: State = { refreshToken: null }; -interface AuthToken { - // common - server_id: string; - token: string; - refresh_token: string; - expired_in: number; -} - -interface User { - uid: number; - email: string; - name: string; - gender: number; - language: string; - is_admin: boolean; - avatar_updated_at: number; - create_by: string; -} - -interface AuthData extends AuthToken { - initialized?: boolean; - user: User; -} - const authDataSlice = createSlice({ name: 'authData', initialState, diff --git a/src/app/slices/contacts.js b/src/app/slices/contacts.js deleted file mode 100644 index a1d62491..00000000 --- a/src/app/slices/contacts.js +++ /dev/null @@ -1,86 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { getNonNullValues } from "../../common/utils"; -import BASE_URL from "../config"; -const initialState = { - ids: [], - byId: {} -}; -const contactsSlice = createSlice({ - name: `contacts`, - initialState, - reducers: { - resetContacts() { - return initialState; - }, - fullfillContacts(state, action) { - console.log("set Contacts store", action); - const contacts = action.payload || []; - state.ids = contacts.map(({ uid }) => uid); - state.byId = Object.fromEntries( - contacts.map((c) => { - const { uid } = c; - return [uid, c]; - }) - ); - }, - removeContact(state, action) { - const uid = action.payload; - state.ids = state.ids.filter((i) => i != uid); - delete state.byId[uid]; - }, - updateUsersByLogs(state, action) { - const changeLogs = action.payload; - changeLogs.forEach(({ action, uid, ...rest }) => { - switch (action) { - case "update": - { - const vals = getNonNullValues(rest); - if (state.byId[uid]) { - Object.keys(vals).forEach((k) => { - state.byId[uid][k] = vals[k]; - if (k == "avatar_updated_at") { - state.byId[uid].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`; - } - }); - } - } - break; - case "create": - { - state.byId[uid] = { uid, ...rest }; - const idx = state.ids.findIndex((i) => i == uid); - if (idx == -1) { - state.ids.push(uid); - } - } - break; - case "delete": - { - const idx = state.ids.findIndex((i) => i == uid); - if (idx > -1) { - state.ids.splice(idx, 1); - delete state.byId[uid]; - } - } - break; - - default: - break; - } - }); - }, - updateUsersStatus(state, action) { - const onlines = action.payload; - onlines.forEach((data) => { - const { uid, online = false } = data; - // console.log("update user status", curr, online); - if (state.byId[uid]) { - state.byId[uid].online = online; - } - }); - } - } -}); -export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } = - contactsSlice.actions; -export default contactsSlice.reducer; diff --git a/src/app/slices/contacts.ts b/src/app/slices/contacts.ts new file mode 100644 index 00000000..ef303402 --- /dev/null +++ b/src/app/slices/contacts.ts @@ -0,0 +1,106 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { getNonNullValues } from '../../common/utils'; +import BASE_URL from '../config'; +import { User } from '../../types/auth'; +import { UserLog, UserState } from '../../types/sse'; + +export interface StoredUser extends User { + online?: boolean; + avatar: string; +} + +export interface State { + ids: number[]; + byId: { [id: number]: StoredUser | undefined }; +} + +const initialState: State = { + ids: [], + byId: {} +}; + +const contactsSlice = createSlice({ + name: 'contacts', + initialState, + reducers: { + resetContacts() { + return initialState; + }, + fullfillContacts(state, action: PayloadAction) { + console.log('set Contacts store', action); + const contacts = action.payload || []; + state.ids = contacts.map(({ uid }) => uid); + // old code + // state.byId = Object.fromEntries( + // contacts.map((c) => { + // const { uid } = c; + // return [uid, c]; + // }) + // ); + + contacts.forEach(u => { + state.byId[u.uid] = { + ...u, + // todo: add avatar field + avatar: '' + }; + }); + }, + removeContact(state, action: PayloadAction) { + const uid = action.payload; + state.ids = state.ids.filter((i) => i != uid); + delete state.byId[uid]; + }, + updateUsersByLogs(state, action: PayloadAction) { + const changeLogs = action.payload; + changeLogs.forEach(({ action, uid, ...rest }) => { + switch (action) { + case 'update': { + const vals = getNonNullValues(rest); + if (state.byId[uid]) { + Object.keys(vals).forEach((k) => { + state.byId[uid][k] = vals[k]; + if (k == 'avatar_updated_at') { + state.byId[uid]!.avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`; + } + }); + } + break; + } + case 'create': { + // todo: missing properties avatar, create_by + state.byId[uid] = { uid, ...rest }; + const idx = state.ids.findIndex((i) => i == uid); + if (idx == -1) { + state.ids.push(uid); + } + break; + } + case 'delete': { + const idx = state.ids.findIndex((i) => i == uid); + if (idx > -1) { + state.ids.splice(idx, 1); + delete state.byId[uid]; + } + break; + } + default: + break; + } + }); + }, + updateUsersStatus(state, action: PayloadAction) { + action.payload.forEach((data) => { + const { uid, online = false } = data; + // console.log("update user status", curr, online); + if (state.byId[uid]) { + state.byId[uid]!.online = online; + } + }); + } + } +}); + +export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } = + contactsSlice.actions; +export default contactsSlice.reducer; diff --git a/src/app/slices/favorites.js b/src/app/slices/favorites.ts similarity index 61% rename from src/app/slices/favorites.js rename to src/app/slices/favorites.ts index a26ea57e..d0fb5d08 100644 --- a/src/app/slices/favorites.js +++ b/src/app/slices/favorites.ts @@ -1,24 +1,32 @@ -import { createSlice } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; // import BASE_URL from "../config"; -const initialState = []; + +// todo: check messages type +export interface Favorite { + id: number; + messages: any[]; +} + +const initialState: Favorite[] = []; + const favoritesSlice = createSlice({ name: `favorites`, initialState, reducers: { - fullfillFavorites(state, action) { + fullfillFavorites(state, action: PayloadAction) { return action.payload; }, - addFavorite(state, action) { + addFavorite(state, action: PayloadAction) { state.push(action.payload); }, - deleteFavorite(state, action) { + deleteFavorite(state, action: PayloadAction) { const id = action.payload; const idx = state.findIndex((f) => f.id == id); if (idx > -1) { state.splice(idx, 1); } }, - populateFavorite(state, action) { + populateFavorite(state, action: PayloadAction) { const { id, messages } = action.payload; const idx = state.findIndex((fav) => fav.id == id); if (idx > -1) { @@ -27,6 +35,8 @@ const favoritesSlice = createSlice({ } } }); + export const { addFavorite, deleteFavorite, fullfillFavorites, populateFavorite } = favoritesSlice.actions; + export default favoritesSlice.reducer; diff --git a/src/app/slices/footprint.js b/src/app/slices/footprint.ts similarity index 84% rename from src/app/slices/footprint.js rename to src/app/slices/footprint.ts index 188285b7..26dda682 100644 --- a/src/app/slices/footprint.js +++ b/src/app/slices/footprint.ts @@ -1,5 +1,15 @@ -import { createSlice } from "@reduxjs/toolkit"; -const initialState = { +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +export interface State { + usersVersion: number; + afterMid: number; + readUsers: {}; + readChannels: {}; + muteUsers: {}; + muteChannels: {}; +} + +const initialState: State = { usersVersion: 0, afterMid: 0, readUsers: {}, @@ -7,6 +17,7 @@ const initialState = { muteUsers: {}, muteChannels: {} }; + const footprintSlice = createSlice({ name: "footprint", initialState, @@ -32,13 +43,11 @@ const footprintSlice = createSlice({ muteChannels }; }, - updateUsersVersion(state, action) { - const usersVersion = action.payload; - state.usersVersion = usersVersion; + updateUsersVersion(state, action: PayloadAction) { + state.usersVersion = action.payload; }, - updateAfterMid(state, action) { - const afterMid = action.payload; - state.afterMid = afterMid; + updateAfterMid(state, action: PayloadAction) { + state.afterMid = action.payload; }, updateMute(state, action) { const payload = action.payload || {}; @@ -97,6 +106,7 @@ const footprintSlice = createSlice({ } } }); + export const { resetFootprint, fullfillFootprint, @@ -106,4 +116,5 @@ export const { updateReadUsers, updateMute } = footprintSlice.actions; + export default footprintSlice.reducer; diff --git a/src/app/slices/message.channel.js b/src/app/slices/message.channel.ts similarity index 56% rename from src/app/slices/message.channel.js rename to src/app/slices/message.channel.ts index 241758ae..cbfb3571 100644 --- a/src/app/slices/message.channel.js +++ b/src/app/slices/message.channel.ts @@ -1,5 +1,12 @@ -import { createSlice } from "@reduxjs/toolkit"; -const initialState = {}; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ChannelMessage } from '../../types/channel'; +import { EntityId } from '../../types/common'; + +export interface State { + [gid: number]: number[] | undefined; +} + +const initialState: State = {}; const channelMsgSlice = createSlice({ name: "channelMessage", @@ -8,41 +15,41 @@ const channelMsgSlice = createSlice({ resetChannelMsg() { return initialState; }, - fullfillChannelMsg(state, action) { + fullfillChannelMsg(state, action: PayloadAction) { return action.payload; }, addChannelMsg(state, action) { const { id, mid, local_id = null } = action.payload; if (state[id]) { - const midExsited = state[id].findIndex((id) => id == mid) > -1; - const localMsgExsited = state[id].findIndex((id) => id == local_id) > -1; + const midExsited = state[id]!.findIndex((id) => id == mid) > -1; + const localMsgExsited = state[id]!.findIndex((id) => id == local_id) > -1; if (midExsited || localMsgExsited) return; - state[id].push(+mid); + state[id]!.push(+mid); } else { state[id] = [+mid]; } }, - removeChannelMsg(state, action) { + removeChannelMsg(state, action: PayloadAction) { const { id, mid } = action.payload; if (state[id]) { - const idx = state[id].findIndex((i) => i == mid); + const idx = state[id]!.findIndex((i) => i == mid); if (idx > -1) { // 存在 则再删除 - state[id].splice(idx, 1); + state[id]!.splice(idx, 1); } } }, replaceChannelMsg(state, action) { const { id, localMid, serverMid } = action.payload; if (state[id]) { - const localIdx = state[id].findIndex((i) => i == localMid); + const localIdx = state[id]!.findIndex((i) => i == localMid); if (localIdx > -1 && serverMid) { // 存在 则再删除 - state[id].splice(localIdx, 1, serverMid); + state[id]!.splice(localIdx, 1, serverMid); } } }, - removeChannelSession(state, action) { + removeChannelSession(state, action: PayloadAction) { const ids = Array.isArray(action.payload) ? action.payload : [action.payload]; ids.forEach((id) => { delete state[id]; @@ -50,6 +57,7 @@ const channelMsgSlice = createSlice({ } } }); + export const { removeChannelSession, resetChannelMsg, @@ -58,4 +66,5 @@ export const { removeChannelMsg, replaceChannelMsg } = channelMsgSlice.actions; + export default channelMsgSlice.reducer; diff --git a/src/app/slices/message.file.js b/src/app/slices/message.file.ts similarity index 74% rename from src/app/slices/message.file.js rename to src/app/slices/message.file.ts index 01d5515e..9594a284 100644 --- a/src/app/slices/message.file.js +++ b/src/app/slices/message.file.ts @@ -1,6 +1,7 @@ -import { createSlice } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +const initialState: number[] = []; -const initialState = []; const fileMessageSlice = createSlice({ name: "fileMessage", initialState, @@ -8,10 +9,10 @@ const fileMessageSlice = createSlice({ resetFileMessage() { return initialState; }, - fullfillFileMessage(state, action) { + fullfillFileMessage(state, action: PayloadAction) { return action.payload || []; }, - addFileMessage(state, action) { + addFileMessage(state, action: PayloadAction) { const mid = action.payload; // 加入file message 列表 const fidIdx = state.findIndex((fid) => fid == mid); @@ -19,7 +20,7 @@ const fileMessageSlice = createSlice({ state.unshift(+mid); } }, - removeFileMessage(state, action) { + removeFileMessage(state, action: PayloadAction) { const mids = Array.isArray(action.payload) ? action.payload : [action.payload]; mids.forEach((id) => { // 从file message 列表删掉 @@ -31,6 +32,8 @@ const fileMessageSlice = createSlice({ } } }); + export const { removeFileMessage, resetFileMessage, fullfillFileMessage, addFileMessage } = fileMessageSlice.actions; + export default fileMessageSlice.reducer; diff --git a/src/app/slices/message.reaction.js b/src/app/slices/message.reaction.ts similarity index 69% rename from src/app/slices/message.reaction.js rename to src/app/slices/message.reaction.ts index 44d40b46..80bd9550 100644 --- a/src/app/slices/message.reaction.js +++ b/src/app/slices/message.reaction.ts @@ -1,6 +1,13 @@ -import { createSlice } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -const initialState = {}; +// todo: check entity type +export interface State { + [mid: number]: { + [reaction: number]: number[] + } | undefined; +} + +const initialState: State = {}; const reactionMessageSlice = createSlice({ name: "reactionMessage", initialState, @@ -8,10 +15,10 @@ const reactionMessageSlice = createSlice({ resetReactionMessage() { return initialState; }, - fullfillReactionMessage(state, action) { + fullfillReactionMessage(state, action: PayloadAction) { return action.payload; }, - removeReactionMessage(state, action) { + removeReactionMessage(state, action: PayloadAction) { const mids = Array.isArray(action.payload) ? action.payload : [action.payload]; mids.forEach((id) => { delete state[id]; @@ -20,39 +27,40 @@ const reactionMessageSlice = createSlice({ toggleReactionMessage(state, action) { // rid: reaction's mid, mid: which message append to const { from_uid, mid, rid, action: reaction } = action.payload; - console.log("msg reaction", mid, from_uid, reaction); const ridExisted = state[rid] || false; // 已经塞过了 if (ridExisted) return; - console.log("ssss"); // 还未塞过任何一表情 if (!state[mid]) { state[mid] = {}; } // 存在该表情数据 - if (state[mid][reaction]) { - const reactionUids = state[mid][reaction]; + if (state[mid]![reaction]) { + const reactionUids = state[mid]![reaction]; const idx = reactionUids.findIndex((id) => id == from_uid); if (idx > -1) { reactionUids.splice(idx, 1); if (reactionUids.length == 0) { // 没有表情了 - delete state[mid][reaction]; + delete state[mid]![reaction]; } } else { reactionUids.push(from_uid); } } else { - state[mid][reaction] = [from_uid]; + state[mid]![reaction] = [from_uid]; } + // todo: ??? state[rid] = true; } } }); + export const { removeReactionMessage, resetReactionMessage, fullfillReactionMessage, toggleReactionMessage } = reactionMessageSlice.actions; + export default reactionMessageSlice.reducer; diff --git a/src/app/slices/message.js b/src/app/slices/message.ts similarity index 96% rename from src/app/slices/message.js rename to src/app/slices/message.ts index c92f6efa..8105240c 100644 --- a/src/app/slices/message.js +++ b/src/app/slices/message.ts @@ -1,9 +1,15 @@ import { createSlice } from "@reduxjs/toolkit"; import BASE_URL, { ContentTypes } from "../config"; import { isImage } from "../../common/utils"; -const initialState = { + +export interface State { + replying: {}; +} + +const initialState: State = { replying: {} }; + const messageSlice = createSlice({ name: "message", initialState, @@ -66,6 +72,7 @@ const messageSlice = createSlice({ } } }); + export const { resetMessage, fullfillMessage, @@ -76,4 +83,5 @@ export const { addReplyingMessage, removeReplyingMessage } = messageSlice.actions; + export default messageSlice.reducer; diff --git a/src/app/slices/message.user.js b/src/app/slices/message.user.ts similarity index 87% rename from src/app/slices/message.user.js rename to src/app/slices/message.user.ts index cc925d1b..e568e8d0 100644 --- a/src/app/slices/message.user.js +++ b/src/app/slices/message.user.ts @@ -1,8 +1,16 @@ -import { createSlice } from "@reduxjs/toolkit"; -const initialState = { +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +export interface State { + ids: string[]; + // todo: check object type + byId: { [id: number]: any; }; +} + +const initialState: State = { ids: [], byId: {} }; + const userMsgSlice = createSlice({ name: "userMessage", initialState, @@ -10,7 +18,7 @@ const userMsgSlice = createSlice({ resetUserMsg() { return initialState; }, - fullfillUserMsg(state, action) { + fullfillUserMsg(state, action: PayloadAction<{ [id: string]: any; }>) { state.ids = Object.keys(action.payload); state.byId = action.payload; }, diff --git a/src/app/slices/server.js b/src/app/slices/server.ts similarity index 100% rename from src/app/slices/server.js rename to src/app/slices/server.ts diff --git a/src/app/slices/ui.js b/src/app/slices/ui.ts similarity index 85% rename from src/app/slices/ui.js rename to src/app/slices/ui.ts index f1302672..b3304254 100644 --- a/src/app/slices/ui.js +++ b/src/app/slices/ui.ts @@ -1,6 +1,28 @@ -import { createSlice } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { Views } from "../config"; -const initialState = { + +export interface State { + online: boolean; + ready: boolean; + userGuide: { + visible: boolean; + step: number; + }; + inputMode: "text"; + menuExpand: boolean; + // todo + fileListView: string; + uploadFiles: { [key: string]: any }; + selectMessages: { [key: string]: any }; + draftMarkdown: { [key: string]: any }; + draftMixedText: { [key: string]: any }; + remeberedNavs: { + chat: null | string; + contact: null | string; + }; +} + +const initialState: State = { online: true, ready: false, userGuide: { @@ -14,11 +36,13 @@ const initialState = { selectMessages: {}, draftMarkdown: {}, draftMixedText: {}, + // todo: typo remeberedNavs: { chat: null, contact: null } }; + const uiSlice = createSlice({ name: "ui", initialState, @@ -41,7 +65,7 @@ const uiSlice = createSlice({ updateFileListView(state, action) { state.fileListView = action.payload; }, - updateRemeberedNavs(state, action) { + updateRemeberedNavs(state, action: PayloadAction<{ key?: string; path: string | null }>) { const { key = "chat", path = null } = action.payload || {}; state.remeberedNavs[key] = path; }, @@ -59,7 +83,6 @@ const uiSlice = createSlice({ state.userGuide[key] = obj[key]; }); }, - updateUploadFiles(state, action) { const { context = "channel", id = null, operation = "add", ...rest } = action.payload; if (!id || !context) return; @@ -144,6 +167,7 @@ const uiSlice = createSlice({ } } }); + export const { fullfillUI, setReady, @@ -158,4 +182,5 @@ export const { updateDraftMixedText, updateRemeberedNavs } = uiSlice.actions; + export default uiSlice.reducer; diff --git a/src/routes/404/index.js b/src/routes/404/index.tsx similarity index 81% rename from src/routes/404/index.js rename to src/routes/404/index.tsx index 8860bb46..d409e09a 100644 --- a/src/routes/404/index.js +++ b/src/routes/404/index.tsx @@ -1,4 +1,3 @@ -// import React from 'react'; import StyledWrapper from "./styled"; export default function NotFoundPage() { diff --git a/src/routes/404/styled.js b/src/routes/404/styled.tsx similarity index 99% rename from src/routes/404/styled.js rename to src/routes/404/styled.tsx index ccbe2286..36b3ee08 100644 --- a/src/routes/404/styled.js +++ b/src/routes/404/styled.tsx @@ -1,4 +1,5 @@ import styled from "styled-components"; + const StyledWrapper = styled.div` display: flex; `; diff --git a/src/routes/invite/index.tsx b/src/routes/invite/index.tsx index 3da0d8b2..6b02cd0c 100644 --- a/src/routes/invite/index.tsx +++ b/src/routes/invite/index.tsx @@ -1,17 +1,17 @@ -import { useState, useEffect, ChangeEvent, FormEvent } from 'react'; -import { Navigate } from 'react-router-dom'; -import toast from 'react-hot-toast'; -import { useSelector } from 'react-redux'; -import StyledWrapper from './styled'; -import BASE_URL from '../../app/config'; -import { useRegisterMutation } from '../../app/services/contact'; -import { useCheckInviteTokenValidMutation } from '../../app/services/auth'; +import { useState, useEffect, ChangeEvent, FormEvent } from "react"; +import { Navigate } from "react-router-dom"; +import toast from "react-hot-toast"; +import StyledWrapper from "./styled"; +import BASE_URL from "../../app/config"; +import { useRegisterMutation } from "../../app/services/contact"; +import { useCheckInviteTokenValidMutation } from "../../app/services/auth"; +import { useAppSelector } from "../../app/store"; export default function InvitePage() { - const { token: loginToken } = useSelector((store) => store.authData); - const [secondPwd, setSecondPwd] = useState(''); + const { token: loginToken } = useAppSelector((store) => store.authData); + const [secondPwd, setSecondPwd] = useState(""); const [samePwd, setSamePwd] = useState(true); - const [token, setToken] = useState(''); + const [token, setToken] = useState(""); const [valid, setValid] = useState(false); // const [sp] = useSearchParams(); // const navigateTo = useNavigate(); @@ -22,7 +22,7 @@ export default function InvitePage() { useEffect(() => { // console.log(search); const query = new URLSearchParams(location.search); - setToken(query.get('token')); + setToken(query.get("token")); }, []); useEffect(() => { @@ -40,15 +40,15 @@ export default function InvitePage() { } }, [checkSuccess, isValid]); - const [input, setInput] = useState({ name: '', email: '', password: '' }); + const [input, setInput] = useState({ name: "", email: "", password: "" }); const handleReg = (evt: FormEvent) => { evt.preventDefault(); if (!samePwd) { - toast.error('two passwords not same'); + toast.error("two passwords not same"); return; } - console.log('wtf', input); + console.log("wtf", input); register({ ...input, magic_token: token, @@ -79,53 +79,53 @@ export default function InvitePage() { useEffect(() => { if (!samePwd) { - toast.error('two passwords not same'); + toast.error("two passwords not same"); } }, [samePwd]); useEffect(() => { if (isSuccess && data) { // 去登录 - toast.success('register success, login please'); + toast.success("register success, login please"); setTimeout(() => { location.href = `/#/login`; // navigateTo("/login",); }, 500); } else if (isError) { - console.log('register failed', error); + console.log("register failed", error); switch (error.status) { case 400: - toast.error('Register Failed: please check inputs'); + toast.error("Register Failed: please check inputs"); break; case 412: - toast.error('Register Failed: invalid token or expired'); + toast.error("Register Failed: invalid token or expired"); break; case 409: { const tips = { - email_conflict: 'email conflict', - name_conflict: 'name conflict' + email_conflict: "email conflict", + name_conflict: "name conflict" }; toast.error(`Register Failed: ${tips[error.data?.reason]}`); break; } default: - toast.error('Register Failed'); + toast.error("Register Failed"); break; } } }, [data, isSuccess, isError, error]); const { email, password, name } = input; - if (loginToken) return ; - if (!token) return 'token not found'; - if (checkLoading) return 'checking token valid'; - if (!valid) return 'invite token expires or invalid'; + if (loginToken) return ; + if (!token) return "token not found"; + if (checkLoading) return "checking token valid"; + if (!valid) return "invite token expires or invalid"; return (
- logo + logo

Sign Up to Rustchat

Please enter your details.
diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 00000000..7277059e --- /dev/null +++ b/src/types/auth.ts @@ -0,0 +1,28 @@ + +export interface AuthToken { + // common + server_id: string; + token: string; + refresh_token: string; + expired_in: number; +} + +// todo: check gender values +export type Gender = 0 | 1; + +export interface User { + // avatar: string; // todo: check transform data in redux slice + uid: number; + email: string; + name: string; + gender: Gender; + language: string; + is_admin: boolean; + avatar_updated_at: number; + create_by: string; +} + +export interface AuthData extends AuthToken { + initialized?: boolean; + user: User; +} diff --git a/src/types/channel.ts b/src/types/channel.ts index c90176c3..124bee86 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -3,6 +3,11 @@ export interface ChannelMember { } +// todo: check message fields +export interface ChannelMessage { + mid: number; +} + export type ContentType = 'text/plain' | 'text/markdown' | @@ -22,7 +27,7 @@ export interface PinnedMessage { export interface Channel { gid: number; - icon: string; // added by frontend + // icon: string; // todo: check owner: number; name: string; description: string; diff --git a/src/types/common.ts b/src/types/common.ts new file mode 100644 index 00000000..5e631bd5 --- /dev/null +++ b/src/types/common.ts @@ -0,0 +1,3 @@ +export interface EntityId { + id: number; +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 36442c00..23623814 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,7 +1,8 @@ +import { PrecacheEntry } from "workbox-precaching/src/_types"; export declare global { interface Window { - __WB_MANIFEST: any; // todo - skipWaiting: any; // todo + __WB_MANIFEST: Array; + skipWaiting: () => void; } } diff --git a/src/types/sse.ts b/src/types/sse.ts index c716f52c..51d630e0 100644 --- a/src/types/sse.ts +++ b/src/types/sse.ts @@ -1,70 +1,138 @@ -// todo -interface ReadyEvent { - type: 'ready' +import { User } from './auth'; +import { Channel, ContentType } from './channel'; + +export interface ReadyEvent { + type: 'ready'; } -interface UsersSnapshotEvent { - type: 'users_snapshot' +export interface UsersSnapshotEvent { + type: 'users_snapshot'; + users: User[]; + version: number; } -interface UsersLogEvent { - type: 'users_log' +// todo: check if create_by field exists +export interface UserLog extends Omit { + log_id: number; + action: 'create' | 'update' | 'delete'; } -interface UsersStateEvent { - type: 'users_state' +export interface UsersLogEvent { + type: 'users_log'; + logs: UserLog[]; } -interface UsersStateChangedEvent { - type: 'users_state_changed' +export interface UserState { + uid: number; + online: boolean; +} + +export interface UsersStateEvent { + type: 'users_state'; + users: UserState[]; +} + +interface UsersStateChangedEvent extends UserState{ + type: 'users_state_changed'; } interface UserSettingsEvent { - type: 'user_settings' + type: 'user_settings'; + mute_users?: { uid: number; expired_at: number; }[]; + mute_groups?: { gid: number; expired_at: number; }[]; + read_index_users?: { uid: number; mid: number; }[]; + read_index_groups?: { gid: number; mid: number; }[]; + burn_after_reading_users?: { uid: number; expires_in: number; }[]; + burn_after_reading_groups?: { gid: number; expires_in: number; }[]; } interface UserSettingsChangedEvent { - type: 'user_settings_changed' + type: 'user_settings_changed'; + from_device?: string; + add_mute_users?: { uid: number; expired_at: number; }[]; + remove_mute_users?: number[]; + add_mute_groups?: { gid: number; expired_at: number; }[]; + remove_mute_groups?: number[]; + read_index_users?: { uid: number; mid: number; }[]; + read_index_groups?: { gid: number; mid: number; }[]; + burn_after_reading_users?: { uid: number; expires_in: number; }[]; + burn_after_reading_groups?: { gid: number; expires_in: number; }[]; } interface RelatedGroupsEvent { - type: 'related_groups' + type: 'related_groups'; + groups: Channel[]; } interface ChatEvent { - type: 'chat' + type: 'chat'; + mid: number; + from_uid: number; + created_at: number; + target: { uid: number }; + detail: { + properties: {}; + content: string; + content_type: ContentType; + expires_in: number; + type: 'normal'; + }; } interface KickEvent { - type: 'kick' + type: 'kick'; + reason: string; } interface UserJoinedGroupEvent { - type: 'user_joined_group' + type: 'user_joined_group'; + gid: number; + uid: number[]; } interface UserLeavedGroupEvent { - type: 'user_leaved_group' + type: 'user_leaved_group'; + gid: number; + uid: number[]; } interface JoinedGroupEvent { - type: 'joined_group' + type: 'joined_group'; + group: Channel; } interface KickFromGroupEvent { - type: 'kick_from_group' + type: 'kick_from_group'; + gid: number; + reason: string; } interface GroupChangedEvent { - type: 'group_changed' + type: 'group_changed'; + gid: number; + name: string; + description: string; + owner: number; + avatar_updated_at: number; } interface PinnedMessageUpdatedEvent { - type: 'pinned_message_updated' + type: 'pinned_message_updated'; + gid: number; + mid: number; + msg: { + mid: number; + created_by: number; + created_at: number; + properties: {}; + content: string; + content_type: ContentType; + } } interface HeartbeatEvent { - type: 'heartbeat' + type: 'heartbeat'; + time: number; } export type ServerEvent = ReadyEvent |