refactor: add typescript support to project
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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<User[]>) {
|
||||
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<number>) {
|
||||
const uid = action.payload;
|
||||
state.ids = state.ids.filter((i) => i != uid);
|
||||
delete state.byId[uid];
|
||||
},
|
||||
updateUsersByLogs(state, action: PayloadAction<UserLog[]>) {
|
||||
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<UserState[]>) {
|
||||
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;
|
||||
@@ -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<Favorite[]>) {
|
||||
return action.payload;
|
||||
},
|
||||
addFavorite(state, action) {
|
||||
addFavorite(state, action: PayloadAction<Favorite>) {
|
||||
state.push(action.payload);
|
||||
},
|
||||
deleteFavorite(state, action) {
|
||||
deleteFavorite(state, action: PayloadAction<number>) {
|
||||
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<Favorite>) {
|
||||
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;
|
||||
@@ -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<number>) {
|
||||
state.usersVersion = action.payload;
|
||||
},
|
||||
updateAfterMid(state, action) {
|
||||
const afterMid = action.payload;
|
||||
state.afterMid = afterMid;
|
||||
updateAfterMid(state, action: PayloadAction<number>) {
|
||||
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;
|
||||
@@ -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<State>) {
|
||||
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<ChannelMessage & EntityId>) {
|
||||
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<number | number[]>) {
|
||||
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;
|
||||
@@ -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<number[]>) {
|
||||
return action.payload || [];
|
||||
},
|
||||
addFileMessage(state, action) {
|
||||
addFileMessage(state, action: PayloadAction<number>) {
|
||||
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<number | number[]>) {
|
||||
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;
|
||||
@@ -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<State>) {
|
||||
return action.payload;
|
||||
},
|
||||
removeReactionMessage(state, action) {
|
||||
removeReactionMessage(state, action: PayloadAction<number | number[]>) {
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
},
|
||||
@@ -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;
|
||||
@@ -1,4 +1,3 @@
|
||||
// import React from 'react';
|
||||
import StyledWrapper from "./styled";
|
||||
|
||||
export default function NotFoundPage() {
|
||||
@@ -1,4 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
+28
-28
@@ -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<string | null>('');
|
||||
const [token, setToken] = useState<string | null>("");
|
||||
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<HTMLFormElement>) => {
|
||||
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 <Navigate replace to="/"/>;
|
||||
if (!token) return 'token not found';
|
||||
if (checkLoading) return 'checking token valid';
|
||||
if (!valid) return 'invite token expires or invalid';
|
||||
if (loginToken) return <Navigate replace to="/" />;
|
||||
if (!token) return "token not found";
|
||||
if (checkLoading) return "checking token valid";
|
||||
if (!valid) return "invite token expires or invalid";
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="form animate__animated animate__fadeInDown animate__faster">
|
||||
<div className="tips">
|
||||
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo"/>
|
||||
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
|
||||
<h2 className="title">Sign Up to Rustchat</h2>
|
||||
<span className="desc">Please enter your details.</span>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface EntityId {
|
||||
id: number;
|
||||
}
|
||||
Vendored
+3
-2
@@ -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<PrecacheEntry | string>;
|
||||
skipWaiting: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
+91
-23
@@ -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<User, 'create_by'> {
|
||||
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 |
|
||||
|
||||
Reference in New Issue
Block a user