refactor: add typescript support to project
This commit is contained in:
@@ -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> = ({
|
||||
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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> = ({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user