Merge remote-tracking branch 'upstream/main' into refactor/typescript
# Conflicts: # src/app/services/server.ts # src/common/component/GoogleLoginButton.tsx # src/common/utils.tsx
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { FC } from "react";
|
||||
import { useEffect, FC } from "react";
|
||||
import IconGithub from "../../assets/icons/github.svg";
|
||||
import styled from "styled-components";
|
||||
import { KEY_LOCAL_MAGIC_TOKEN } from "../../app/config";
|
||||
|
||||
import Button from "./styled/Button";
|
||||
import { useLoginMutation } from "../../app/services/auth";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const StyledSocialButton = styled(Button)`
|
||||
width: 100%;
|
||||
@@ -20,26 +24,58 @@ const StyledSocialButton = styled(Button)`
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
config: {
|
||||
client_id: string;
|
||||
};
|
||||
}
|
||||
type Props = {
|
||||
client_id: string;
|
||||
type?: "login" | "register";
|
||||
};
|
||||
|
||||
const GithubLoginButton: FC<Props> = ({ config }) => {
|
||||
const { client_id } = config;
|
||||
const GithubLoginButton: FC<Props> = ({ type = "login", client_id }) => {
|
||||
//拿本地存的magic token
|
||||
const magic_token = localStorage.getItem(KEY_LOCAL_MAGIC_TOKEN);
|
||||
const [login, { isLoading, isSuccess, error }] = useLoginMutation();
|
||||
useEffect(() => {
|
||||
const query = new URLSearchParams(location.search);
|
||||
const isGithub = query.get("oauth") === "github";
|
||||
const code = query.get("code");
|
||||
if (isGithub && code) {
|
||||
login({
|
||||
magic_token,
|
||||
code: code,
|
||||
type: "github"
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Login Successfully");
|
||||
// navigateTo("/");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
switch (error.status) {
|
||||
case 410:
|
||||
toast.error(
|
||||
"No associated account found, please contact admin for an invitation link to join."
|
||||
);
|
||||
break;
|
||||
default:
|
||||
toast.error("Something Error");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [error]);
|
||||
const handleGithubLogin = () => {
|
||||
location.href = `https://github.com/login/oauth/authorize?client_id=${client_id}`;
|
||||
// console.log("github login");
|
||||
};
|
||||
// console.log("google login ", loaded);
|
||||
return (
|
||||
<StyledSocialButton onClick={handleGithubLogin}>
|
||||
<StyledSocialButton onClick={handleGithubLogin} disabled={isLoading}>
|
||||
<IconGithub className="icon" />
|
||||
Sign in with Github
|
||||
{/* {loaded ? `Sign in with Github` : `Initializing`} */}
|
||||
{` ${type === "login" ? "Sign in" : "Sign up"} with Github`}
|
||||
</StyledSocialButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default GithubLoginButton;
|
||||
|
||||
@@ -2,7 +2,8 @@ import { FC, useEffect } from "react";
|
||||
import { useGoogleLogin } from "react-google-login";
|
||||
import toast from "react-hot-toast";
|
||||
import styled from "styled-components";
|
||||
import googleSvg from "../../assets/icons/google.svg?url";
|
||||
import { KEY_LOCAL_MAGIC_TOKEN } from "../../app/config";
|
||||
import IconGoogle from "../../assets/icons/google.svg";
|
||||
import Button from "./styled/Button";
|
||||
import { useLoginMutation } from "../../app/services/auth";
|
||||
|
||||
@@ -24,11 +25,13 @@ const StyledSocialButton = styled(Button)`
|
||||
|
||||
interface Props {
|
||||
clientId: string;
|
||||
type?: "login" | "register";
|
||||
}
|
||||
|
||||
const GoogleLoginButton: FC<Props> = ({ clientId }) => {
|
||||
const [login, { isSuccess, isLoading }] = useLoginMutation();
|
||||
|
||||
const GoogleLoginButton: FC<Props> = ({ type = "login", clientId }) => {
|
||||
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
|
||||
//拿本地存的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);
|
||||
@@ -37,6 +40,7 @@ const GoogleLoginButton: FC<Props> = ({ clientId }) => {
|
||||
onSuccess: ({ tokenId, ...rest }) => {
|
||||
console.info("google oauth success", tokenId, rest);
|
||||
login({
|
||||
magic_token,
|
||||
id_token: tokenId,
|
||||
type: "google"
|
||||
});
|
||||
@@ -52,6 +56,21 @@ const GoogleLoginButton: FC<Props> = ({ clientId }) => {
|
||||
// navigateTo("/");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
switch (error.status) {
|
||||
case 410:
|
||||
toast.error(
|
||||
"No associated account found, please contact admin for an invitation link to join."
|
||||
);
|
||||
break;
|
||||
default:
|
||||
toast.error("Something Error");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [error]);
|
||||
const handleGoogleLogin = () => {
|
||||
signIn();
|
||||
};
|
||||
@@ -59,8 +78,8 @@ const GoogleLoginButton: FC<Props> = ({ clientId }) => {
|
||||
// console.log("google login ", loaded);
|
||||
return (
|
||||
<StyledSocialButton disabled={!loaded || isLoading} onClick={handleGoogleLogin}>
|
||||
<img className="icon" src={googleSvg} alt="google icon" />
|
||||
{loaded ? "Sign in with Google" : "Initializing"}
|
||||
<IconGoogle className="icon" alt="google icon" />
|
||||
{loaded ? `${type === "login" ? "Sign in" : "Sign up"} with Google` : `Initializing`}
|
||||
</StyledSocialButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import Prompt from "./Prompt";
|
||||
import usePrompt from "./usePrompt";
|
||||
|
||||
export default function Manifest() {
|
||||
const { setCanneled, prompted } = usePrompt();
|
||||
const { setCanceled: setCanceled, prompted } = usePrompt();
|
||||
const deferredPromptRef = useRef(null);
|
||||
const [popup, setPopup] = useState(false);
|
||||
// const { data, isSuccess } = useGetServerQuery();
|
||||
@@ -57,7 +57,7 @@ export default function Manifest() {
|
||||
deferredPromptRef.current = null;
|
||||
};
|
||||
const handleClose = async () => {
|
||||
setCanneled();
|
||||
setCanceled();
|
||||
setPopup(false);
|
||||
};
|
||||
if (!popup || prompted) return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "RustChat",
|
||||
"name": "VoceChat",
|
||||
"short_name": "Your private chat APP",
|
||||
"icons": [
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function usePrompt() {
|
||||
};
|
||||
|
||||
return {
|
||||
setCanneled: setPrompt,
|
||||
setCanceled: setPrompt,
|
||||
prompted: !!localStorage.getItem(KEY_PWA_INSTALLED),
|
||||
resetPrompt
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ import useUploadFile from "../../hook/useUploadFile";
|
||||
import Styled from "./styled";
|
||||
import { CONFIG } from "./config";
|
||||
import Contact from "../Contact";
|
||||
export const TEXT_EDITOR_PREFIX = "rustchat_text_editor";
|
||||
export const TEXT_EDITOR_PREFIX = "_text_editor";
|
||||
|
||||
let components = createPlateUI({
|
||||
// [ELEMENT_IMAGE]: ImageElement,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { isObjectEqual } from "../utils";
|
||||
import { isEqual } from "lodash";
|
||||
import {
|
||||
useUpdateLoginConfigMutation,
|
||||
useUpdateSMTPConfigMutation,
|
||||
@@ -101,7 +101,7 @@ export default function useConfig(config = "smtp") {
|
||||
useEffect(() => {
|
||||
// 空对象
|
||||
if (Object.keys(values).length == 0) return;
|
||||
if (!isObjectEqual(originalValue, values)) {
|
||||
if (!isEqual(originalValue, values)) {
|
||||
setChanged(true);
|
||||
} else {
|
||||
setChanged(false);
|
||||
|
||||
@@ -65,7 +65,7 @@ export default function useMessageFeed({ context = "channel", id = null }) {
|
||||
}, [context, id]);
|
||||
useEffect(() => {
|
||||
if (items.length) {
|
||||
containerRef.current = document.querySelector(`#RUSTCHAT_FEED_${context}_${id}`);
|
||||
containerRef.current = document.querySelector(`#VOCECHAT_FEED_${context}_${id}`);
|
||||
if (containerRef.current) {
|
||||
const newScroll = containerRef.current.scrollHeight - containerRef.current.clientHeight;
|
||||
containerRef.current.scrollTop = curScrollPos + (newScroll - oldScroll);
|
||||
|
||||
@@ -219,7 +219,7 @@ export default function useStreaming() {
|
||||
const { gid, ...rest } = data;
|
||||
dispatch(
|
||||
updateChannel({
|
||||
id: gid,
|
||||
gid,
|
||||
...rest
|
||||
})
|
||||
);
|
||||
@@ -233,7 +233,7 @@ export default function useStreaming() {
|
||||
dispatch(
|
||||
updateChannel({
|
||||
operation: "add_member",
|
||||
id: gid,
|
||||
gid,
|
||||
members: uids
|
||||
})
|
||||
);
|
||||
@@ -242,13 +242,13 @@ export default function useStreaming() {
|
||||
case "user_leaved_group":
|
||||
{
|
||||
const { gid, uid: uids } = data;
|
||||
if (uids.findIndex((id) => id == loginUid) > -1) {
|
||||
if (uids.findIndex((uid) => uid == loginUid) > -1) {
|
||||
dispatch(removeChannel(gid));
|
||||
} else {
|
||||
dispatch(
|
||||
updateChannel({
|
||||
operation: "remove_member",
|
||||
id: gid,
|
||||
gid,
|
||||
members: uids
|
||||
})
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function useUploadFile(props: { context: string; id: string } | o
|
||||
if (!file) return;
|
||||
setData(null);
|
||||
const {
|
||||
name = `rustchat-${+new Date()}.${file.type.split("/")[1]}`,
|
||||
name = `-${+new Date()}.${file.type.split("/")[1]}`,
|
||||
type: file_type,
|
||||
size: file_size
|
||||
} = file;
|
||||
|
||||
+20
-61
@@ -11,27 +11,7 @@ export const isImage = (file_type = "", size = 0) => {
|
||||
return file_type.startsWith("image") && size <= FILE_IMAGE_SIZE;
|
||||
};
|
||||
|
||||
const deepSortObject = (obj: object): any => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.map((entry) => [
|
||||
entry[0],
|
||||
typeof entry[1] === "object" ? deepSortObject(entry[1]) : entry[1]
|
||||
])
|
||||
.sort()
|
||||
);
|
||||
};
|
||||
|
||||
export const isObjectEqual = (obj1: any, obj2: any) => {
|
||||
// Check for reference equal
|
||||
if (obj1 === obj2) return true;
|
||||
// Check for deep equal
|
||||
let o1 = JSON.stringify(deepSortObject(obj1 ?? {}));
|
||||
let o2 = JSON.stringify(deepSortObject(obj2 ?? {}));
|
||||
return o1 === o2;
|
||||
};
|
||||
|
||||
export const isTreatAsImage = (file: File | null) => {
|
||||
export const isTreatAsImage = (file) => {
|
||||
let isImage = false;
|
||||
if (!file) return isImage;
|
||||
const { type, size } = file;
|
||||
@@ -42,8 +22,8 @@ export const isTreatAsImage = (file: File | null) => {
|
||||
return isImage;
|
||||
};
|
||||
|
||||
export const getNonNullValues = (obj: any, whiteList = ["log_id"]): any => {
|
||||
const tmp: any = {};
|
||||
export const getNonNullValues = (obj, whiteList = ["log_id"]) => {
|
||||
const tmp = {};
|
||||
Object.keys(obj).forEach((k) => {
|
||||
if (!whiteList.includes(k) && obj[k] !== null) {
|
||||
tmp[k] = obj[k];
|
||||
@@ -51,20 +31,14 @@ export const getNonNullValues = (obj: any, whiteList = ["log_id"]): any => {
|
||||
});
|
||||
return tmp;
|
||||
};
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export function getDefaultSize(size: Size | null = null, min: number = 480) {
|
||||
export function getDefaultSize(size = null, min = 480) {
|
||||
if (!size) return { width: 0, height: 0 };
|
||||
const { width: oWidth, height: oHeight } = size;
|
||||
if (oWidth == oHeight) {
|
||||
const tmp = min > oWidth ? oWidth : min;
|
||||
return { width: tmp, height: tmp };
|
||||
}
|
||||
const isVertical = oWidth <= oHeight;
|
||||
const isVertical = oWidth > oHeight ? false : true;
|
||||
let dWidth = 0;
|
||||
let dHeight = 0;
|
||||
if (isVertical) {
|
||||
@@ -76,8 +50,7 @@ export function getDefaultSize(size: Size | null = null, min: number = 480) {
|
||||
}
|
||||
return { width: dWidth, height: dHeight };
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number, decimals = 2) {
|
||||
export function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
|
||||
const k = 1000;
|
||||
@@ -88,16 +61,9 @@ export function formatBytes(bytes: number, decimals = 2) {
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
export interface ImageSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const getImageSize = (url: string): Promise<ImageSize> => {
|
||||
const size: ImageSize = { width: 0, height: 0 };
|
||||
// todo: check inactive code
|
||||
// if (!url) return size;
|
||||
export const getImageSize = (url) => {
|
||||
const size = { width: 0, height: 0 };
|
||||
if (!url) return size;
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
@@ -111,15 +77,13 @@ export const getImageSize = (url: string): Promise<ImageSize> => {
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getInitials = (name: string) => {
|
||||
export const getInitials = (name) => {
|
||||
const arr = name.split(" ").filter((n) => !!n);
|
||||
return arr
|
||||
.map((t) => t[0])
|
||||
.join("")
|
||||
.toUpperCase();
|
||||
};
|
||||
|
||||
export const getInitialsAvatar = ({
|
||||
initials = "UK",
|
||||
initial_size = 0,
|
||||
@@ -138,7 +102,7 @@ export const getInitialsAvatar = ({
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
|
||||
const context = canvas.getContext("2d")!;
|
||||
const context = canvas.getContext("2d");
|
||||
context.scale(devicePixelRatio, devicePixelRatio);
|
||||
context.rect(0, 0, canvas.width, canvas.height);
|
||||
context.fillStyle = background;
|
||||
@@ -156,8 +120,12 @@ export const getInitialsAvatar = ({
|
||||
/* istanbul ignore next */
|
||||
return canvas.toDataURL("image/png");
|
||||
};
|
||||
|
||||
export function sliceFile(file: File | null, chunksAmount: number) {
|
||||
/**
|
||||
* @param {File|Blob} - file to slice
|
||||
* @param {Number} - chunksAmount
|
||||
* @return {Array} - an array of Blobs
|
||||
**/
|
||||
export function sliceFile(file, chunksAmount) {
|
||||
if (!file) return null;
|
||||
let byteIndex = 0;
|
||||
let chunks = [];
|
||||
@@ -170,8 +138,7 @@ export function sliceFile(file: File | null, chunksAmount: number) {
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
export const getFileIcon = (type: string, name: string = "") => {
|
||||
export const getFileIcon = (type, name = "") => {
|
||||
let icon = null;
|
||||
|
||||
const checks = {
|
||||
@@ -213,18 +180,10 @@ export const getFileIcon = (type: string, name: string = "") => {
|
||||
}
|
||||
return icon;
|
||||
};
|
||||
|
||||
export const normalizeArchiveData = (
|
||||
data = null,
|
||||
filePath: string | null = null,
|
||||
uid: number | null = null
|
||||
) => {
|
||||
export const normalizeArchiveData = (data = null, filePath = null, uid = null) => {
|
||||
if (!data || !filePath) return [];
|
||||
const { messages, users } = data;
|
||||
const getUrls = (
|
||||
uid: number,
|
||||
{ content, content_type, file_id, thumbnail_id, filePath, avatar }
|
||||
) => {
|
||||
const getUrls = (uid, { content, content_type, file_id, thumbnail_id, filePath, avatar }) => {
|
||||
// uid存在,则favorite,否则archive
|
||||
const prefix = uid
|
||||
? `${BASE_URL}/favorite/attachment/${uid}/${filePath}/`
|
||||
|
||||
Reference in New Issue
Block a user