From 9f1b115c73e29f9950e5b9dacf8c24dc0dd012b8 Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Fri, 8 Jul 2022 19:13:49 +0800 Subject: [PATCH] refactor: more TS code --- .vscode/settings.json | 17 ++++- src/app/services/auth.ts | 62 +++++++++-------- src/app/services/channel.ts | 17 ++--- src/app/services/handlers.ts | 12 ---- src/app/services/message.ts | 34 +++++----- src/app/services/server.ts | 5 +- src/app/services/user.ts | 16 +++-- src/app/slices/auth.data.ts | 4 +- src/app/slices/channels.ts | 2 +- src/app/slices/ui.ts | 2 +- src/app/slices/users.ts | 2 +- src/common/component/BlankPlaceholder.tsx | 2 +- src/common/component/Channel.tsx | 1 - src/common/component/ChannelIcon.tsx | 11 ++-- src/common/component/CurrentUser.tsx | 8 ++- src/common/component/EmojiPicker.tsx | 6 -- src/common/component/FAQ.tsx | 8 ++- src/common/component/FileMessage/index.tsx | 4 +- src/common/component/ForwardModal/index.tsx | 11 +++- src/common/component/GithubLoginButton.tsx | 4 +- src/common/component/GoogleLoginButton.tsx | 4 +- src/common/component/ImagePreviewModal.tsx | 8 +-- src/common/component/InviteLink.tsx | 9 ++- src/common/component/ManageMembers.tsx | 14 +++- src/common/component/Manifest/index.tsx | 28 +++----- src/common/component/MarkdownEditor/index.tsx | 2 +- src/common/component/MarkdownRender.tsx | 58 ++++++++-------- .../component/Message/FavoredMessage.tsx | 66 +++++++++++++++++++ .../component/Message/FavoritedMessage.tsx | 65 ------------------ src/common/component/Meta.tsx | 8 ++- .../component/Notification/useDeviceToken.ts | 7 +- src/common/component/Profile/index.tsx | 4 +- src/common/component/Search.tsx | 8 ++- src/common/component/Send/index.tsx | 16 +++-- src/common/hook/useAddLocalFileMessage.ts | 6 +- src/common/hook/useStreaming/index.ts | 2 +- src/common/hook/useUploadFile.ts | 17 +++-- src/common/hook/useUserOperation.ts | 2 +- src/routes/chat/FavList.tsx | 4 +- src/routes/favs/index.tsx | 8 +-- src/routes/login/OidcLoginButton.tsx | 20 +++--- src/routes/login/OidcLoginEntry.tsx | 16 ++--- src/routes/login/index.tsx | 14 ++-- src/routes/oauth/index.tsx | 8 +-- src/routes/setting/APIConfig.tsx | 3 +- src/routes/setting/index.tsx | 5 +- src/routes/settingChannel/index.tsx | 26 ++++---- src/routes/users/index.tsx | 9 +-- src/types/auth.ts | 15 +++++ src/types/channel.ts | 8 ++- src/types/global.d.ts | 26 ++++++++ src/types/message.ts | 12 ++++ src/types/resource.ts | 12 ++-- src/types/sse.ts | 11 +++- src/types/user.ts | 10 ++- 55 files changed, 421 insertions(+), 338 deletions(-) create mode 100644 src/common/component/Message/FavoredMessage.tsx delete mode 100644 src/common/component/Message/FavoritedMessage.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index b51be803..7a121e2b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,7 +26,22 @@ "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "cSpell.words": ["btns", "navs", "oidc", "reduxjs", "thirdparty", "tippyjs", "vocechat"], + "cSpell.words": [ + "appinstalled", + "beforeinstallprompt", + "btns", + "localstorage", + "magiclink", + "navs", + "oidc", + "reduxjs", + "Swiper", + "thirdparty", + "tippyjs", + "toastui", + "uiball", + "vocechat" + ], "reactSnippets.settings.prettierEnabled": true, "reactSnippets.settings.importReactOnTop": false } diff --git a/src/app/services/auth.ts b/src/app/services/auth.ts index 008565e2..69aea5c2 100644 --- a/src/app/services/auth.ts +++ b/src/app/services/auth.ts @@ -3,7 +3,13 @@ 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, KEY_LOCAL_MAGIC_TOKEN } from "../config"; -import { AuthData, LoginCredential } from "../../types/auth"; +import { + AuthData, + CredentialResponse, + LoginCredential, + RenewTokenDTO, + RenewTokenResponse +} from "../../types/auth"; const getDeviceId = () => { let d = localStorage.getItem(KEY_DEVICE_KEY); @@ -59,14 +65,11 @@ export const authApi = createApi({ }) }), // 更新token - renew: builder.mutation({ - query: ({ token, refreshToken }) => ({ + renew: builder.mutation({ + query: (data) => ({ url: "/token/renew", method: "POST", - body: { - token, - refresh_token: refreshToken - } + body: data }), async onQueryStarted(params, { dispatch, queryFulfilled }) { try { @@ -79,7 +82,7 @@ export const authApi = createApi({ } }), // 更新 device token - updateDeviceToken: builder.mutation({ + updateDeviceToken: builder.mutation({ query: (device_token) => ({ url: "/token/device_token", method: "PUT", @@ -89,66 +92,69 @@ export const authApi = createApi({ }) }), // 获取openid - getOpenid: builder.mutation({ - query: ({ issuer, redirect_uri }) => ({ + getOpenid: builder.mutation<{ url: string }, { issuer: string; redirect_uri: string }>({ + query: (data) => ({ url: "/token/openid/authorize", method: "POST", - body: { - issuer, - redirect_uri - } + body: data }) }), - checkMagicTokenValid: builder.mutation({ + checkMagicTokenValid: builder.mutation({ query: (token) => ({ url: "user/check_magic_token", method: "POST", body: { magic_token: token } }) }), - updatePassword: builder.mutation({ - query: ({ old_password, new_password }) => ({ + updatePassword: builder.mutation({ + query: (data) => ({ url: "user/change_password", method: "POST", - body: { old_password, new_password } + body: data }) }), - sendLoginMagicLink: builder.mutation({ + sendLoginMagicLink: builder.mutation({ query: (email) => ({ headers: { - // "content-type": "text/plain", accept: "text/plain" }, url: `user/send_login_magic_link?email=${encodeURIComponent(email)}`, method: "POST", responseHandler: (response: Response) => response.text() - // body: { email } }) }), - sendRegMagicLink: builder.mutation({ + sendRegMagicLink: builder.mutation< + { + new_magic_token: "string"; + mail_is_sent: boolean; + }, + { + magic_token: "string"; + email: "string"; + password: "string"; + } + >({ query: (data) => ({ url: `user/send_reg_magic_link`, method: "POST", body: data }) }), - getMetamaskNonce: builder.query({ + getMetamaskNonce: builder.query({ query: (address) => ({ url: `/token/metamask/nonce?public_address=${address}` }) }), - checkEmail: builder.query({ + checkEmail: builder.query({ query: (email) => ({ url: `/user/check_email?email=${encodeURIComponent(email)}` }) }), - // todo: check return type - getCredentials: builder.query({ + getCredentials: builder.query({ query: () => ({ url: "/token/credentials" }) }), - // todo: check return type - logout: builder.query({ + logout: builder.query({ query: () => ({ url: "token/logout" }), async onQueryStarted(params, { dispatch, queryFulfilled }) { try { diff --git a/src/app/services/channel.ts b/src/app/services/channel.ts index 1f659367..e88f0f0e 100644 --- a/src/app/services/channel.ts +++ b/src/app/services/channel.ts @@ -9,9 +9,10 @@ import { removeChannelSession } from "../slices/message.channel"; import { removeReactionMessage } from "../slices/message.reaction"; import { onMessageSendStarted } from "./handlers"; import handleChatMessage from "../../common/hook/useStreaming/chat.handler"; -import { Channel, CreateChannelDTO } from "../../types/channel"; +import { Channel, ChannelDTO, CreateChannelDTO } from "../../types/channel"; import { RootState } from "../store"; -import { ContentTypeKey } from "../../types/message"; +import { ContentTypeKey, ChatMessage } from "../../types/message"; + export const channelApi = createApi({ reducerPath: "channelApi", baseQuery, @@ -41,7 +42,7 @@ export const channelApi = createApi({ body: data }) }), - updateChannel: builder.mutation({ + updateChannel: builder.mutation({ query: ({ id, ...data }) => ({ url: `group/${id}`, method: "PUT", @@ -49,7 +50,7 @@ export const channelApi = createApi({ }), async onQueryStarted({ id, name, description }, { dispatch, queryFulfilled }) { // id: who send to ,from_uid: who sent - const patchResult = dispatch(updateChannel({ id, name, description })); + const patchResult = dispatch(updateChannel({ gid: id, name, description })); try { await queryFulfilled; } catch { @@ -58,7 +59,7 @@ export const channelApi = createApi({ } } }), - getHistoryMessages: builder.query({ + getHistoryMessages: builder.query({ query: ({ id, mid = null, limit = 100 }) => ({ url: mid ? `/group/${id}/history?before=${mid}&limit=${limit}` @@ -145,21 +146,21 @@ export const channelApi = createApi({ await onMessageSendStarted.call(this, param1, param2, "channel"); } }), - addMembers: builder.mutation({ + addMembers: builder.mutation({ query: ({ id, members }) => ({ url: `group/${id}/members/add`, method: "POST", body: members }) }), - removeMembers: builder.mutation({ + removeMembers: builder.mutation({ query: ({ id, members }) => ({ url: `group/${id}/members/remove`, method: "POST", body: members }) }), - updateIcon: builder.mutation({ + updateIcon: builder.mutation({ query: ({ gid, image }) => ({ headers: { "content-type": "image/png" diff --git a/src/app/services/handlers.ts b/src/app/services/handlers.ts index 794cdb66..30660672 100644 --- a/src/app/services/handlers.ts +++ b/src/app/services/handlers.ts @@ -25,18 +25,6 @@ export const onMessageSendStarted = async ( console.log("handlers data", content, type, properties, ignoreLocal, id); const isImage = properties.content_type?.startsWith("image"); const ts = properties.local_id || +new Date(); - // let imageData = null; - // if (type == "image") { - // if (typeof content == "string" && content.startsWith("data:image")) { - // // base64 - // // const resp = await fetch(content); - // // const blob = await resp.blob(); - // // imageData = new File([blob], "tmp.png", { type: "image/png" }); - // imageData = content; - // } else { - // imageData = URL.createObjectURL(content); - // } - // } const tmpMsg = { content: isImage ? content.path : content, content_type: ContentTypes[type], diff --git a/src/app/services/message.ts b/src/app/services/message.ts index 6a28b122..b6c52888 100644 --- a/src/app/services/message.ts +++ b/src/app/services/message.ts @@ -4,23 +4,16 @@ import { updateReadChannels, updateReadUsers } from "../slices/footprint"; import { fillFavorites, populateFavorite, addFavorite, deleteFavorite } from "../slices/favorites"; import { onMessageSendStarted } from "./handlers"; import { normalizeArchiveData } from "../../common/utils"; -// import { updateMessage } from "../slices/message"; import baseQuery from "./base.query"; -import { OG } from "../../types/common"; -type UploadFileResponse = { - path: string; - size: number; - hash: string; - image_properties: { - width: number; - height: number; - }; -}; +import { Archive, FavoriteArchive, OG } from "../../types/resource"; +import { ContentTypeKey, UploadFileResponse } from "../../types/message"; +import { RootState } from "../store"; + export const messageApi = createApi({ reducerPath: "messageApi", baseQuery, endpoints: (builder) => ({ - editMessage: builder.mutation({ + editMessage: builder.mutation({ query: ({ mid, content, type = "text" }) => ({ headers: { "content-type": ContentTypes[type] @@ -50,7 +43,7 @@ export const messageApi = createApi({ body: meta }) }), - createArchive: builder.mutation({ + createArchive: builder.mutation({ query: (mids = []) => ({ url: `/resource/archive`, method: "POST", @@ -73,7 +66,7 @@ export const messageApi = createApi({ url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}` }) }), - getArchiveMessage: builder.query({ + getArchiveMessage: builder.query({ query: (file_path) => ({ url: `/resource/archive?file_path=${encodeURIComponent(file_path)}` }) @@ -92,7 +85,7 @@ export const messageApi = createApi({ body: { mid } }) }), - favoriteMessage: builder.mutation({ + favoriteMessage: builder.mutation({ query: (mids) => ({ url: `/favorite`, method: "POST", @@ -123,14 +116,14 @@ export const messageApi = createApi({ } } }), - getFavoriteDetails: builder.query({ + getFavoriteDetails: builder.query({ query: (id) => ({ url: `/favorite/${id}` }), async onQueryStarted(id, { dispatch, queryFulfilled, getState }) { try { const { data } = await queryFulfilled; - const loginUid = getState().authData.user.uid; + const loginUid = (getState() as RootState).authData.user.uid; const messages = normalizeArchiveData(data, id, loginUid); dispatch(populateFavorite({ id, messages })); } catch (err) { @@ -138,7 +131,7 @@ export const messageApi = createApi({ } } }), - getFavorites: builder.query({ + getFavorites: builder.query({ query: () => ({ url: `/favorite` }), @@ -155,7 +148,10 @@ export const messageApi = createApi({ } } }), - replyMessage: builder.mutation({ + replyMessage: builder.mutation< + number, + { reply_mid: number; content: string; type: ContentTypeKey } + >({ query: ({ reply_mid, content, type = "text" }) => ({ headers: { "content-type": ContentTypes[type] diff --git a/src/app/services/server.ts b/src/app/services/server.ts index 0c33fb74..bdea4d05 100644 --- a/src/app/services/server.ts +++ b/src/app/services/server.ts @@ -3,7 +3,7 @@ import BASE_URL from "../config"; import { updateInfo } from "../slices/server"; import baseQuery from "./base.query"; import { RootState } from "../store"; -import { User } from "../../types/auth"; +import { User } from "../../types/user"; import { FirebaseConfig, GoogleAuthConfig, @@ -51,7 +51,6 @@ export const serverApi = createApi({ getServerVersion: builder.query({ query: () => ({ headers: { - // "content-type": "text/plain", accept: "text/plain" }, url: `/admin/system/version`, @@ -128,7 +127,7 @@ export const serverApi = createApi({ body: data }) }), - updateLogo: builder.mutation({ + updateLogo: builder.mutation({ query: (data) => ({ headers: { "content-type": "image/png" diff --git a/src/app/services/user.ts b/src/app/services/user.ts index c29c0b36..a2e444b1 100644 --- a/src/app/services/user.ts +++ b/src/app/services/user.ts @@ -9,13 +9,14 @@ import BASE_URL, { ContentTypes } from "../config"; import { onMessageSendStarted } from "./handlers"; import handleChatMessage from "../../common/hook/useStreaming/chat.handler"; import { User, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user"; -import { ContentType, MuteDTO } from "../../types/message"; +import { ChatMessage, ContentTypeKey, MuteDTO } from "../../types/message"; +import { RootState } from "../store"; export const userApi = createApi({ reducerPath: "userApi", baseQuery, endpoints: (builder) => ({ - getUsers: builder.query({ + getUsers: builder.query({ query: () => ({ url: `user` }), transformResponse: (data: User[]) => { return data.map((user) => { @@ -74,7 +75,7 @@ export const userApi = createApi({ } } }), - updateAvatar: builder.mutation({ + updateAvatar: builder.mutation({ query: (data) => ({ headers: { "content-type": "image/png" @@ -92,7 +93,10 @@ export const userApi = createApi({ }) }), - sendMsg: builder.mutation({ + sendMsg: builder.mutation< + number, + { id: number; content: string; type: ContentTypeKey; properties?: object } + >({ query: ({ id, content, type = "text", properties = "" }) => ({ headers: { "content-type": ContentTypes[type], @@ -108,7 +112,7 @@ export const userApi = createApi({ await onMessageSendStarted.call(this, param1, param2, "user"); } }), - getHistoryMessages: builder.query({ + getHistoryMessages: builder.query({ query: ({ id, mid = null, limit = 100 }) => ({ url: mid ? `/user/${id}/history?before=${mid}&limit=${limit}` @@ -118,7 +122,7 @@ export const userApi = createApi({ const { data: messages } = await queryFulfilled; if (messages?.length) { messages.forEach((msg) => { - handleChatMessage(msg, dispatch, getState()); + handleChatMessage(msg, dispatch, getState() as RootState); }); } } diff --git a/src/app/slices/auth.data.ts b/src/app/slices/auth.data.ts index 95aea646..f1ea066c 100644 --- a/src/app/slices/auth.data.ts +++ b/src/app/slices/auth.data.ts @@ -7,7 +7,7 @@ import { KEY_TOKEN, KEY_UID } from "../config"; -import { AuthData, AuthToken, User } from "../../types/auth"; +import { AuthData, AuthToken, RenewTokenResponse, User } from "../../types/auth"; interface State { initialized: boolean; @@ -67,7 +67,7 @@ const authDataSlice = createSlice({ updateInitialized(state, action: PayloadAction) { state.initialized = action.payload; }, - updateToken(state, action: PayloadAction) { + updateToken(state, action: PayloadAction) { const { token, refresh_token, expired_in } = action.payload; state.token = token; const et = +new Date() + Number(expired_in) * 1000; diff --git a/src/app/slices/channels.ts b/src/app/slices/channels.ts index 409ebc60..42128070 100644 --- a/src/app/slices/channels.ts +++ b/src/app/slices/channels.ts @@ -53,7 +53,7 @@ const channelsSlice = createSlice({ }, updateChannel(state, action: PayloadAction) { const ignoreInPublic = ["add_member", "remove_member"]; - const { gid, operation, members = [], ...rest } = action.payload; + const { gid, operation = "", members = [], ...rest } = action.payload; const currChannel = state.byId[gid]; if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return; switch (operation) { diff --git a/src/app/slices/ui.ts b/src/app/slices/ui.ts index 078316de..18d02897 100644 --- a/src/app/slices/ui.ts +++ b/src/app/slices/ui.ts @@ -59,7 +59,7 @@ const uiSlice = createSlice({ }, updateRememberedNavs( state, - action: PayloadAction<{ key?: "chat" | "user"; path: string | null } | undefined> + action: PayloadAction<{ key?: "chat" | "user"; path?: string | null } | undefined> ) { const { key = "chat", path = null } = action.payload || {}; state.rememberedNavs[key] = path; diff --git a/src/app/slices/users.ts b/src/app/slices/users.ts index 9cad2334..5ad3c24c 100644 --- a/src/app/slices/users.ts +++ b/src/app/slices/users.ts @@ -1,7 +1,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { isNull, omitBy } from "lodash"; import BASE_URL from "../config"; -import { User } from "../../types/auth"; +import { User } from "../../types/user"; import { UserLog, UserState } from "../../types/sse"; export interface StoredUser extends User { diff --git a/src/common/component/BlankPlaceholder.tsx b/src/common/component/BlankPlaceholder.tsx index 44895124..f603d080 100644 --- a/src/common/component/BlankPlaceholder.tsx +++ b/src/common/component/BlankPlaceholder.tsx @@ -68,7 +68,7 @@ const Styled = styled.div` `; interface Props { - type?: "chat"; + type?: "chat" | "user"; } const BlankPlaceholder: FC = ({ type = "chat" }) => { diff --git a/src/common/component/Channel.tsx b/src/common/component/Channel.tsx index be59fa90..2eb194f2 100644 --- a/src/common/component/Channel.tsx +++ b/src/common/component/Channel.tsx @@ -32,7 +32,6 @@ const StyledWrapper = styled.div` } } .name { - /* user-select: text; */ display: flex; font-weight: 600; font-size: 14px; diff --git a/src/common/component/ChannelIcon.tsx b/src/common/component/ChannelIcon.tsx index b477ff10..6d0f06b5 100644 --- a/src/common/component/ChannelIcon.tsx +++ b/src/common/component/ChannelIcon.tsx @@ -3,18 +3,17 @@ import styled from "styled-components"; import HashIcon from "../../assets/icons/channel.svg"; import LockHashIcon from "../../assets/icons/channel.private.svg"; -interface Props { - personal?: boolean; - muted?: boolean; - className?: string; -} - const Styled = styled.div` display: flex; &.muted path { fill: #d0d5dd; } `; +interface Props { + personal?: boolean; + muted?: boolean; + className?: string; +} const ChannelIcon: FC = ({ personal = false, muted = false, className = "" }) => { return ( diff --git a/src/common/component/CurrentUser.tsx b/src/common/component/CurrentUser.tsx index 8a3449c3..247767ad 100644 --- a/src/common/component/CurrentUser.tsx +++ b/src/common/component/CurrentUser.tsx @@ -4,6 +4,7 @@ import micIcon from "../../assets/icons/mic.on.svg?url"; import Avatar from "./Avatar"; import useConfig from "../hook/useConfig"; import { useAppSelector } from "../../app/store"; +import { FC } from "react"; const StyledWrapper = styled.div` background-color: #f4f4f5; @@ -60,8 +61,8 @@ const StyledWrapper = styled.div` } } `; - -export default function CurrentUser() { +type Props = {}; +const CurrentUser: FC = () => { const { values: agoraConfig } = useConfig("agora"); const currUser = useAppSelector((store) => { return store.authData.user; @@ -86,4 +87,5 @@ export default function CurrentUser() { )} ); -} +}; +export default CurrentUser; diff --git a/src/common/component/EmojiPicker.tsx b/src/common/component/EmojiPicker.tsx index 54e24555..3d4cc67d 100644 --- a/src/common/component/EmojiPicker.tsx +++ b/src/common/component/EmojiPicker.tsx @@ -11,8 +11,6 @@ interface Props { const StyledWrapper = styled.div` filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25)); border-radius: 12px; - /* height: 358px; - overflow: hidden; */ .emoji-mart { border: none; border-radius: 12px; @@ -43,10 +41,6 @@ const EmojiPicker: FC = ({ onSelect, ...rest }) => { perLine={10} emojiSize={24} emojiTooltip={true} - // set="twitter" - // data={data} - // set="twitter" - // showPreview={false} showSkinTones={false} onSelect={onSelect} {...rest} diff --git a/src/common/component/FAQ.tsx b/src/common/component/FAQ.tsx index 3d2264e8..33a56b1c 100644 --- a/src/common/component/FAQ.tsx +++ b/src/common/component/FAQ.tsx @@ -1,3 +1,4 @@ +import { FC } from "react"; import styled from "styled-components"; import { useGetServerVersionQuery } from "../../app/services/server"; @@ -6,8 +7,8 @@ const Styled = styled.div` flex-direction: column; gap: 12px; `; - -export default function FAQ() { +type Props = {}; +const FAQ: FC = () => { const { data: serverVersion } = useGetServerVersionQuery(); return ( @@ -16,4 +17,5 @@ export default function FAQ() {
Build Timestamp: {process.env.REACT_APP_BUILD_TIME}
); -} +}; +export default FAQ; diff --git a/src/common/component/FileMessage/index.tsx b/src/common/component/FileMessage/index.tsx index 92e55e1e..bf2214c5 100644 --- a/src/common/component/FileMessage/index.tsx +++ b/src/common/component/FileMessage/index.tsx @@ -25,7 +25,7 @@ interface Props { context: "user" | "channel"; to: number; created_at: number; - from_uid?: number; + from_uid: number; content: string; download: string; thumbnail: string; @@ -41,7 +41,7 @@ const FileMessage: FC = ({ context, to, created_at, - from_uid = null, + from_uid, content = "", download = "", thumbnail = "", diff --git a/src/common/component/ForwardModal/index.tsx b/src/common/component/ForwardModal/index.tsx index 4a3da547..4a46c648 100644 --- a/src/common/component/ForwardModal/index.tsx +++ b/src/common/component/ForwardModal/index.tsx @@ -1,4 +1,4 @@ -import { useState, MouseEvent, ChangeEvent } from "react"; +import { useState, MouseEvent, ChangeEvent, FC } from "react"; // import toast from "react-hot-toast"; import Modal from "../Modal"; import Button from "../styled/Button"; @@ -15,8 +15,12 @@ import useFilteredUsers from "../../hook/useFilteredUsers"; import CloseIcon from "../../../assets/icons/close.circle.svg"; import StyledCheckbox from "../styled/Checkbox"; import toast from "react-hot-toast"; +interface IProps { + mids: number[]; + closeModal: () => void; +} -export default function ForwardModal({ mids, closeModal }) { +const ForwardModal: FC = ({ mids, closeModal }) => { const [appendText, setAppendText] = useState(""); const { sendMessages } = useSendMessage(); const { forwardMessage, forwarding } = useForwardMessage(); @@ -176,4 +180,5 @@ export default function ForwardModal({ mids, closeModal }) { ); -} +}; +export default ForwardModal; diff --git a/src/common/component/GithubLoginButton.tsx b/src/common/component/GithubLoginButton.tsx index 9bd7f821..e0e60058 100644 --- a/src/common/component/GithubLoginButton.tsx +++ b/src/common/component/GithubLoginButton.tsx @@ -6,6 +6,7 @@ import { KEY_LOCAL_MAGIC_TOKEN } from "../../app/config"; import Button from "./styled/Button"; import { useLoginMutation } from "../../app/services/auth"; import toast from "react-hot-toast"; +import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query"; const StyledSocialButton = styled(Button)` width: 100%; @@ -51,7 +52,8 @@ const GithubLoginButton: FC = ({ type = "login", client_id }) => { }, [isSuccess]); useEffect(() => { if (error) { - switch (error?.status) { + // todo: why? + switch ((error as FetchBaseQueryError).status) { case 410: toast.error( "No associated account found, please user admin for an invitation link to join." diff --git a/src/common/component/GoogleLoginButton.tsx b/src/common/component/GoogleLoginButton.tsx index 87588d23..74648b3e 100644 --- a/src/common/component/GoogleLoginButton.tsx +++ b/src/common/component/GoogleLoginButton.tsx @@ -66,13 +66,13 @@ const GoogleLoginInner: FC = ({ type = "login", loaded, loadError }) => { }, [error]); return ( - +
{ login({ magic_token, - id_token: res.credential, + id_token: res.credential || "", type: "google" }); }} diff --git a/src/common/component/ImagePreviewModal.tsx b/src/common/component/ImagePreviewModal.tsx index d67f189f..e5eb07fc 100644 --- a/src/common/component/ImagePreviewModal.tsx +++ b/src/common/component/ImagePreviewModal.tsx @@ -66,10 +66,10 @@ const StyledWrapper = styled.div` export interface PreviewImageData { originUrl: string; - thumbnail: string; - downloadLink: string; - name: string; - type: string; + thumbnail?: string; + downloadLink?: string; + name?: string; + type?: string; } interface Props { diff --git a/src/common/component/InviteLink.tsx b/src/common/component/InviteLink.tsx index 8dba8dbb..5b29a8b2 100644 --- a/src/common/component/InviteLink.tsx +++ b/src/common/component/InviteLink.tsx @@ -2,6 +2,7 @@ import styled from "styled-components"; import useInviteLink from "../hook/useInviteLink"; import Input from "./styled/Input"; import Button from "./styled/Button"; +import { FC } from "react"; const StyledWrapper = styled.div` display: flex; @@ -37,8 +38,8 @@ const StyledWrapper = styled.div` margin-bottom: 20px; } `; - -export default function InviteLink() { +type Props = {}; +const InviteLink: FC = () => { const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink(); const handleNewLink = () => { generateNewLink(); @@ -59,4 +60,6 @@ export default function InviteLink() { ); -} +}; + +export default InviteLink; diff --git a/src/common/component/ManageMembers.tsx b/src/common/component/ManageMembers.tsx index 6dcadebd..a474c8ee 100644 --- a/src/common/component/ManageMembers.tsx +++ b/src/common/component/ManageMembers.tsx @@ -135,7 +135,15 @@ const ManageMembers: FC = ({ cid }) => { } }, [updateSuccess]); - const handleToggleRole = ({ ignore = false, uid = null, isAdmin = true }) => { + const handleToggleRole = ({ + ignore = false, + uid, + isAdmin = true + }: { + ignore: boolean; + uid: number; + isAdmin: boolean; + }) => { hideAll(); if (ignore) return; updateUser({ id: uid, is_admin: isAdmin }); @@ -154,7 +162,9 @@ const ManageMembers: FC = ({ cid }) => {
    {uids.map((uid) => { - const { name, email, is_admin } = users.byId[uid]; + const currUser = users.byId[uid]; + if (!currUser) return null; + const { name, email, is_admin } = currUser; const owner = channel && channel.owner == uid; const switchRoleVisible = loginUser.is_admin && loginUser.uid !== uid; const dotsVisible = email || loginUser?.is_admin; diff --git a/src/common/component/Manifest/index.tsx b/src/common/component/Manifest/index.tsx index 4dc8a220..7d643342 100644 --- a/src/common/component/Manifest/index.tsx +++ b/src/common/component/Manifest/index.tsx @@ -1,13 +1,14 @@ -import { useEffect, useState, useRef, SyntheticEvent } from "react"; +import { useEffect, useState, useRef, FC } from "react"; +import { BeforeInstallPromptEvent } from "../../../types/global"; import Prompt from "./Prompt"; import usePrompt from "./usePrompt"; - -export default function Manifest() { +interface IProps {} +const Manifest: FC = () => { const { setCanceled: setCanceled, prompted } = usePrompt(); - const deferredPromptRef = useRef(null); + const deferredPromptRef = useRef(null); const [popup, setPopup] = useState(false); useEffect(() => { - const handleInstallPromotion = (e: SyntheticEvent) => { + const handleInstallPromotion = (e: BeforeInstallPromptEvent) => { // Prevent the mini-infobar from appearing on mobile e.preventDefault(); // Stash the event so it can be triggered later. @@ -21,20 +22,6 @@ export default function Manifest() { deferredPromptRef.current = null; setPopup(false); }; - // if (isSuccess && data) { - // console.log("server", data); - // manifest.name = `${data.name}'s Chat`; - // // const stringManifest = JSON.stringify(manifest); - // // const blob = new Blob([stringManifest], { type: "application/json" }); - // // const manifestURL = URL.createObjectURL(blob); - // let content = encodeURIComponent(JSON.stringify(manifest)); - // let manifestURL = "data:application/manifest+json," + content; - // const manifestEle = document.querySelector("#my-manifest-placeholder"); - // if (manifestEle) { - // manifestEle.setAttribute("href", manifestURL); - // } - - // } window.addEventListener("beforeinstallprompt", handleInstallPromotion, true); window.addEventListener("appinstalled", handleInstalled); return () => { @@ -61,4 +48,5 @@ export default function Manifest() { }; if (!popup || prompted) return null; return ; -} +}; +export default Manifest; diff --git a/src/common/component/MarkdownEditor/index.tsx b/src/common/component/MarkdownEditor/index.tsx index 59b33eba..dc635b30 100644 --- a/src/common/component/MarkdownEditor/index.tsx +++ b/src/common/component/MarkdownEditor/index.tsx @@ -27,7 +27,7 @@ function MarkdownEditor({ const editorInstance = editor.getInstance(); editorInstance.removeHook("addImageBlobHook"); editorInstance.addHook("addImageBlobHook", async (blob, callback) => { - const { thumbnail = "" } = await uploadFile(blob); + const { thumbnail = "" } = (await uploadFile(blob)) || {}; callback(thumbnail); }); setEditorInstance(editorInstance); diff --git a/src/common/component/MarkdownRender.tsx b/src/common/component/MarkdownRender.tsx index b322a2a2..7c58812b 100644 --- a/src/common/component/MarkdownRender.tsx +++ b/src/common/component/MarkdownRender.tsx @@ -1,14 +1,8 @@ -import { useEffect, useState, useRef, FC, MouseEvent } from "react"; +import { useEffect, useState, useRef, FC } from "react"; import "prismjs/themes/prism.css"; import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css"; import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js"; -// import "prismjs/themes/prism.css"; -// import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css"; -// import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight"; -// Import prismjs -// import Prism from "prismjs"; - import { Viewer } from "@toast-ui/react-editor"; import styled from "styled-components"; import ImagePreviewModal, { PreviewImageData } from "./ImagePreviewModal"; @@ -24,37 +18,37 @@ const Styled = styled.div` } `; -interface Props { +interface IProps { content: string; } - -const MarkdownRender: FC = ({ content }) => { - const mdContainer = useRef(null); +const MarkdownRender: FC = ({ content }) => { + const mdContainer = useRef(); const [previewImage, setPreviewImage] = useState(null); useEffect(() => { const container = mdContainer?.current; - if (container) { - // 点击查看大图 - container.addEventListener( - "click", - (evt) => { - console.log(evt); - evt.stopPropagation(); - const { target } = evt; - // 图片 - if (target.nodeName == "IMG") { - const urlObj = new URL(target.src); - const originUrl = `${urlObj.origin}${ - urlObj.pathname - }?file_path=${urlObj.searchParams.get("file_path")}`; - const data = { originUrl }; - setPreviewImage(data); - } - }, - true - ); - } + if (!container) return; + // 点击查看大图 + // todo: 事件代理 + container.addEventListener( + "click", + (evt) => { + console.log(evt); + evt.stopPropagation(); + const target = evt.target as HTMLImageElement; + if (!target) return; + // 图片 + if (target.nodeName == "IMG") { + const urlObj = new URL(target.src); + const originUrl = `${urlObj.origin}${urlObj.pathname}?file_path=${urlObj.searchParams.get( + "file_path" + )}`; + const data = { originUrl }; + setPreviewImage(data); + } + }, + true + ); }, []); const closePreviewModal = () => { diff --git a/src/common/component/Message/FavoredMessage.tsx b/src/common/component/Message/FavoredMessage.tsx new file mode 100644 index 00000000..a5d87e47 --- /dev/null +++ b/src/common/component/Message/FavoredMessage.tsx @@ -0,0 +1,66 @@ +import { FC, ReactElement, useEffect, useState } from "react"; +import styled from "styled-components"; +import StyledMsg from "./styled"; +import renderContent from "./renderContent"; +import Avatar from "../Avatar"; +import useFavMessage from "../../hook/useFavMessage"; + +const StyledFav = styled.div` + display: flex; + flex-direction: column; + border-radius: var(--br); + background-color: #f4f4f5; +`; +type Props = { + id?: number; +}; +const FavoredMessage: FC = ({ id }) => { + const { favorites } = useFavMessage({}); + const [msgs, setMsgs] = useState(null); + + useEffect(() => { + const current = favorites.find((f) => f.id == id); + const { messages } = current || {}; + if (!messages) return; + const favorite_mids = messages.map(({ from_mid }) => +from_mid) || []; + + setMsgs( + +
    + {messages.map((msg, idx) => { + const { user = {}, download, content, content_type, properties, thumbnail } = msg; + return ( + + {user && ( +
    + +
    + )} +
    +
    + {user?.name} +
    +
    + {renderContent({ + download, + content, + content_type, + properties, + thumbnail + })} +
    +
    +
    + ); + })} +
    +
    + ); + }, [favorites, id]); + + if (!id) return null; + + return msgs; +}; + +export default FavoredMessage; diff --git a/src/common/component/Message/FavoritedMessage.tsx b/src/common/component/Message/FavoritedMessage.tsx deleted file mode 100644 index d31e0393..00000000 --- a/src/common/component/Message/FavoritedMessage.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useEffect, useState } from "react"; -import styled from "styled-components"; -import StyledMsg from "./styled"; -import renderContent from "./renderContent"; -import Avatar from "../Avatar"; -import useFavMessage from "../../hook/useFavMessage"; - -const StyledFav = styled.div` - display: flex; - flex-direction: column; - border-radius: var(--br); - background-color: #f4f4f5; -`; - -const FavoritedMessage = ({ id }) => { - const { favorites } = useFavMessage({}); - const [msgs, setMsgs] = useState(null); - - useEffect(() => { - const current = favorites.find((f) => f.id == id); - const { messages } = current || {}; - if (messages) { - const favorite_mids = messages.map(({ from_mid }) => from_mid) || []; - - setMsgs( - -
    - {messages.map((msg, idx) => { - const { user = {}, download, content, content_type, properties, thumbnail } = msg; - return ( - - {user && ( -
    - -
    - )} -
    -
    - {user?.name} -
    -
    - {renderContent({ - download, - content, - content_type, - properties, - thumbnail - })} -
    -
    -
    - ); - })} -
    -
    - ); - } - }, [favorites, id]); - - if (!id) return null; - - return msgs; -}; - -export default FavoritedMessage; diff --git a/src/common/component/Meta.tsx b/src/common/component/Meta.tsx index 2f76b4d6..463060e1 100644 --- a/src/common/component/Meta.tsx +++ b/src/common/component/Meta.tsx @@ -1,8 +1,9 @@ +import { FC } from "react"; import { Helmet } from "react-helmet"; import BASE_URL from "../../app/config"; import { useGetServerQuery } from "../../app/services/server"; - -export default function Meta() { +type Props = {}; +const Meta: FC = () => { const { data, isSuccess } = useGetServerQuery(); return ( @@ -11,4 +12,5 @@ export default function Meta() { {isSuccess && {data.name} Web App} ); -} +}; +export default Meta; diff --git a/src/common/component/Notification/useDeviceToken.ts b/src/common/component/Notification/useDeviceToken.ts index 5423fb3f..02f19478 100644 --- a/src/common/component/Notification/useDeviceToken.ts +++ b/src/common/component/Notification/useDeviceToken.ts @@ -2,9 +2,8 @@ import { useState } from "react"; import { initializeApp } from "firebase/app"; import { getToken, getMessaging } from "firebase/messaging"; import { firebaseConfig } from "../../../app/config"; - -const useDeviceToken = (vapidKey) => { - const [token, setToken] = useState(null); +const useDeviceToken = (vapidKey: string) => { + const [token, setToken] = useState(null); // https only if (navigator.serviceWorker) { const messaging = getMessaging(initializeApp(firebaseConfig)); @@ -17,7 +16,7 @@ const useDeviceToken = (vapidKey) => { console.log("current token for client: ", currentToken); setToken(currentToken); // updateDeviceToken(currentToken) - // Perform any other neccessary action with the token + // Perform any other necessary action with the token } else { // Show permission request UI console.log("No registration token available. Request permission to generate one."); diff --git a/src/common/component/Profile/index.tsx b/src/common/component/Profile/index.tsx index aec5122c..0ddd836f 100644 --- a/src/common/component/Profile/index.tsx +++ b/src/common/component/Profile/index.tsx @@ -11,8 +11,8 @@ import useUserOperation from "../../hook/useUserOperation"; import { useAppSelector } from "../../../app/store"; interface Props { - uid?: number; - type: string; + uid: number; + type?: "embed" | "card"; cid?: number; } diff --git a/src/common/component/Search.tsx b/src/common/component/Search.tsx index 2d406267..878b0cb6 100644 --- a/src/common/component/Search.tsx +++ b/src/common/component/Search.tsx @@ -4,6 +4,7 @@ import searchIcon from "../../assets/icons/search.svg?url"; import addIcon from "../../assets/icons/add.svg?url"; import AddEntriesMenu from "./AddEntriesMenu"; import Tooltip from "./Tooltip"; +import { FC } from "react"; const StyledWrapper = styled.div` position: relative; @@ -32,8 +33,8 @@ const StyledWrapper = styled.div` cursor: pointer; } `; - -export default function Search() { +type Props = {}; +const Search: FC = () => { return (
    @@ -47,4 +48,5 @@ export default function Search() { ); -} +}; +export default Search; diff --git a/src/common/component/Send/index.tsx b/src/common/component/Send/index.tsx index 5f805d1c..ba522d64 100644 --- a/src/common/component/Send/index.tsx +++ b/src/common/component/Send/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, FC } from "react"; import useSendMessage from "../../hook/useSendMessage"; import useAddLocalFileMessage from "../../hook/useAddLocalFileMessage"; import { updateInputMode } from "../../../app/slices/ui"; @@ -20,11 +20,15 @@ const Modes = { text: "text", markdown: "markdown" }; -function Send({ +interface IProps { + context?: "channel" | "user"; + id: number; +} +const Send: FC = ({ // 发给谁,或者是channel,或者是user context = "channel", - id = "" -}) { + id +}) => { const { resetStageFiles } = useUploadFile({ context, id }); const { getDraft, getUpdateDraft } = useDraft({ context, id }); const editor = useMixedEditor(`${context}_${id}`); @@ -111,7 +115,7 @@ function Send({ resetStageFiles(); } }; - const sendMarkdown = async (content) => { + const sendMarkdown = async (content: string) => { sendMessage({ id, reply_mid: replying_mid, @@ -173,7 +177,7 @@ function Send({
    ); -} +}; export default Send; // export default memo(Send, (prevs, nexts) => { diff --git a/src/common/hook/useAddLocalFileMessage.ts b/src/common/hook/useAddLocalFileMessage.ts index d95913d6..48ed84cc 100644 --- a/src/common/hook/useAddLocalFileMessage.ts +++ b/src/common/hook/useAddLocalFileMessage.ts @@ -5,16 +5,16 @@ import { addChannelMsg } from "../../app/slices/message.channel"; import { addUserMsg } from "../../app/slices/message.user"; interface IProps { - context: "channel" | "uesr"; + context: "channel" | "user"; to: number; } export default function useAddLocalFileMessage({ context, to }: IProps) { const dispatch = useDispatch(); const addContextMessage = context == "channel" ? addChannelMsg : addUserMsg; - const addLocalFileMesage = (data) => { + const addLocalFileMessage = (data) => { dispatch(addMessage(data)); dispatch(addContextMessage({ id: to, mid: data.mid })); }; - return addLocalFileMesage; + return addLocalFileMessage; } diff --git a/src/common/hook/useStreaming/index.ts b/src/common/hook/useStreaming/index.ts index f8a53e1e..baf8aa36 100644 --- a/src/common/hook/useStreaming/index.ts +++ b/src/common/hook/useStreaming/index.ts @@ -74,7 +74,7 @@ export default function useStreaming() { isError } = await renewToken({ token, - refreshToken + refresh_token: refreshToken }); if (isError) return; api_token = newToken; diff --git a/src/common/hook/useUploadFile.ts b/src/common/hook/useUploadFile.ts index 2f3151d4..aac816e0 100644 --- a/src/common/hook/useUploadFile.ts +++ b/src/common/hook/useUploadFile.ts @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef, FC } from "react"; import toast from "react-hot-toast"; import { updateUploadFiles } from "../../app/slices/ui"; import BASE_URL, { FILE_SLICE_SIZE } from "../../app/config"; @@ -6,8 +6,12 @@ import { usePrepareUploadFileMutation, useUploadFileMutation } from "../../app/s import { useAppDispatch, useAppSelector } from "../../app/store"; // todo: check props type -export default function useUploadFile(props: { context: string; id: string } | object = {}) { - const { context = "", id = "" } = props; +interface IProps { + context: "channel" | "user"; + id: number; +} +const useUploadFile = (props: IProps) => { + const { context, id } = props; const dispatch = useAppDispatch(); const { stageFiles, replying } = useAppSelector((store) => { return { @@ -112,7 +116,7 @@ export default function useUploadFile(props: { context: string; id: string } | o canceledRef.current = true; }; - const removeStageFile = (idx) => { + const removeStageFile = (idx: number) => { dispatch(updateUploadFiles({ context, id, operation: "remove", index: idx })); }; @@ -128,7 +132,7 @@ export default function useUploadFile(props: { context: string; id: string } | o dispatch(updateUploadFiles({ context, id, operation: "reset" })); }; - const updateStageFile = (idx, data = {}) => { + const updateStageFile = (idx: number, data = {}) => { dispatch( updateUploadFiles({ context, @@ -156,4 +160,5 @@ export default function useUploadFile(props: { context: string; id: string } | o removeStageFile, updateStageFile }; -} +}; +export default useUploadFile; diff --git a/src/common/hook/useUserOperation.ts b/src/common/hook/useUserOperation.ts index 81769aba..6ade4d21 100644 --- a/src/common/hook/useUserOperation.ts +++ b/src/common/hook/useUserOperation.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, FC } from "react"; +import { useState, useEffect } from "react"; import toast from "react-hot-toast"; // import { ContentTypes } from "../../../app/config"; import { useNavigate, useMatch } from "react-router-dom"; diff --git a/src/routes/chat/FavList.tsx b/src/routes/chat/FavList.tsx index b30c24e4..707cc30b 100644 --- a/src/routes/chat/FavList.tsx +++ b/src/routes/chat/FavList.tsx @@ -1,7 +1,7 @@ // import React from "react"; // import { useSelector } from "react-redux"; import styled from "styled-components"; -import FavoritedMessage from "../../common/component/Message/FavoritedMessage"; +import FavoredMessage from "../../common/component/Message/FavoredMessage"; import IconSurprise from "../../assets/icons/emoji.suprise.svg"; // import IconForward from "../../../assets/icons/forward.svg"; import IconRemove from "../../assets/icons/close.svg"; @@ -107,7 +107,7 @@ export default function FavList({ cid = null, uid = null }) { console.log("favv", id); return (
  • - +
    {/*
    ); })} diff --git a/src/routes/login/OidcLoginButton.tsx b/src/routes/login/OidcLoginButton.tsx index ff5aa5c0..afbd2b01 100644 --- a/src/routes/login/OidcLoginButton.tsx +++ b/src/routes/login/OidcLoginButton.tsx @@ -1,13 +1,14 @@ /* eslint-disable no-undef */ -import { useState } from "react"; +import { FC, useState } from "react"; import styled from "styled-components"; import StyledModal from "../../common/component/styled/Modal"; import Modal from "../../common/component/Modal"; import { StyledSocialButton } from "./styled"; import StyledButton from "../../common/component/styled/Button"; import OidcLoginEntry from "./OidcLoginEntry"; +import { OIDCConfig } from "../../types/auth"; -const StyledOicdLoginModal = styled(StyledModal)` +const StyledOidcLoginModal = styled(StyledModal)` text-align: center; padding: 32px 32px 16px; @@ -21,13 +22,15 @@ const StyledOicdLoginModal = styled(StyledModal)` height: 24px; } - &Cancel { + &.buttonCancel { color: #8f8f8f; } } `; - -export default function OidcLoginButton({ issuers }) { +interface IProps { + issuers?: OIDCConfig[]; +} +const OidcLoginButton: FC = ({ issuers }) => { const [modal, setModal] = useState(false); if (!issuers) return null; return ( @@ -41,7 +44,7 @@ export default function OidcLoginButton({ issuers }) { {modal && ( - + {issuers .filter((issuer) => issuer.enable) .map((issuer, index) => ( @@ -55,9 +58,10 @@ export default function OidcLoginButton({ issuers }) { > Close - + )} ); -} +}; +export default OidcLoginButton; diff --git a/src/routes/login/OidcLoginEntry.tsx b/src/routes/login/OidcLoginEntry.tsx index bc71cb40..86ebadc0 100644 --- a/src/routes/login/OidcLoginEntry.tsx +++ b/src/routes/login/OidcLoginEntry.tsx @@ -1,27 +1,24 @@ -/* eslint-disable no-undef */ -import { useEffect } from "react"; +import { useEffect, FC } from "react"; import { useGetOpenidMutation } from "../../app/services/auth"; +import { OIDCConfig } from "../../types/auth"; import { StyledSocialButton } from "./styled"; -export default function OidcLoginEntry({ issuer }) { +const OidcLoginEntry: FC<{ issuer: OIDCConfig }> = ({ issuer }) => { const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation(); const handleSolidLogin = () => { getOpenId({ - // issuer: "solidweb.org", - issuer, + issuer: issuer.domain, redirect_uri: `${location.origin}/#/login` }); }; useEffect(() => { - if (isSuccess) { - console.log("wtf", data); + if (isSuccess && data) { const { url } = data; location.href = url; } }, [data, isSuccess]); - console.log(issuer); return ( @@ -29,4 +26,5 @@ export default function OidcLoginEntry({ issuer }) { Login with {issuer.domain} ); -} +}; +export default OidcLoginEntry; diff --git a/src/routes/login/index.tsx b/src/routes/login/index.tsx index 5cacb5b9..6a960225 100644 --- a/src/routes/login/index.tsx +++ b/src/routes/login/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { useState, useEffect } from "react"; +import { useState, useEffect, FormEvent, ChangeEvent } from "react"; import toast from "react-hot-toast"; import BASE_URL from "../../app/config"; // import web3 from "web3"; @@ -16,6 +16,7 @@ import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig"; import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig"; import GoogleLoginButton from "../../common/component/GoogleLoginButton"; import GithubLoginButton from "../../common/component/GithubLoginButton"; +import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query"; export default function LoginPage() { const { data: enableSMTP } = useGetSMTPStatusQuery(); @@ -53,7 +54,6 @@ export default function LoginPage() { } // magic link if (magic_token && typeof exists !== "undefined") { - // console.log("tokken", token, exists); const isLogin = exists == "true"; if (isLogin) { // login @@ -71,7 +71,7 @@ export default function LoginPage() { useEffect(() => { if (error) { console.log("login err", error); - switch (error.status) { + switch ((error as FetchBaseQueryError).status) { case 401: toast.error("Username or Password incorrect"); break; @@ -94,7 +94,7 @@ export default function LoginPage() { } }, [isSuccess]); - const handleLogin = (evt) => { + const handleLogin = (evt: FormEvent) => { evt.preventDefault(); console.log("wtf", input); login({ @@ -103,10 +103,10 @@ export default function LoginPage() { }); }; - const handleInput = (evt) => { - const { type } = evt.target.dataset; + const handleInput = (evt: ChangeEvent) => { + const { type } = evt.target.dataset as { type?: "email" | "password" }; const { value } = evt.target; - // console.log(type, value); + if (!type) return; setInput((prev) => { prev[type] = value; return { ...prev }; diff --git a/src/routes/oauth/index.tsx b/src/routes/oauth/index.tsx index 4a428e96..e9045887 100644 --- a/src/routes/oauth/index.tsx +++ b/src/routes/oauth/index.tsx @@ -8,10 +8,10 @@ import toast from "react-hot-toast"; import { setAuthData } from "../../app/slices/auth.data"; export default function OAuthPage() { - const [login, { data, isSuccess, isError, error: loginError }] = useLoginMutation(); + const [login, { data, isSuccess, isError }] = useLoginMutation(); const { token } = useParams(); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const dispatch = useDispatch(); const navigateTo = useNavigate(); useEffect(() => { @@ -28,9 +28,9 @@ export default function OAuthPage() { }, [token]); useEffect(() => { if (isError) { - setError(loginError); + setError("Something Error"); } - }, [isError, loginError]); + }, [isError]); useEffect(() => { if (isSuccess && data) { diff --git a/src/routes/setting/APIConfig.tsx b/src/routes/setting/APIConfig.tsx index f3d91a91..8d1ddffd 100644 --- a/src/routes/setting/APIConfig.tsx +++ b/src/routes/setting/APIConfig.tsx @@ -20,7 +20,6 @@ const StyledConfirm = styled.div` gap: 10px; width: 250px; .tip { - /* word-break: break-all; */ color: orange; font-size: 12px; line-height: 1.5; @@ -100,7 +99,7 @@ export default function APIConfig() {
    Tip: The security key agreed between the server and the third-party app is used to encrypt - the communication data.{" "} + the communication data.
    ); diff --git a/src/routes/setting/index.tsx b/src/routes/setting/index.tsx index e02eb051..259709a9 100644 --- a/src/routes/setting/index.tsx +++ b/src/routes/setting/index.tsx @@ -4,7 +4,7 @@ import StyledSettingContainer from "../../common/component/StyledSettingContaine import useNavs from "./navs"; import LogoutConfirmModal from "./LogoutConfirmModal"; -let from: string | null = null; +let from: string = ""; export default function Setting() { const [searchParams] = useSearchParams(); @@ -15,10 +15,9 @@ export default function Setting() { const [logoutConfirm, setLogoutConfirm] = useState(false); const navigateTo = useNavigate(); const close = () => { - // dispatch(toggleSetting()); // todo: check usage navigateTo(from!); - from = null; + from = ""; }; const toggleLogoutConfirm = () => { diff --git a/src/routes/settingChannel/index.tsx b/src/routes/settingChannel/index.tsx index 978468fe..f7056481 100644 --- a/src/routes/settingChannel/index.tsx +++ b/src/routes/settingChannel/index.tsx @@ -6,20 +6,20 @@ import DeleteConfirmModal from "./DeleteConfirmModal"; import useNavs from "./navs"; import { useAppSelector } from "../../app/store"; -let from: string | null = null; +let from: string = ""; export default function ChannelSetting() { - const { cid } = useParams(); + const { cid = 0 } = useParams(); const { loginUser, channel } = useAppSelector((store) => { return { loginUser: store.authData.user, - channel: store.channels.byId[cid] + channel: cid ? store.channels.byId[+cid] : undefined }; }); const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const navs = useNavs(cid); - const flatenNavs = navs + const navs = useNavs(+cid); + const flattenNavs = navs .map(({ items }) => { return items; }) @@ -30,16 +30,16 @@ export default function ChannelSetting() { const [leaveConfirm, setLeaveConfirm] = useState(false); const close = () => { navigate(from); - from = null; + from = ""; }; - const toggleDeleteConfrim = () => { + const toggleDeleteConfirm = () => { setDeleteConfirm((prev) => !prev); }; - const toggleLeaveConfrim = () => { + const toggleLeaveConfirm = () => { setLeaveConfirm((prev) => !prev); }; if (!cid) return null; - const currNav = flatenNavs.find((n) => n.name == navKey) || flatenNavs[0]; + const currNav = flattenNavs.find((n) => n.name == navKey) || flattenNavs[0]; const canDelete = loginUser.isAdmin || channel?.owner == loginUser.uid; const canLeave = !channel?.is_public; @@ -53,18 +53,18 @@ export default function ChannelSetting() { dangers={[ canLeave && { title: "Leave Channel", - handler: toggleLeaveConfrim + handler: toggleLeaveConfirm }, canDelete && { title: "Delete Channel", - handler: toggleDeleteConfrim + handler: toggleDeleteConfirm } ]} > {currNav.component} - {deleteConfirm && } - {leaveConfirm && } + {deleteConfirm && } + {leaveConfirm && } ); } diff --git a/src/routes/users/index.tsx b/src/routes/users/index.tsx index 6f2d9d0b..a1fcc75b 100644 --- a/src/routes/users/index.tsx +++ b/src/routes/users/index.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { NavLink, useParams, useLocation } from "react-router-dom"; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; import { updateRememberedNavs } from "../../app/slices/ui"; import Search from "../../common/component/Search"; import User from "../../common/component/User"; @@ -8,13 +8,14 @@ import Profile from "../../common/component/Profile"; import StyledWrapper from "./styled"; import BlankPlaceholder from "../../common/component/BlankPlaceholder"; +import { useAppSelector } from "../../app/store"; export default function UsersPage() { const dispatch = useDispatch(); const { pathname } = useLocation(); const { user_id } = useParams(); - const userIds = useSelector((store) => store.users.ids); + const userIds = useAppSelector((store) => store.users.ids); useEffect(() => { dispatch(updateRememberedNavs({ key: "user" })); return () => { @@ -30,7 +31,7 @@ export default function UsersPage() {
    {user_id ? (
    - +
    ) : (
    diff --git a/src/types/auth.ts b/src/types/auth.ts index f1975a0d..eb643858 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -6,6 +6,9 @@ export interface AuthToken { refresh_token: string; expired_in: number; } +export interface RenewTokenDTO extends Pick {} +export interface RenewTokenResponse + extends Pick {} export interface AuthData extends AuthToken { initialized?: boolean; @@ -57,3 +60,15 @@ export type LoginCredential = | GoogleCredential | ThirdPartyCredential | MagicLinkCredential; + +export type CredentialResponse = { + password: boolean; + google: string; + metamask: string; + oidc: string[]; +}; +export interface OIDCConfig { + enable: boolean; + favicon: string; + domain: string; +} diff --git a/src/types/channel.ts b/src/types/channel.ts index cf131b43..436c9401 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -34,8 +34,12 @@ export interface CreateChannelDTO { is_public: boolean; } +export interface ChannelDTO extends Pick { + id: number; +} + export interface UpdateChannelDTO { - operation: "add_member" | "remove_member"; + operation?: "add_member" | "remove_member"; members?: number[]; gid: number; // todo check name?: string; @@ -43,8 +47,6 @@ export interface UpdateChannelDTO { owner?: number; avatar_updated_at?: number; type?: string; - // type = 'user_joined_group' | 'user_leaved_group' - // gid uid?: number[]; icon?: string; } diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 0a11b24a..918a9434 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,3 +1,26 @@ +interface BeforeInstallPromptEvent extends Event { + /** + * Returns an array of DOMString items containing the platforms on which the event was dispatched. + * This is provided for user agents that want to present a choice of versions to the user such as, + * for example, "web" or "play" which would allow the user to chose between a web version or + * an Android version. + */ + readonly platforms: Array; + + /** + * Returns a Promise that resolves to a DOMString containing either "accepted" or "dismissed". + */ + readonly userChoice: Promise<{ + outcome: "accepted" | "dismissed"; + platform: string; + }>; + + /** + * Allows a developer to show the install prompt at a time of their own choosing. + * This method returns a Promise. + */ + prompt(): Promise; +} export declare global { import { PrecacheEntry } from "workbox-precaching/src/_types"; import localforage from "localforage"; @@ -7,4 +30,7 @@ export declare global { skipWaiting: () => void; CACHE: { [key: string]: typeof localforage | undefined }; } + interface WindowEventMap { + beforeinstallprompt: BeforeInstallPromptEvent; + } } diff --git a/src/types/message.ts b/src/types/message.ts index c7ec3d3f..de878c14 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -1,3 +1,5 @@ +import { ChatEvent } from "./sse"; + export type ContentType = "text/plain" | "text/markdown" | "vocechat/file" | "vocechat/archive"; export type ContentTypeKey = "text" | "markdown" | "file" | "archive"; @@ -13,3 +15,13 @@ export interface MuteDTO { remove_users?: number[]; remove_groups?: number[]; } +export interface UploadFileResponse { + path: string; + size: number; + hash: string; + image_properties: { + width: number; + height: number; + }; +} +export interface ChatMessage extends Omit {} diff --git a/src/types/resource.ts b/src/types/resource.ts index c41a3713..63d54e5d 100644 --- a/src/types/resource.ts +++ b/src/types/resource.ts @@ -1,3 +1,8 @@ +export interface Archive { + users: ArchiveUser[]; + messages: ArchiveMessage[]; + num_attachments: number; +} export interface ArchiveUser { name: string; avatar?: number; @@ -13,10 +18,9 @@ export interface ArchiveMessage { file_id?: number; thumbnail_id?: number; } -export interface Archive { - users: ArchiveUser[]; - messages: ArchiveMessage[]; - num_attachments: number; +export interface FavoriteArchive { + id: string; + created_at: number; } // 上传文件API响应 export interface UploadResponse { diff --git a/src/types/sse.ts b/src/types/sse.ts index 3e980650..68e4c76b 100644 --- a/src/types/sse.ts +++ b/src/types/sse.ts @@ -1,4 +1,4 @@ -import { User } from "./auth"; +import { User } from "./user"; import { Channel } from "./channel"; import { ContentType } from "./message"; @@ -110,13 +110,18 @@ export interface ReplyMessage { content_type: ContentType; content: string; } - +type MessageTargetUser = { + uid: number; +}; +type MessageTargetChannel = { + gid: number; +}; export interface ChatEvent { type: "chat"; mid: number; from_uid: number; created_at: number; - target: { uid: number }; + target: MessageTargetChannel | MessageTargetUser; detail: NormalMessage | ReactionMessage | ReplyMessage; } diff --git a/src/types/user.ts b/src/types/user.ts index a694da0f..c7ab43d0 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -2,7 +2,7 @@ export type Gender = 0 | 1; export interface User { uid: number; - email: string; + email?: string; name: string; gender: Gender; language: string; @@ -25,9 +25,7 @@ export interface UserForAdmin extends User { status: UserStatus; online_devices: UserDevice[]; } -export interface UserForAdminDTO - extends Pick< - UserForAdmin, - "email" | "password" | "name" | "gender" | "is_admin" | "language" | "status" - > {} +export interface UserForAdminDTO extends Partial { + id?: number; +} export interface UserDTO extends Pick {}