diff --git a/src/app/services/auth.js b/src/app/services/auth.ts similarity index 99% rename from src/app/services/auth.js rename to src/app/services/auth.ts index 648c8512..2f78deda 100644 --- a/src/app/services/auth.js +++ 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"; + const getDeviceId = () => { let d = localStorage.getItem(KEY_DEVICE_KEY); if (!d) { @@ -11,6 +12,7 @@ const getDeviceId = () => { } return d; }; + export const authApi = createApi({ reducerPath: "authApi", baseQuery, diff --git a/src/app/slices/auth.data.js b/src/app/slices/auth.data.ts similarity index 54% rename from src/app/slices/auth.data.js rename to src/app/slices/auth.data.ts index f4606260..258aa630 100644 --- a/src/app/slices/auth.data.js +++ b/src/app/slices/auth.data.ts @@ -1,24 +1,59 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID, KEY_EXPIRE } from "../config"; -const initialState = { +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { KEY_EXPIRE, KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from '../config'; + +interface State { + initialized: boolean; + uid: string | null; + token: string | null; + expireTime: string | number; // todo + refreshToken: string | null; +} + +const initialState: State = { initialized: true, uid: null, token: localStorage.getItem(KEY_TOKEN), expireTime: localStorage.getItem(KEY_EXPIRE) || +new Date(), refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN) }; -const emptyState = { + +const emptyState: State = { initialized: true, uid: null, token: null, expireTime: +new Date(), 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", + name: 'authData', initialState, reducers: { - setAuthData(state, action) { + setAuthData(state, action: PayloadAction) { const { initialized = true, user: { uid }, @@ -27,21 +62,21 @@ const authDataSlice = createSlice({ expired_in = 0 } = action.payload; state.initialized = initialized; - state.uid = uid; + state.uid = `${uid}`; state.token = token; state.refreshToken = refresh_token; // 当前时间往后推expire时长 - console.log("expire", expired_in); + console.log('expire', expired_in); const expireTime = +new Date() + Number(expired_in) * 1000; state.expireTime = expireTime; // set local data - localStorage.setItem(KEY_EXPIRE, expireTime); + localStorage.setItem(KEY_EXPIRE, `${expireTime}`); localStorage.setItem(KEY_TOKEN, token); localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token); - localStorage.setItem(KEY_UID, uid); + localStorage.setItem(KEY_UID, `${uid}`); }, resetAuthData() { - console.log("clear auth data"); + console.log('clear auth data'); // remove local data localStorage.removeItem(KEY_EXPIRE); localStorage.removeItem(KEY_TOKEN); @@ -51,28 +86,27 @@ const authDataSlice = createSlice({ return emptyState; }, - setUid(state, action) { - const uid = action.payload; - state.uid = uid; - console.log("set uid orginal"); + setUid(state, action: PayloadAction) { + state.uid = action.payload; + console.log('set uid original'); }, - updateInitialized(state, action) { - const isInitialized = action.payload; - state.initialized = isInitialized; + updateInitialized(state, action: PayloadAction) { + state.initialized = action.payload; }, - updateToken(state, action) { + updateToken(state, action: PayloadAction) { const { token, refresh_token, expired_in } = action.payload; - console.log("refresh token"); + console.log('refresh token'); state.token = token; const et = +new Date() + Number(expired_in) * 1000; state.expireTime = et; state.refreshToken = refresh_token; - localStorage.setItem(KEY_EXPIRE, et); + localStorage.setItem(KEY_EXPIRE, `${et}`); localStorage.setItem(KEY_TOKEN, token); localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token); } } }); + export const { updateInitialized, setAuthData, resetAuthData, setUid, updateToken } = authDataSlice.actions; export default authDataSlice.reducer; diff --git a/src/app/slices/channels.js b/src/app/slices/channels.js deleted file mode 100644 index 41ae352b..00000000 --- a/src/app/slices/channels.js +++ /dev/null @@ -1,112 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import BASE_URL from "../config"; -import { getNonNullValues } from "../../common/utils"; -const initialState = { - ids: [], - byId: {} -}; -const channelsSlice = createSlice({ - name: `channels`, - initialState, - reducers: { - resetChannels() { - return initialState; - }, - fullfillChannels(state, action) { - console.log("set channels store", state); - const chs = action.payload || []; - state.ids = chs.map(({ gid }) => +gid); - state.byId = Object.fromEntries( - chs.map((c) => { - const { gid, avatar_updated_at } = c; - c.icon = - avatar_updated_at == 0 - ? "" - : `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`; - return [gid, c]; - }) - ); - }, - addChannel(state, action) { - // console.log("set channels store", action); - const ch = action.payload; - const { gid, avatar_updated_at } = ch; - if (!state.ids.includes(+gid)) { - state.ids.push(+gid); - } - state.byId[gid] = { - ...ch, - icon: - avatar_updated_at == 0 - ? "" - : `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}` - }; - }, - updateChannel(state, action) { - const ignoreInPublic = ["add_member", "remove_member"]; - const { id, operation, members = [], ...rest } = action.payload; - const currChannel = state.byId[id]; - if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return; - switch (operation) { - case "remove_member": - { - const filtered = state.byId[id].members.filter( - (id) => members.findIndex((uid) => uid == id) == -1 - ); - state.byId[id].members = filtered; - } - break; - case "add_member": - { - const currs = state.byId[id].members; - const _set = new Set([...currs, ...members]); - console.log("add member to channel", [..._set]); - state.byId[id].members = [..._set]; - } - break; - default: - state.byId[id] = { ...state.byId[id], ...getNonNullValues(rest) }; - break; - } - }, - updatePinMessage(state, action) { - const { gid, mid, msg } = action.payload; - let msgs = state.byId[gid]?.pinned_messages; - if (!msgs) return; - if (msg) { - if (!msgs) { - msgs = [msg]; - } else { - const idx = msgs.findIndex((msg) => msg.mid == mid); - if (idx > -1) { - msgs.splice(idx, 1); - } - msgs.push(msg); - } - } else { - // remove - const idx = msgs.findIndex((msg) => msg.mid == mid); - if (idx > -1) { - msgs.splice(idx, 1); - } - } - }, - removeChannel(state, action) { - const gid = action.payload; - const idx = state.ids.findIndex((i) => i == gid); - if (idx > -1) { - state.ids.splice(idx, 1); - delete state.byId[gid]; - } - } - } -}); -export const { - updatePinMessage, - resetChannels, - fullfillChannels, - addChannel, - updateChannel, - removeChannel -} = channelsSlice.actions; -export default channelsSlice.reducer; diff --git a/src/app/slices/channels.ts b/src/app/slices/channels.ts new file mode 100644 index 00000000..024f64d7 --- /dev/null +++ b/src/app/slices/channels.ts @@ -0,0 +1,121 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import BASE_URL from '../config'; +import { getNonNullValues } from '../../common/utils'; +import { Channel, UpdateChannelDTO, UpdatePinnedMessageDTO } from '../../types/channel'; + +interface State { + ids: number[]; + byId: { [id: number]: Channel | undefined }; +} + +const initialState: State = { + ids: [], + byId: {} +}; + +const channelsSlice = createSlice({ + name: `channels`, + initialState, + reducers: { + resetChannels() { + return initialState; + }, + fullfillChannels(state, action: PayloadAction) { + const channels = action.payload || []; + state.ids = channels.map(({ gid }) => gid); + state.byId = Object.fromEntries( + channels.map((c) => { + const { gid, avatar_updated_at } = c; + c.icon = + avatar_updated_at == 0 + ? '' + : `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`; + return [gid, c]; + }) + ); + }, + addChannel(state, action: PayloadAction) { + const ch = action.payload; + const { gid, avatar_updated_at } = ch; + if (!state.ids.includes(+gid)) { + state.ids.push(+gid); + } + state.byId[gid] = { + ...ch, + icon: + avatar_updated_at == 0 + ? '' + : `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}` + }; + }, + updateChannel(state, action: PayloadAction) { + const ignoreInPublic = ['add_member', 'remove_member']; + const { gid, operation, members = [], ...rest } = action.payload; + const currChannel = state.byId[gid]; + if ( + !currChannel || + (currChannel.is_public && ignoreInPublic.includes(operation)) + ) return; + switch (operation) { + case 'remove_member': { + state.byId[gid]!.members = state.byId[gid]!.members.filter( + (id) => members.findIndex((uid) => uid == id) == -1 + ); + break; + } + case 'add_member': { + const currs = state.byId[gid]!.members; + const _set = new Set([...currs, ...members]); + state.byId[gid]!.members = Array.from(_set); + break; + } + default: + state.byId[gid] = { ...state.byId[gid]!, ...getNonNullValues(rest) }; + break; + } + }, + updatePinMessage(state, action: PayloadAction) { + const { gid, mid, msg } = action.payload; + let pinnedMessages = state.byId[gid]?.pinned_messages; + // add + if (msg) { + if (!pinnedMessages) { + pinnedMessages = [msg]; + } else { + const idx = pinnedMessages.findIndex((msg) => msg.mid == mid); + if (idx > -1) { + pinnedMessages.splice(idx, 1); + } + pinnedMessages.push(msg); + } + } else { + // remove + if (pinnedMessages) { + const idx = pinnedMessages.findIndex((m) => m.mid == mid); + if (idx > -1) { + pinnedMessages.splice(idx, 1); + } + } + } + }, + removeChannel(state, action: PayloadAction) { + const gid = action.payload; + const idx = state.ids.findIndex((i) => i == gid); + if (idx > -1) { + state.ids.splice(idx, 1); + delete state.byId[gid]; + } + } + } +}); + +export const { + updatePinMessage, + resetChannels, + fullfillChannels, + addChannel, + updateChannel, + removeChannel +} = channelsSlice.actions; + +export default channelsSlice.reducer; diff --git a/src/app/store.js b/src/app/store.ts similarity index 100% rename from src/app/store.js rename to src/app/store.ts diff --git a/src/common/component/FileMessage/index.js b/src/common/component/FileMessage/index.js index e2a67045..79c319cd 100644 --- a/src/common/component/FileMessage/index.js +++ b/src/common/component/FileMessage/index.js @@ -11,10 +11,12 @@ import Progress from "./Progress"; import { getFileIcon, formatBytes, isImage, getImageSize } from "../../utils"; import IconDownload from "../../../assets/icons/download.svg"; import IconClose from "../../../assets/icons/close.circle.svg"; + dayjs.extend(relativeTime); const isLocalFile = (content) => { return content && typeof content == "string" && content.startsWith("blob:"); }; + export default function FileMessage({ context = "", to = null, diff --git a/src/common/component/Send/UploadFileList/index.js b/src/common/component/Send/UploadFileList/index.js index 6c93e3e5..acc46bc7 100644 --- a/src/common/component/Send/UploadFileList/index.js +++ b/src/common/component/Send/UploadFileList/index.js @@ -2,7 +2,6 @@ import { useState, useEffect } from "react"; import Styled from "./styled"; import { useMixedEditor } from "../../MixedInput"; import EditFileDetailsModal from "./EditFileDetails"; - import { getFileIcon, formatBytes } from "../../../utils"; import useUploadFile from "../../../hook/useUploadFile"; import EditIcon from "../../../../assets/icons/edit.svg"; diff --git a/src/common/component/styled/Input.js b/src/common/component/styled/Input.tsx similarity index 74% rename from src/common/component/styled/Input.js rename to src/common/component/styled/Input.tsx index 943015a2..86df587f 100644 --- a/src/common/component/styled/Input.js +++ b/src/common/component/styled/Input.tsx @@ -1,6 +1,12 @@ -import { useState } from "react"; -import { HiEye, HiEyeOff } from "react-icons/hi"; -import styled from "styled-components"; +import { + useState, + FC, + DetailedHTMLProps, + InputHTMLAttributes +} from 'react'; +import { HiEye, HiEyeOff } from 'react-icons/hi'; +import styled from 'styled-components'; + const StyledWrapper = styled.div` width: 100%; position: relative; @@ -9,6 +15,7 @@ const StyledWrapper = styled.div` border-radius: 4px; border: 1px solid #e5e7eb; box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08); + .prefix { padding: 8px 16px; font-weight: 400; @@ -18,6 +25,7 @@ const StyledWrapper = styled.div` background: #f3f4f6; border-right: 1px solid #e5e7eb; } + .view { position: absolute; top: 50%; @@ -26,6 +34,7 @@ const StyledWrapper = styled.div` cursor: pointer; } `; + const StyledInput = styled.input` width: 100%; background: #ffffff; @@ -36,45 +45,56 @@ const StyledInput = styled.input` color: #333; padding: 8px; outline: none; + &:not(.inner) { border-radius: 4px; border: 1px solid #e5e7eb; box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08); } + &.large { font-weight: 400; font-size: 14px; line-height: 20px; padding: 11px 8px; } + &.none { outline: none; border: none; background: none; box-shadow: none; } + &:disabled { color: #78787c; background-color: #f9fafb; } + &::placeholder { color: #d1d5db; } + &[type="password"] { padding-right: 30px; } `; -export default function Input({ type = "text", prefix = "", className, ...rest }) { +interface Props extends DetailedHTMLProps, HTMLInputElement> { + prefix?: string; +} + +const Input: FC = ({ type = 'text', prefix = '', className, ...rest }) => { const [inputType, setInputType] = useState(type); const togglePasswordVisible = () => { - setInputType((prev) => (prev == "password" ? "text" : "password")); + setInputType((prev) => (prev == 'password' ? 'text' : 'password')); }; - return type == "password" ? ( + + return type == 'password' ? (
- {inputType == "password" ? : } + {inputType == 'password' ? : }
) : prefix ? ( @@ -85,6 +105,6 @@ export default function Input({ type = "text", prefix = "", className, ...rest } ) : ( ); -} +}; -// export default StyledInput; +export default Input; diff --git a/src/routes/index.js b/src/routes/index.tsx similarity index 98% rename from src/routes/index.js rename to src/routes/index.tsx index 5da10911..5c8b910c 100644 --- a/src/routes/index.js +++ b/src/routes/index.tsx @@ -2,11 +2,11 @@ import { Suspense, useEffect, lazy } from "react"; import { Route, Routes, HashRouter } from "react-router-dom"; import { Provider, useSelector } from "react-redux"; import toast from "react-hot-toast"; -// import Welcome from './Welcome' import NotFoundPage from "./404"; +// import Welcome from './Welcome' // const HomePage = lazy(() => import("./home")); -const RegBasePage = lazy(() => import("./reg")); // const ChatPage = lazy(() => import("./chat")); +const RegBasePage = lazy(() => import("./reg")); const RegWithUsernamePage = lazy(() => import("./reg/RegWithUsername")); const SendMagicLinkPage = lazy(() => import("./sendMagicLink")); const RegPage = lazy(() => import("./reg/Register")); @@ -25,19 +25,16 @@ import Meta from "../common/component/Meta"; import HomePage from "./home"; import ChatPage from "./chat"; import Loading from "../common/component/Loading"; - import store from "../app/store"; const PageRoutes = () => { - const { - ui: { online }, - fileMessages - } = useSelector((store) => { + const { ui: { online }, fileMessages } = useSelector((store) => { return { ui: store.ui, fileMessages: store.fileMessage }; }); + // 掉线检测 useEffect(() => { - let toastId = 0; + let toastId = '0'; if (!online) { toast.error("Network Offline!", { duration: Infinity }); } else { @@ -179,6 +176,7 @@ const PageRoutes = () => { ); }; + // const local_key = "AUTH_DATA"; export default function ReduxRoutes() { // const [authData, setAuthData] = useState( diff --git a/src/routes/invite/index.js b/src/routes/invite/index.tsx similarity index 69% rename from src/routes/invite/index.js rename to src/routes/invite/index.tsx index 10f86ad6..3da0d8b2 100644 --- a/src/routes/invite/index.js +++ b/src/routes/invite/index.tsx @@ -1,33 +1,36 @@ -import { useState, useEffect } from "react"; -import StyledWrapper from "./styled"; -import { Navigate } from "react-router-dom"; -import toast from "react-hot-toast"; -import BASE_URL from "../../app/config"; -import { useRegisterMutation } from "../../app/services/contact"; -import { useCheckInviteTokenValidMutation } from "../../app/services/auth"; -import { useSelector } from "react-redux"; +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'; export default function InvitePage() { const { token: loginToken } = useSelector((store) => store.authData); - const [secondPwd, setSecondPwd] = useState(""); + 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(); const [register, { data, isLoading, isSuccess, isError, error }] = useRegisterMutation(); const [checkToken, { data: isValid, isLoading: checkLoading, isSuccess: checkSuccess }] = useCheckInviteTokenValidMutation(); + useEffect(() => { // console.log(search); const query = new URLSearchParams(location.search); - setToken(query.get("token")); + setToken(query.get('token')); }, []); + useEffect(() => { if (token) { checkToken(token); } }, [token]); + useEffect(() => { if (checkSuccess) { console.log({ isValid }); @@ -37,26 +40,23 @@ export default function InvitePage() { } }, [checkSuccess, isValid]); - const [input, setInput] = useState({ - name: "", - email: "", - password: "" - }); + const [input, setInput] = useState({ name: '', email: '', password: '' }); - const handleReg = (evt) => { + 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, gender: 1 }); }; - const handleInput = (evt) => { + + const handleInput = (evt: ChangeEvent) => { const { type } = evt.target.dataset; const { value } = evt.target; console.log(type, value); @@ -65,63 +65,67 @@ export default function InvitePage() { return { ...prev }; }); }; - const handleSecondPwdInput = (evt) => { + + const handleSecondPwdInput = (evt: ChangeEvent) => { const { value } = evt.target; setSecondPwd(value); }; + const handlePwdCheck = () => { if (secondPwd) { setSamePwd(secondPwd == input.password); } }; + 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/routes/invite/styled.js b/src/routes/invite/styled.tsx similarity index 100% rename from src/routes/invite/styled.js rename to src/routes/invite/styled.tsx diff --git a/src/routes/reg/RegWithUsername.js b/src/routes/reg/RegWithUsername.tsx similarity index 91% rename from src/routes/reg/RegWithUsername.js rename to src/routes/reg/RegWithUsername.tsx index 14ad4fa7..ea351f32 100644 --- a/src/routes/reg/RegWithUsername.js +++ b/src/routes/reg/RegWithUsername.tsx @@ -1,15 +1,14 @@ /* eslint-disable no-undef */ -import { useState, useEffect } from "react"; -import { useDispatch } from "react-redux"; // import { useNavigate } from "react-router-dom"; // import toast from "react-hot-toast"; +import { useState, useEffect, ChangeEvent, FormEvent } from "react"; +import { useDispatch } from "react-redux"; import { useParams } from "react-router-dom"; +import toast from "react-hot-toast"; import { setAuthData } from "../../app/slices/auth.data"; - import Input from "../../common/component/styled/Input"; import Button from "../../common/component/styled/Button"; import { useLoginMutation, useCheckInviteTokenValidMutation } from "../../app/services/auth"; -import toast from "react-hot-toast"; import ExpiredTip from "./ExpiredTip"; export default function RegWithUsername() { @@ -20,6 +19,7 @@ export default function RegWithUsername() { // const navigateTo = useNavigate(); const dispatch = useDispatch(); const [username, setUsername] = useState(""); + useEffect(() => { if (token) { checkTokenInvalid(token); @@ -27,16 +27,16 @@ export default function RegWithUsername() { }, [token]); useEffect(() => { - console.log("errr", error); + console.log("error", error); switch (error?.status) { case 401: toast.error("Invalided Token"); break; - default: break; } }, [error]); + useEffect(() => { if (isSuccess && data) { // 更新本地认证信息 @@ -47,7 +47,7 @@ export default function RegWithUsername() { } }, [isSuccess, data]); - const handleLogin = (evt) => { + const handleLogin = (evt: FormEvent) => { evt.preventDefault(); login({ token, @@ -57,13 +57,15 @@ export default function RegWithUsername() { // sendMagicLink(email); }; - const handleInput = (evt) => { + const handleInput = (evt: ChangeEvent) => { const { value } = evt.target; setUsername(value); }; + if (!token) return "no token"; if (checkingToken) return "checking Magic Link..."; if (!isTokenValid) return ; + return ( <>
diff --git a/src/types/channel.ts b/src/types/channel.ts new file mode 100644 index 00000000..c90176c3 --- /dev/null +++ b/src/types/channel.ts @@ -0,0 +1,55 @@ + +export interface ChannelMember { + +} + +export type ContentType = + 'text/plain' | + 'text/markdown' | + 'rustchat/file' | + 'rustchat/archive'; + +export interface PinnedMessage { + mid: number; + content: string; + content_type: ContentType; + created_by: number; + created_at: number; + properties: { + local_id?: string; + }; +} + +export interface Channel { + gid: number; + icon: string; // added by frontend + owner: number; + name: string; + description: string; + members: number[]; + is_public: boolean; + avatar_updated_at: number; + pinned_messages: PinnedMessage[]; +} + +export interface UpdateChannelDTO { + operation: 'add_member' | 'remove_member'; + members?: number[]; + + // type = 'group_changed' + gid: number; // todo check + name?: string; + description?: string; + owner?: number; + avatar_updated_at?: number; + type?: string; + // type = 'user_joined_group' | 'user_leaved_group' + // gid + uid?: number[]; +} + +export interface UpdatePinnedMessageDTO { + gid: number; + mid: number; + msg: PinnedMessage; +} diff --git a/src/types/sse.ts b/src/types/sse.ts new file mode 100644 index 00000000..c716f52c --- /dev/null +++ b/src/types/sse.ts @@ -0,0 +1,86 @@ +// todo +interface ReadyEvent { + type: 'ready' +} + +interface UsersSnapshotEvent { + type: 'users_snapshot' +} + +interface UsersLogEvent { + type: 'users_log' +} + +interface UsersStateEvent { + type: 'users_state' +} + +interface UsersStateChangedEvent { + type: 'users_state_changed' +} + +interface UserSettingsEvent { + type: 'user_settings' +} + +interface UserSettingsChangedEvent { + type: 'user_settings_changed' +} + +interface RelatedGroupsEvent { + type: 'related_groups' +} + +interface ChatEvent { + type: 'chat' +} + +interface KickEvent { + type: 'kick' +} + +interface UserJoinedGroupEvent { + type: 'user_joined_group' +} + +interface UserLeavedGroupEvent { + type: 'user_leaved_group' +} + +interface JoinedGroupEvent { + type: 'joined_group' +} + +interface KickFromGroupEvent { + type: 'kick_from_group' +} + +interface GroupChangedEvent { + type: 'group_changed' +} + +interface PinnedMessageUpdatedEvent { + type: 'pinned_message_updated' +} + +interface HeartbeatEvent { + type: 'heartbeat' +} + +export type ServerEvent = ReadyEvent | + UsersSnapshotEvent | + UsersLogEvent | + UsersStateEvent | + UsersStateChangedEvent | + UserSettingsEvent | + UserSettingsChangedEvent | + RelatedGroupsEvent | + ChatEvent | + KickEvent | + UserJoinedGroupEvent | + UserLeavedGroupEvent | + JoinedGroupEvent | + KickFromGroupEvent | + GroupChangedEvent | + PinnedMessageUpdatedEvent | + HeartbeatEvent;