refactor: add typescript support to project

This commit is contained in:
HD
2022-06-29 16:54:58 +08:00
parent ccfee28e13
commit 003353e43f
33 changed files with 358 additions and 224 deletions
+2 -2
View File
@@ -64,9 +64,9 @@ const StyledWrapper = styled.div`
interface Props {
url?: string;
name?: string;
type?: "user";
type?: "user" | "channel";
disabled?: boolean;
uploadImage: (file: File) => Promise<any>;
uploadImage: (file: File) => void;
}
const AvatarUploader: FC<Props> = ({
+2 -2
View File
@@ -6,7 +6,7 @@ import LockHashIcon from "../../assets/icons/channel.private.svg";
interface Props {
personal?: boolean;
muted?: boolean;
className: string;
className?: string;
}
const Styled = styled.div`
@@ -16,7 +16,7 @@ const Styled = styled.div`
}
`;
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className }) => {
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className = "" }) => {
return (
<Styled className={`${muted ? "muted" : ""} ${className}`}>
{personal ? <LockHashIcon /> : <HashIcon />}
+9 -13
View File
@@ -10,19 +10,13 @@ import StyledCheckbox from "../styled/Checkbox";
import useFilteredUsers from "../../hook/useFilteredUsers";
import { useCreateChannelMutation } from "../../../app/services/channel";
import { useAppSelector } from "../../../app/store";
import { CreateChannelDTO } from "../../../types/channel";
interface Props {
personal?: boolean;
closeModal: () => void;
}
interface CreateChannelDTO {
name: string;
description: string;
members?: number[];
is_public: boolean;
}
const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
const { contactsData, loginUid } = useAppSelector((store) => {
return { contactsData: store.contacts.byId, loginUid: store.authData.uid };
@@ -31,7 +25,7 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
const [data, setData] = useState<CreateChannelDTO>({
name: "",
description: "",
members: [loginUid],
members: loginUid ? [Number(loginUid)] : [],
is_public: !personal
});
@@ -82,13 +76,15 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
};
const toggleCheckMember = ({ currentTarget }: MouseEvent<HTMLLIElement>) => {
const { members } = data;
const members = data.members ?? [];
const { uid } = currentTarget.dataset;
let tmp = members.includes(+uid) ? members.filter((m) => m != uid) : [...members, +uid];
const uidNum = Number(uid);
let tmp = members.includes(uidNum) ? members.filter((m) => m != uidNum) : [...members, uidNum];
setData((prev) => ({ ...prev, members: tmp }));
};
const loginUser = contactsData[loginUid];
if (!loginUid) return null;
const loginUser = contactsData[Number(loginUid)];
if (!loginUser) return null;
const { name, members, is_public } = data;
@@ -108,13 +104,13 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
<ul className="users">
{contacts.map((u) => {
const { uid } = u;
const checked = members.includes(uid);
const checked = members ? members.includes(uid) : false;
return (
<li
key={uid}
data-uid={uid}
className="user"
onClick={loginUid == uid ? null : toggleCheckMember}
onClick={loginUid == uid ? undefined : toggleCheckMember}
>
<StyledCheckbox
disabled={loginUid == uid}
+10 -11
View File
@@ -30,20 +30,19 @@ interface Props {
const GoogleLoginButton: FC<Props> = ({ type = "login", clientId }) => {
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
//拿本地存的magic token
// 拿本地存的magic token
const magic_token = localStorage.getItem(KEY_LOCAL_MAGIC_TOKEN);
const { signIn, loaded } = useGoogleLogin({
onScriptLoadFailure: (wtf) => {
console.error("google login script load failure", wtf);
},
clientId,
onSuccess: ({ tokenId, ...rest }) => {
console.info("google oauth success", tokenId, rest);
login({
magic_token,
id_token: tokenId,
type: "google"
});
onSuccess: (res) => {
if ("code" in res) {
console.error(`google login failed: ${res.code}`);
} else {
login({ magic_token, id_token: res.tokenId, type: "google" });
}
},
onFailure: (wtf) => {
console.error("google login failure", wtf);
@@ -57,7 +56,7 @@ const GoogleLoginButton: FC<Props> = ({ type = "login", clientId }) => {
}
}, [isSuccess]);
useEffect(() => {
if (error) {
if (error && "status" in error) {
switch (error.status) {
case 410:
toast.error(
@@ -71,14 +70,14 @@ const GoogleLoginButton: FC<Props> = ({ type = "login", clientId }) => {
return;
}
}, [error]);
const handleGoogleLogin = () => {
signIn();
};
// console.log("google login ", loaded);
return (
<StyledSocialButton disabled={!loaded || isLoading} onClick={handleGoogleLogin}>
<IconGoogle className="icon" alt="google icon" />
<IconGoogle className="icon" />
{loaded ? `${type === "login" ? "Sign in" : "Sign up"} with Google` : `Initializing`}
</StyledSocialButton>
);
+19 -12
View File
@@ -1,18 +1,21 @@
import { FC, ReactElement } from "react";
import { FC, ReactNode } from "react";
import styled from "styled-components";
import { useLocation, NavLink } from "react-router-dom";
import backIcon from "../../assets/icons/arrow.left.svg?url";
import { Nav } from "../../routes/settingChannel/navs";
const StyledWrapper = styled.div`
width: 100vw;
height: 100vh;
display: flex;
> .left {
max-height: 100vh;
overflow: scroll;
padding: 32px 16px;
min-width: 212px;
background-color: #f5f6f7;
> .title {
cursor: pointer;
font-weight: bold;
@@ -21,16 +24,16 @@ const StyledWrapper = styled.div`
color: #1c1c1e;
margin-bottom: 32px;
padding-left: 24px;
background: url(${backIcon});
background-size: 16px;
background-repeat: no-repeat;
background-position: left;
background: url(${backIcon}) no-repeat left;
}
> .items {
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: 36px;
&:before {
padding-left: 12px;
content: attr(data-title);
@@ -40,31 +43,37 @@ const StyledWrapper = styled.div`
color: #6b7280;
margin-bottom: 2px;
}
.item {
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #44494f;
border-radius: 4px;
&:hover,
&.curr {
background: #e7e5e4;
}
> a {
display: block;
padding: 4px 12px;
}
}
&.danger .item {
cursor: pointer;
padding: 4px 12px;
color: #ef4444;
&:hover {
background: none;
}
}
}
}
> .right {
background-color: #fff;
width: 100%;
@@ -72,6 +81,7 @@ const StyledWrapper = styled.div`
/* max-height: -webkit-fill-available; */
overflow: auto;
padding: 32px;
> .title {
font-weight: bold;
font-size: 20px;
@@ -82,21 +92,18 @@ const StyledWrapper = styled.div`
}
`;
interface Nav {
name?: string;
export interface Danger {
title: string;
// todo
items: [];
component: ReactElement;
handler: () => void;
}
interface Props {
closeModal: () => void;
title?: string;
navs: Nav[];
dangers: [];
nav: Nav;
children: ReactElement;
dangers: Danger[];
nav: { title: string; name?: string; component?: ReactNode };
children: ReactNode;
}
const StyledSettingContainer: FC<Props> = ({
+23 -10
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { FC, useState } from "react";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import IconSelect from "../../../assets/icons/check.sign.svg";
@@ -26,21 +26,34 @@ const Styled = styled.div`
}
`;
const CURRENT_NOT_SET = {};
export interface Option {
icon?: string;
title: string;
value: string;
selected: boolean;
underline?: boolean;
}
export default function Select({ options = [], updateSelect = null, current = CURRENT_NOT_SET }) {
interface Props {
options: Option[];
updateSelect: null | ((option: Partial<Option>) => void);
current: null | Partial<Option>;
}
const Select: FC<Props> = ({ options = [], updateSelect = null, current = null }) => {
const [optionsVisible, setOptionsVisible] = useState(false);
const [curr, setCurr] = useState(undefined);
const [curr, setCurr] = useState<Partial<Option> | null>(null);
const toggleVisible = () => {
setOptionsVisible((prev) => !prev);
};
const handleSelect = (data) => {
const handleSelect = (data: Partial<Option>) => {
setCurr(data);
toggleVisible();
if (updateSelect) {
updateSelect(data);
}
};
return (
<Tippy
visible={optionsVisible}
@@ -52,7 +65,7 @@ export default function Select({ options = [], updateSelect = null, current = CU
{options.map(({ title, value, selected, underline }) => {
return (
<li
onClick={selected ? null : handleSelect.bind(null, { title, value })}
onClick={selected ? undefined : handleSelect.bind(null, { title, value })}
className={`item sb ${underline ? "underline" : ""}`}
data-disabled={selected}
key={value}
@@ -66,11 +79,11 @@ export default function Select({ options = [], updateSelect = null, current = CU
}
>
<Styled onClick={toggleVisible}>
<span className="txt">
{(current !== CURRENT_NOT_SET ? current : curr)?.title || `Select`}
</span>
<span className="txt">{(current !== null ? current : curr)?.title || "Select"}</span>
<IconArrow className="icon" />
</Styled>
</Tippy>
);
}
};
export default Select;
+4
View File
@@ -11,6 +11,10 @@ import { useLazyLogoutQuery } from "../../app/services/auth";
export default function useLogout() {
const dispatch = useDispatch();
const [logout, { isLoading, isSuccess }] = useLazyLogoutQuery();
// todo: remove batch
// If you're using React 18, you do not need to use the batch API. React 18 automatically
// batches all state updates, no matter where they're queued.
// ref: https://react-redux.js.org/api/batch
const clearLocalData = () => {
batch(() => {
dispatch(resetFootprint());
+2 -2
View File
@@ -1,10 +1,10 @@
import { useState, useEffect } from "react";
import { normalizeArchiveData } from "../utils";
import { normalizeArchiveData, ArchiveMessage } from "../utils";
import { useLazyGetArchiveMessageQuery } from "../../app/services/message";
export default function useNormalizeMessage() {
const [filePath, setFilePath] = useState<string | null>(null);
const [normalizedMessages, setNormalizedMessages] = useState(null);
const [normalizedMessages, setNormalizedMessages] = useState<ArchiveMessage[] | null>(null);
const [getArchiveMessage, { data, isError, isLoading, isSuccess }] =
useLazyGetArchiveMessageQuery();
useEffect(() => {