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";
|
||||
|
||||
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,
|
||||
@@ -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<AuthData>) {
|
||||
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<string>) {
|
||||
state.uid = action.payload;
|
||||
console.log('set uid original');
|
||||
},
|
||||
updateInitialized(state, action) {
|
||||
const isInitialized = action.payload;
|
||||
state.initialized = isInitialized;
|
||||
updateInitialized(state, action: PayloadAction<boolean>) {
|
||||
state.initialized = action.payload;
|
||||
},
|
||||
updateToken(state, action) {
|
||||
updateToken(state, action: PayloadAction<AuthToken>) {
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -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<Channel[]>) {
|
||||
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<Channel>) {
|
||||
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<UpdateChannelDTO>) {
|
||||
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<UpdatePinnedMessageDTO>) {
|
||||
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<number>) {
|
||||
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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
const Input: FC<Props> = ({ 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' ? (
|
||||
<StyledWrapper className={className}>
|
||||
<StyledInput type={inputType} className={`inner ${className}`} {...rest} />
|
||||
<div className="view" onClick={togglePasswordVisible}>
|
||||
{inputType == "password" ? <HiEyeOff color="#78787c" /> : <HiEye color="#78787c" />}
|
||||
{inputType == 'password' ? <HiEyeOff color="#78787c"/> : <HiEye color="#78787c"/>}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
) : prefix ? (
|
||||
@@ -85,6 +105,6 @@ export default function Input({ type = "text", prefix = "", className, ...rest }
|
||||
) : (
|
||||
<StyledInput type={inputType} className={className} {...rest} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// export default StyledInput;
|
||||
export default Input;
|
||||
@@ -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 = () => {
|
||||
</HashRouter>
|
||||
);
|
||||
};
|
||||
|
||||
// const local_key = "AUTH_DATA";
|
||||
export default function ReduxRoutes() {
|
||||
// const [authData, setAuthData] = useState(
|
||||
@@ -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<string | null>('');
|
||||
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<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,
|
||||
gender: 1
|
||||
});
|
||||
};
|
||||
const handleInput = (evt) => {
|
||||
|
||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
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 <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>
|
||||
@@ -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<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
login({
|
||||
token,
|
||||
@@ -57,13 +57,15 @@ export default function RegWithUsername() {
|
||||
// sendMagicLink(email);
|
||||
};
|
||||
|
||||
const handleInput = (evt) => {
|
||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = evt.target;
|
||||
setUsername(value);
|
||||
};
|
||||
|
||||
if (!token) return "no token";
|
||||
if (checkingToken) return "checking Magic Link...";
|
||||
if (!isTokenValid) return <ExpiredTip />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tips">
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user