refactor: add typescript support to project
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Suspense, useEffect, lazy } from "react";
|
||||
import { Route, Routes, HashRouter } from "react-router-dom";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { Provider } from "react-redux";
|
||||
import toast from "react-hot-toast";
|
||||
import NotFoundPage from "./404";
|
||||
// import Welcome from './Welcome'
|
||||
@@ -25,16 +25,19 @@ 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";
|
||||
import store, { useAppSelector } from "../app/store";
|
||||
|
||||
const PageRoutes = () => {
|
||||
const { ui: { online }, fileMessages } = useSelector((store) => {
|
||||
const {
|
||||
ui: { online },
|
||||
fileMessages
|
||||
} = useAppSelector((store) => {
|
||||
return { ui: store.ui, fileMessages: store.fileMessage };
|
||||
});
|
||||
|
||||
// 掉线检测
|
||||
useEffect(() => {
|
||||
let toastId = '0';
|
||||
let toastId = "0";
|
||||
if (!online) {
|
||||
toast.error("Network Offline!", { duration: Infinity });
|
||||
} else {
|
||||
|
||||
+33
-24
@@ -1,79 +1,84 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, FormEvent, ChangeEvent, FC } 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/auth";
|
||||
import { useCheckMagicTokenValidMutation } from "../../app/services/auth";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
|
||||
export default function InvitePage() {
|
||||
const { token: loginToken } = useSelector((store) => store.authData);
|
||||
interface AuthForm {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const InvitePage: FC = () => {
|
||||
const { token: loginToken } = useAppSelector((store) => store.authData);
|
||||
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 }] =
|
||||
useCheckMagicTokenValidMutation();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(search);
|
||||
const query = new URLSearchParams(location.search);
|
||||
setToken(query.get("token"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
checkToken(token);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkSuccess) {
|
||||
console.log({ isValid });
|
||||
setValid(isValid);
|
||||
} else {
|
||||
setValid(false);
|
||||
}
|
||||
}, [checkSuccess, isValid]);
|
||||
|
||||
const [input, setInput] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
password: ""
|
||||
});
|
||||
const [input, setInput] = useState<AuthForm>({ name: "", email: "", password: "" });
|
||||
|
||||
const handleReg = (evt) => {
|
||||
const handleReg = (evt: FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
if (!samePwd) {
|
||||
toast.error("two passwords not same");
|
||||
return;
|
||||
}
|
||||
console.log("wtf", input);
|
||||
register({
|
||||
...input,
|
||||
magic_token: token,
|
||||
gender: 1
|
||||
});
|
||||
};
|
||||
const handleInput = (evt) => {
|
||||
const { type } = evt.target.dataset;
|
||||
|
||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { type } = evt.target.dataset as { type: keyof AuthForm };
|
||||
const { value } = evt.target;
|
||||
console.log(type, value);
|
||||
setInput((prev) => {
|
||||
prev[type] = value;
|
||||
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");
|
||||
@@ -88,8 +93,7 @@ export default function InvitePage() {
|
||||
location.href = `/#/login`;
|
||||
// navigateTo("/login",);
|
||||
}, 500);
|
||||
} else if (isError) {
|
||||
console.log("register failed", error);
|
||||
} else if (isError && error && "data" in error) {
|
||||
switch (error.status) {
|
||||
case 400:
|
||||
toast.error("Register Failed: please check inputs");
|
||||
@@ -102,6 +106,8 @@ export default function InvitePage() {
|
||||
email_conflict: "email conflict",
|
||||
name_conflict: "name conflict"
|
||||
};
|
||||
// todo
|
||||
// @ts-ignore
|
||||
toast.error(`Register Failed: ${tips[error.data?.reason]}`);
|
||||
break;
|
||||
}
|
||||
@@ -114,9 +120,10 @@ export default function InvitePage() {
|
||||
|
||||
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 (!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">
|
||||
@@ -168,4 +175,6 @@ export default function InvitePage() {
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default InvitePage;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
@@ -11,9 +11,14 @@ import DonePage from "./steps/donePage";
|
||||
import useServerSetup, { steps } from "./useServerSetup";
|
||||
import StyledOnboardingPage from "./styled";
|
||||
|
||||
function Navigator({ step, setStep }) {
|
||||
interface Props {
|
||||
step: string;
|
||||
setStep: (step: string) => void;
|
||||
}
|
||||
|
||||
const Navigator: FC<Props> = ({ step, setStep }) => {
|
||||
const index = steps.map((value) => value.name).indexOf(step);
|
||||
const canJumpTo = steps.find((value) => value.name === step).canJumpTo || [];
|
||||
const canJumpTo = steps.find((value) => value.name === step)?.canJumpTo || [];
|
||||
|
||||
return (
|
||||
<div className="navigator">
|
||||
@@ -41,7 +46,7 @@ function Navigator({ step, setStep }) {
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default function OnboardingPage() {
|
||||
const serverSetup = useServerSetup();
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { Swiper } from "swiper/types";
|
||||
|
||||
// `name` for in-code usage, `label` for display
|
||||
const steps = [
|
||||
export interface Step {
|
||||
name: string;
|
||||
label: string;
|
||||
canJumpTo?: string[];
|
||||
}
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
name: "welcomePage",
|
||||
label: "Welcome Page"
|
||||
@@ -31,12 +38,12 @@ const steps = [
|
||||
];
|
||||
|
||||
export default function useServerSetup() {
|
||||
const [swiper, setSwiper] = useState(null);
|
||||
const [swiper, setSwiper] = useState<Swiper | null>(null);
|
||||
|
||||
const [index, setIndex] = useState(0);
|
||||
const [index, setIndex] = useState<number>(0);
|
||||
const step = steps[index].name;
|
||||
const setStep = useCallback(
|
||||
(name) => {
|
||||
(name: string) => {
|
||||
const newIndex = steps.map((step) => step.name).indexOf(name);
|
||||
setIndex(newIndex);
|
||||
if (swiper !== null) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, FC, FormEvent, ChangeEvent } 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, useCheckMagicTokenValidMutation } from "../../app/services/auth";
|
||||
import toast from "react-hot-toast";
|
||||
import ExpiredTip from "./ExpiredTip";
|
||||
import { useRegisterMutation } from "../../app/services/auth";
|
||||
|
||||
export default function RegWithUsername() {
|
||||
const RegWithUsername: FC = () => {
|
||||
const [checkTokenInvalid, { data: isTokenValid, isLoading: checkingToken }] =
|
||||
useCheckMagicTokenValidMutation();
|
||||
const [
|
||||
@@ -28,7 +26,8 @@ export default function RegWithUsername() {
|
||||
const [username, setUsername] = useState("");
|
||||
const query = new URLSearchParams(location.search);
|
||||
// const githubCode = query.get("gcode");
|
||||
const token = query.get("magic_token");
|
||||
// todo: check if query param exists
|
||||
const token = query.get("magic_token") as string;
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
checkTokenInvalid(token);
|
||||
@@ -36,25 +35,29 @@ export default function RegWithUsername() {
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
switch (loginError?.status) {
|
||||
case 401:
|
||||
toast.error("Invalided Token");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (loginError && "status" in loginError) {
|
||||
switch (loginError.status) {
|
||||
case 401:
|
||||
toast.error("Invalided Token");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [loginError]);
|
||||
useEffect(() => {
|
||||
switch (regError?.status) {
|
||||
case 409:
|
||||
toast.error("Something Conflicted!");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
useEffect(() => {
|
||||
if (regError && "status" in regError) {
|
||||
switch (regError.status) {
|
||||
case 409:
|
||||
toast.error("Something Conflicted!");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [regError]);
|
||||
|
||||
useEffect(() => {
|
||||
const isSuccess = loginSuccess || regSuccess;
|
||||
const data = loginData || regData;
|
||||
@@ -67,7 +70,7 @@ export default function RegWithUsername() {
|
||||
}
|
||||
}, [loginSuccess, regSuccess, loginData, regData]);
|
||||
|
||||
const handleAuth = (evt) => {
|
||||
const handleAuth = (evt: FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
if (from == "reg") {
|
||||
register({
|
||||
@@ -83,13 +86,12 @@ export default function RegWithUsername() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleInput = (evt) => {
|
||||
const { value } = evt.target;
|
||||
setUsername(value);
|
||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
setUsername(evt.target.value);
|
||||
};
|
||||
console.log("ffff", from);
|
||||
if (!token) return "No Token";
|
||||
if (checkingToken) return "Checking Magic Link...";
|
||||
|
||||
if (!token) return <>"No Token"</>;
|
||||
if (checkingToken) return <>"Checking Magic Link..."</>;
|
||||
if (!isTokenValid) return <ExpiredTip />;
|
||||
const isLoading = loginLoading || regLoading;
|
||||
const isSuccess = loginSuccess || regSuccess;
|
||||
@@ -112,9 +114,12 @@ export default function RegWithUsername() {
|
||||
onChange={handleInput}
|
||||
/>
|
||||
<Button type="submit" disabled={isLoading || !username || isSuccess}>
|
||||
{/* todo typo */}
|
||||
{isLoading ? "Logining" : `Continue`}
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default RegWithUsername;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, ChangeEvent, FormEvent } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
// import { useNavigate } from "react-router-dom";
|
||||
import BASE_URL, { KEY_LOCAL_MAGIC_TOKEN } from "../../app/config";
|
||||
import Input from "../../common/component/styled/Input";
|
||||
import Button from "../../common/component/styled/Button";
|
||||
@@ -13,13 +12,19 @@ import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig";
|
||||
import GoogleLoginButton from "../../common/component/GoogleLoginButton";
|
||||
import GithubLoginButton from "../../common/component/GithubLoginButton";
|
||||
|
||||
interface AuthForm {
|
||||
email: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export default function Reg() {
|
||||
const [sendRegMagicLink, { isLoading: signingUp, data, isSuccess }] =
|
||||
useSendRegMagicLinkMutation();
|
||||
const [checkEmail, { isLoading: checkingEmail }] = useLazyCheckEmailQuery();
|
||||
// const navigateTo = useNavigate();
|
||||
const [magicToken, setMagicToken] = useState("");
|
||||
const [input, setInput] = useState({
|
||||
const [input, setInput] = useState<AuthForm>({
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: ""
|
||||
@@ -47,7 +52,7 @@ export default function Reg() {
|
||||
}
|
||||
}, [isSuccess, data]);
|
||||
|
||||
const handleReg = async (evt) => {
|
||||
const handleReg = async (evt: FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
const { email, password, confirmPassword } = input;
|
||||
if (password !== confirmPassword) {
|
||||
@@ -55,7 +60,6 @@ export default function Reg() {
|
||||
return;
|
||||
}
|
||||
const { data: canReg } = await checkEmail(email);
|
||||
console.log("can reg", canReg);
|
||||
if (canReg) {
|
||||
sendRegMagicLink({
|
||||
magic_token: magicToken,
|
||||
@@ -67,21 +71,23 @@ export default function Reg() {
|
||||
}
|
||||
// sendMagicLink(email);
|
||||
};
|
||||
|
||||
const handleCompare = () => {
|
||||
const { password, confirmPassword } = input;
|
||||
if (password !== confirmPassword) {
|
||||
toast.error("Not Same Password!");
|
||||
}
|
||||
};
|
||||
const handleInput = (evt) => {
|
||||
const { type } = evt.target.dataset;
|
||||
|
||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { type } = evt.target.dataset as { type: keyof AuthForm };
|
||||
const { value } = evt.target;
|
||||
// console.log(type, value);
|
||||
setInput((prev) => {
|
||||
prev[type] = value;
|
||||
return { ...prev };
|
||||
});
|
||||
};
|
||||
|
||||
const { clientId } = useGoogleAuthConfig();
|
||||
const { config: githubAuthConfig } = useGithubAuthConfig();
|
||||
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
|
||||
@@ -94,10 +100,11 @@ export default function Reg() {
|
||||
const googleLogin = enableGoogleLogin && clientId;
|
||||
// magic token 没有并且没有开放注册
|
||||
if (whoCanSignUp !== "EveryOne" && !magicToken)
|
||||
return `Sign up method is updated to Invitation Link Only`;
|
||||
return <>Sign up method is updated to Invitation Link Only</>;
|
||||
const { email, password, confirmPassword } = input;
|
||||
if (data?.mail_is_sent) return <EmailNextTip />;
|
||||
const isLoading = signingUp || checkingEmail;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tips">
|
||||
|
||||
@@ -2,8 +2,6 @@ import { Outlet } from "react-router-dom";
|
||||
import StyledWrapper from "./styled";
|
||||
|
||||
export default function RegContainer() {
|
||||
// const isRegHome = useMatch(`/register`);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="form">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChangeEvent, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import iconSearch from "../../assets/icons/search.svg?url";
|
||||
|
||||
@@ -24,8 +25,14 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Search({ value = "", updateSearchValue = null, embed = false }) {
|
||||
const handleChange = (evt) => {
|
||||
interface Props {
|
||||
value?: string;
|
||||
updateSearchValue?: (value: string) => void;
|
||||
embed?: boolean;
|
||||
}
|
||||
|
||||
const Search: FC<Props> = ({ value = "", updateSearchValue = null, embed = false }) => {
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
if (updateSearchValue) {
|
||||
updateSearchValue(evt.target.value);
|
||||
}
|
||||
@@ -36,4 +43,6 @@ export default function Search({ value = "", updateSearchValue = null, embed = f
|
||||
<input value={value} onChange={handleChange} className="search" placeholder="Search..." />
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Search;
|
||||
|
||||
@@ -22,8 +22,7 @@ export default function SendMagicLinkPage() {
|
||||
}, [isSuccess]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
if (error && "status" in error) {
|
||||
switch (error.status) {
|
||||
case "PARSING_ERROR":
|
||||
toast.error(error.data);
|
||||
@@ -32,7 +31,7 @@ export default function SendMagicLinkPage() {
|
||||
toast.error("Username or Password Incorrect");
|
||||
break;
|
||||
case 404:
|
||||
toast.error("Account not exsit");
|
||||
toast.error("Account not exist");
|
||||
break;
|
||||
default:
|
||||
toast.error("Something Error");
|
||||
@@ -53,7 +52,6 @@ export default function SendMagicLinkPage() {
|
||||
|
||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = evt.target;
|
||||
console.log(value);
|
||||
setEmail(value);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -8,7 +9,7 @@ const StyledWrapper = styled.div`
|
||||
max-width: 440px;
|
||||
padding: 32px 40px 32px 40px;
|
||||
/* border: 1px solid #eee; */
|
||||
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
box-shadow: 0 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
border-radius: 12px;
|
||||
.tips {
|
||||
display: flex;
|
||||
|
||||
@@ -86,10 +86,10 @@ export default function APIConfig() {
|
||||
Are you sure to update API secret? Previous secret will be invalided
|
||||
</div>
|
||||
<div className="btns">
|
||||
<Button onClick={hideAll} className="cancel small">
|
||||
<Button onClick={() => hideAll()} className="cancel small">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={isLoading} className="small danger" onClick={updateSecret}>
|
||||
<Button disabled={isLoading} className="small danger" onClick={() => updateSecret()}>
|
||||
Yes
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChangeEvent, FC, useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
// import { useNavigate } from "react-router-dom";
|
||||
|
||||
import toast from "react-hot-toast";
|
||||
import Modal from "../../common/component/Modal";
|
||||
import StyledModal from "../../common/component/styled/Modal";
|
||||
import Button from "../../common/component/styled/Button";
|
||||
import Checkbox from "../../common/component/styled/Checkbox";
|
||||
import useLogout from "../../common/hook/useLogout";
|
||||
|
||||
const StyledConfirm = styled(StyledModal)`
|
||||
.clear {
|
||||
font-weight: normal;
|
||||
@@ -26,21 +26,25 @@ const StyledConfirm = styled(StyledModal)`
|
||||
}
|
||||
}
|
||||
`;
|
||||
import Modal from "../../common/component/Modal";
|
||||
import toast from "react-hot-toast";
|
||||
export default function LogoutConfirmModal({ closeModal }) {
|
||||
|
||||
interface Props {
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const LogoutConfirmModal: FC<Props> = ({ closeModal }) => {
|
||||
const [clearLocal, setClearLocal] = useState(false);
|
||||
const { logout, exited, exiting, clearLocalData } = useLogout();
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
};
|
||||
const handleCheck = (evt) => {
|
||||
|
||||
const handleCheck = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
setClearLocal(evt.target.checked);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (exited) {
|
||||
if (clearLocal) {
|
||||
console.log("clear all store");
|
||||
clearLocalData();
|
||||
}
|
||||
toast.success("Logout Successfully");
|
||||
@@ -50,6 +54,7 @@ export default function LogoutConfirmModal({ closeModal }) {
|
||||
// location.reload();
|
||||
}
|
||||
}, [exited, clearLocal]);
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledConfirm
|
||||
@@ -59,7 +64,7 @@ export default function LogoutConfirmModal({ closeModal }) {
|
||||
<>
|
||||
<Button onClick={closeModal}>Cancel</Button>
|
||||
<Button onClick={handleLogout} className="danger">
|
||||
{exiting ? "Logging out" : `Log Out`}
|
||||
{exiting ? "Logging out" : "Log Out"}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
@@ -74,4 +79,6 @@ export default function LogoutConfirmModal({ closeModal }) {
|
||||
</StyledConfirm>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default LogoutConfirmModal;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChangeEvent, FC, useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import Input from "../../common/component/styled/Input";
|
||||
@@ -24,18 +23,27 @@ const StyledEdit = styled(StyledModal)`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function ProfileBasicEditModal({
|
||||
interface Props {
|
||||
label?: string;
|
||||
valueKey?: string;
|
||||
value?: string;
|
||||
title?: string;
|
||||
intro?: string;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const ProfileBasicEditModal: FC<Props> = ({
|
||||
label = "Username",
|
||||
valueKey = "name",
|
||||
value = "",
|
||||
title = "Change your username",
|
||||
intro = "Enter a new username and your existing password.",
|
||||
closeModal
|
||||
}) {
|
||||
}) => {
|
||||
const [input, setInput] = useState(value);
|
||||
// const dispatch = useDispatch();
|
||||
const [update, { isLoading, isSuccess }] = useUpdateInfoMutation();
|
||||
const handleChange = (evt) => {
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
setInput(evt.target.value);
|
||||
};
|
||||
const handleUpdate = () => {
|
||||
@@ -69,4 +77,6 @@ export default function ProfileBasicEditModal({
|
||||
</StyledEdit>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ProfileBasicEditModal;
|
||||
|
||||
@@ -27,9 +27,15 @@ interface Props {
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
interface BaseForm {
|
||||
current: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
const { data } = useGetCredentialsQuery();
|
||||
const [input, setInput] = useState({
|
||||
const [input, setInput] = useState<BaseForm>({
|
||||
current: "",
|
||||
newPassword: "",
|
||||
confirmPassword: ""
|
||||
@@ -37,7 +43,7 @@ const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
// const dispatch = useDispatch();
|
||||
const [updatePassword, { isLoading, isSuccess }] = useUpdatePasswordMutation();
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { type } = evt.target.dataset;
|
||||
const { type } = evt.target.dataset as { type: keyof BaseForm };
|
||||
setInput((prev) => {
|
||||
return { ...prev, [type]: evt.target.value };
|
||||
});
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import Select from "../../../../common/component/styled/Select";
|
||||
import { ChangeEvent, FC, useState } from "react";
|
||||
import Select, { Option } from "../../../../common/component/styled/Select";
|
||||
import Button from "../../../../common/component/styled/Button";
|
||||
import Input from "../../../../common/component/styled/Input";
|
||||
import Toggle from "../../../../common/component/styled/Toggle";
|
||||
import options from "./items.json";
|
||||
import Styled from "./styled";
|
||||
// import IconPlus from "../../../../assets/icons/plus.circle.svg";
|
||||
import IconMinus from "../../../../assets/icons/minus.circle.svg";
|
||||
|
||||
export default function IssuerList({ issuers = [], onChange }) {
|
||||
const [select, setSelect] = useState({});
|
||||
interface Issuer {
|
||||
domain: string;
|
||||
enable: boolean;
|
||||
favicon: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
issuers: Issuer[];
|
||||
onChange: (issuers: Issuer[]) => void;
|
||||
}
|
||||
|
||||
const IssuerList: FC<Props> = ({ issuers = [], onChange }) => {
|
||||
const [select, setSelect] = useState<Partial<Option> | null>(null);
|
||||
const [newDomain, setNewDomain] = useState("");
|
||||
|
||||
const handleNewDomain = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -87,7 +97,9 @@ export default function IssuerList({ issuers = [], onChange }) {
|
||||
<Button
|
||||
disabled={disableBtn}
|
||||
onClick={() => {
|
||||
const { icon, value } = options.find((option) => option.value === select.value);
|
||||
const found = options.find((option) => option.value === select?.value);
|
||||
if (!found) return;
|
||||
const { icon, value } = found;
|
||||
onChange(
|
||||
issuers.concat({
|
||||
enable: true,
|
||||
@@ -95,7 +107,7 @@ export default function IssuerList({ issuers = [], onChange }) {
|
||||
domain: value || newDomain
|
||||
})
|
||||
);
|
||||
setSelect({});
|
||||
setSelect(null);
|
||||
setNewDomain("");
|
||||
}}
|
||||
>
|
||||
@@ -107,4 +119,6 @@ export default function IssuerList({ issuers = [], onChange }) {
|
||||
{/* <IconPlus className="add" /> */}
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default IssuerList;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { roundArrow } from "tippy.js";
|
||||
import "tippy.js/dist/svg-arrow.css";
|
||||
// import React from 'react'
|
||||
import styled from "styled-components";
|
||||
import IconQuestion from "../../../assets/icons/question.svg";
|
||||
|
||||
const StyledContent = styled.div`
|
||||
padding: 8px 12px;
|
||||
background: #101828;
|
||||
@@ -15,7 +16,7 @@ const StyledContent = styled.div`
|
||||
color: #55c7ec;
|
||||
}
|
||||
`;
|
||||
import IconQuestion from "../../../assets/icons/question.svg";
|
||||
|
||||
export default function Tooltip({ link = "#" }) {
|
||||
return (
|
||||
<Tippy
|
||||
|
||||
@@ -9,24 +9,23 @@ let from: string | null = null;
|
||||
export default function Setting() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navs = useNavs();
|
||||
const flatenNavs = navs
|
||||
.map(({ items }) => {
|
||||
return items;
|
||||
})
|
||||
.flat();
|
||||
const flattenNaves = navs.map(({ items }) => items).flat();
|
||||
const navKey = searchParams.get("nav");
|
||||
from = from ?? (searchParams.get("f") || "/");
|
||||
const [logoutConfirm, setLogoutConfirm] = useState(false);
|
||||
const navgateTo = useNavigate();
|
||||
const navigateTo = useNavigate();
|
||||
const close = () => {
|
||||
// dispatch(toggleSetting());
|
||||
navgateTo(from);
|
||||
// todo: check usage
|
||||
navigateTo(from!);
|
||||
from = null;
|
||||
};
|
||||
const toggleLogoutConfrim = () => {
|
||||
|
||||
const toggleLogoutConfirm = () => {
|
||||
setLogoutConfirm((prev) => !prev);
|
||||
};
|
||||
const currNav = flatenNavs.find((n) => n.name == navKey) || flatenNavs[0];
|
||||
|
||||
const currNav = flattenNaves.find((n) => n.name == navKey) || flattenNaves[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -35,11 +34,11 @@ export default function Setting() {
|
||||
closeModal={close}
|
||||
title="Settings"
|
||||
navs={navs}
|
||||
dangers={[{ title: "Log Out", handler: toggleLogoutConfrim }]}
|
||||
dangers={[{ title: "Log Out", handler: toggleLogoutConfirm }]}
|
||||
>
|
||||
{currNav.component}
|
||||
</StyledSettingContainer>
|
||||
{logoutConfirm && <LogoutConfirmModal closeModal={toggleLogoutConfrim} />}
|
||||
{logoutConfirm && <LogoutConfirmModal closeModal={toggleLogoutConfirm} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, ChangeEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
@@ -43,37 +43,44 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ChannelFormData {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function Overview({ id = 0 }) {
|
||||
const { loginUser, channel } = useAppSelector((store) => {
|
||||
const { uid } = store.authData;
|
||||
return {
|
||||
loginUser: store.contacts.byId[store.authData.uid],
|
||||
loginUser: uid ? store.contacts.byId[Number(uid)] : undefined,
|
||||
channel: store.channels.byId[id]
|
||||
};
|
||||
});
|
||||
const { data, refetch } = useGetChannelQuery(id);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [values, setValues] = useState(null);
|
||||
const [values, setValues] = useState<ChannelFormData>({ name: "", description: "" });
|
||||
const [updateChannelIcon] = useUpdateIconMutation();
|
||||
const [updateChannel, { isSuccess: updated }] = useUpdateChannelMutation();
|
||||
|
||||
const handleUpdate = () => {
|
||||
const { name, description } = values;
|
||||
updateChannel({ id, name, description });
|
||||
};
|
||||
|
||||
const handleChange = (evt) => {
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const newValue = evt.target.value;
|
||||
const { type } = evt.target.dataset;
|
||||
const { type } = evt.target.dataset as { type: keyof ChannelFormData };
|
||||
setValues((prev) => {
|
||||
return { ...prev, [type]: newValue };
|
||||
});
|
||||
};
|
||||
|
||||
const updateIcon = (image) => {
|
||||
const updateIcon = (image: File) => {
|
||||
updateChannelIcon({ gid: id, image });
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
console.log("reset", data);
|
||||
setValues(data);
|
||||
};
|
||||
|
||||
@@ -100,6 +107,7 @@ export default function Overview({ id = 0 }) {
|
||||
toast.success("Channel updated!");
|
||||
refetch();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [updated]);
|
||||
|
||||
if (!values || !id) return null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useParams, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import LeaveChannel from "../../common/component/LeaveChannel";
|
||||
import StyledSettingContainer from "../../common/component/StyledSettingContainer";
|
||||
import StyledSettingContainer, { Danger } from "../../common/component/StyledSettingContainer";
|
||||
import DeleteConfirmModal from "./DeleteConfirmModal";
|
||||
import useNavs from "./navs";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
@@ -9,40 +9,54 @@ import { useAppSelector } from "../../app/store";
|
||||
let from: string | null = null;
|
||||
|
||||
export default function ChannelSetting() {
|
||||
const { cid } = useParams();
|
||||
const { cid } = useParams<{ cid: string }>();
|
||||
const cidNum = Number(cid);
|
||||
const { isAdmin, loginUid, channel } = useAppSelector((store) => {
|
||||
return {
|
||||
loginUid: store.authData.uid,
|
||||
isAdmin: store.contacts.byId[store.authData.uid]?.is_admin,
|
||||
channel: store.channels.byId[cid]
|
||||
isAdmin: store.authData.uid
|
||||
? store.contacts.byId[Number(store.authData.uid)]?.is_admin
|
||||
: false,
|
||||
channel: store.channels.byId[cidNum]
|
||||
};
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navs = useNavs(cid);
|
||||
const flatenNavs = navs
|
||||
.map(({ items }) => {
|
||||
return items;
|
||||
})
|
||||
.flat();
|
||||
const navs = useNavs(cidNum);
|
||||
const flattenNaves = navs.map(({ items }) => items).flat();
|
||||
const navKey = searchParams.get("nav");
|
||||
from = from ?? (searchParams.get("f") || "/");
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
||||
const [leaveConfirm, setLeaveConfirm] = useState(false);
|
||||
const close = () => {
|
||||
navigate(from);
|
||||
// todo: check usage
|
||||
navigate(from!);
|
||||
from = null;
|
||||
};
|
||||
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 canDelete = isAdmin || channel?.owner == loginUid;
|
||||
const currNav = flattenNaves.find((n) => n.name == navKey) || flattenNaves[0];
|
||||
const canDelete = isAdmin || channel?.owner === Number(loginUid);
|
||||
const canLeave = !channel?.is_public;
|
||||
const dangers: Danger[] = [];
|
||||
|
||||
if (canLeave) {
|
||||
dangers.push({
|
||||
title: "Leave Channel",
|
||||
handler: toggleLeaveConfirm
|
||||
});
|
||||
}
|
||||
if (canDelete) {
|
||||
dangers.push({
|
||||
title: "Delete Channel",
|
||||
handler: toggleDeleteConfirm
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -51,21 +65,12 @@ export default function ChannelSetting() {
|
||||
closeModal={close}
|
||||
title="Channel Setting"
|
||||
navs={navs}
|
||||
dangers={[
|
||||
canLeave && {
|
||||
title: "Leave Channel",
|
||||
handler: toggleLeaveConfrim
|
||||
},
|
||||
canDelete && {
|
||||
title: "Delete Channel",
|
||||
handler: toggleDeleteConfrim
|
||||
}
|
||||
]}
|
||||
dangers={dangers}
|
||||
>
|
||||
{currNav.component}
|
||||
</StyledSettingContainer>
|
||||
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfrim} id={cid} />}
|
||||
{leaveConfirm && <LeaveChannel closeModal={toggleLeaveConfrim} id={cid} />}
|
||||
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfirm} id={Number(cid)} />}
|
||||
{leaveConfirm && <LeaveChannel closeModal={toggleLeaveConfirm} id={cidNum} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import Overview from "./Overview";
|
||||
import ManageMembers from "../../common/component/ManageMembers";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const useNavs = (channelId: number) => {
|
||||
export interface NavItem {
|
||||
name: string;
|
||||
title: string;
|
||||
component: ReactNode;
|
||||
}
|
||||
|
||||
export interface Nav {
|
||||
name?: string;
|
||||
title: string;
|
||||
items: NavItem[];
|
||||
}
|
||||
|
||||
const useNavs = (cid: number): Nav[] => {
|
||||
return [
|
||||
{
|
||||
title: "General",
|
||||
@@ -9,12 +22,12 @@ const useNavs = (channelId: number) => {
|
||||
{
|
||||
name: "overview",
|
||||
title: "Overview",
|
||||
component: <Overview id={channelId} />
|
||||
component: <Overview id={cid} />
|
||||
},
|
||||
{
|
||||
name: "members",
|
||||
title: "Members",
|
||||
component: <ManageMembers cid={channelId} />
|
||||
component: <ManageMembers cid={cid} />
|
||||
}
|
||||
// {
|
||||
// name: "permissions",
|
||||
|
||||
Reference in New Issue
Block a user