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:
HD
2022-06-28 10:10:56 +08:00
47 changed files with 513 additions and 355 deletions
+2 -1
View File
@@ -25,5 +25,6 @@
"emmet.triggerExpansionOnTab": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"cSpell.words": ["btns", "oidc", "vocechat"]
}
+2 -2
View File
@@ -1,8 +1,8 @@
## Refs
- preview demo: https://privoce.rustchat.com/
- preview demo: https://privoce.voce.chat/
- design: https://www.figma.com/file/EHnNr53kNmDWgUT86It6CH/UI
- backend APIs: https://dev.rustchat.com/api/swagger
- backend APIs: https://dev.voce.chat/api/swagger
- text editor: https://plate.udecode.io/docs/installation
- markdown editor: https://nhn.github.io/tui.editor/latest/
- redux: [@reduxjs/toolkit](https://redux-toolkit.js.org/introduction/getting-started)
+3 -2
View File
@@ -1,8 +1,8 @@
{
"name": "rustchat-web",
"name": "vocechat-web",
"version": "0.2.14",
"private": true,
"homepage": "https://privoce.rustchat.com",
"homepage": "https://privoce.voce.chat",
"dependencies": {
"@emoji-mart/data": "^1.0.2",
"@metamask/onboarding": "^1.0.1",
@@ -43,6 +43,7 @@
"linkify-plugin-mention": "^3.0.4",
"linkifyjs": "^3.0.5",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"masonry-layout": "^4.2.2",
"mini-css-extract-plugin": "^2.6.0",
"pwa-badge": "^1.1.1",
+1 -1
View File
@@ -1 +1 @@
privoce.rustchat.com
privoce.voce.chat
File diff suppressed because one or more lines are too long
+12 -12
View File
@@ -15,12 +15,12 @@ self.addEventListener("notificationclick", function (event) {
firstClient.focus();
return;
}
const { rustchat_from_uid, rustchat_to_uid, rustchat_to_gid } = customData;
const { vocechat_from_uid, vocechat_to_uid, vocechat_to_gid } = customData;
let chatClient;
let redirectPath = rustchat_to_uid
? `/chat/dm/${rustchat_from_uid}`
: rustchat_to_gid
? `/chat/channel/${rustchat_to_gid}`
let redirectPath = vocechat_to_uid
? `/chat/dm/${vocechat_from_uid}`
: vocechat_to_gid
? `/chat/channel/${vocechat_to_gid}`
: "";
if (!redirectPath) {
firstClient.focus();
@@ -49,13 +49,13 @@ importScripts(
// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
apiKey: "AIzaSyDyJ6B1Ouenoha_gdGkBwIkBNStlwhlbO0",
authDomain: "rustchat-develop.firebaseapp.com",
projectId: "rustchat-develop",
storageBucket: "rustchat-develop.appspot.com",
messagingSenderId: "418687074928",
appId: "1:418687074928:web:753286adbf239f5af9eab5",
measurementId: "G-XV476KEC8P"
apiKey: "AIzaSyCc3VuCJZgzQLIH2wrYdQzsUOc1DuZiIOA",
authDomain: "vocechatdev.firebaseapp.com",
projectId: "vocechatdev",
storageBucket: "vocechatdev.appspot.com",
messagingSenderId: "526613312184",
appId: "1:526613312184:web:d13c92582baf470d487a4d",
measurementId: "G-82RQ3YSCP7"
};
firebase.initializeApp(firebaseConfig);
+1 -1
View File
@@ -55,7 +55,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Rustchat WebAPP</title>
<title>VoceChat WebAPP</title>
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/github.min.css"> -->
<style>
html {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "RustChat",
"name": "VoceChat",
"short_name": "Your private chat APP",
"icons": [
{
+1 -1
View File
@@ -99,7 +99,7 @@ checkBrowsers(paths.appPath, isInteractive)
// version and md5 files
fs.writeFileSync(`${buildFolder}/VERSION`, require("../package.json").version);
const hash = md5File.sync(`${buildFolder}/VERSION`);
fs.writeFileSync(`${buildFolder}/web.rustchat.md5`, hash);
fs.writeFileSync(`${buildFolder}/web..md5`, hash);
},
(err) => {
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === "true";
+20 -19
View File
@@ -1,22 +1,22 @@
// const BASE_URL = `${location.origin}/api`;
const BASE_URL = `https://dev.rustchat.com/api`;
export const CACHE_VERSION = `0.2.17`;
export const CACHE_VERSION = `0.3.0`;
export const ContentTypes = {
text: "text/plain",
markdown: "text/markdown",
file: "rustchat/file",
file: "vocechat/file",
archive: "vocechat/archive",
formData: "multipart/form-data",
json: "application/json",
archive: "rustchat/archive"
json: "application/json"
};
export const firebaseConfig = {
apiKey: "AIzaSyDyJ6B1Ouenoha_gdGkBwIkBNStlwhlbO0",
authDomain: "rustchat-develop.firebaseapp.com",
projectId: "rustchat-develop",
storageBucket: "rustchat-develop.appspot.com",
messagingSenderId: "418687074928",
appId: "1:418687074928:web:753286adbf239f5af9eab5",
measurementId: "G-XV476KEC8P"
apiKey: "AIzaSyCc3VuCJZgzQLIH2wrYdQzsUOc1DuZiIOA",
authDomain: "vocechatdev.firebaseapp.com",
projectId: "vocechatdev",
storageBucket: "vocechatdev.appspot.com",
messagingSenderId: "526613312184",
appId: "1:526613312184:web:d13c92582baf470d487a4d",
measurementId: "G-82RQ3YSCP7"
};
export const ChatPrefixs = {
channel: "#",
@@ -27,14 +27,15 @@ export const vapidKey = `BGXCn-5YRXSFw38Q9lUKJ5bibL212-yIQn1pCvthGhp6_KwA29FO1Ax
export const tokenHeader = "X-API-Key";
export const FILE_SLICE_SIZE = 1000 * 200 * 8; //200kb
export const FILE_IMAGE_SIZE = 1000 * 10000 * 8; //10mb
export const KEY_TOKEN = "RUSTCHAT_TOKEN";
export const KEY_EXPIRE = "RUSTCHAT_TOKEN_EXPIRE";
export const KEY_REFRESH_TOKEN = "RUSTCHAT_REFRESH_TOKEN";
export const KEY_UID = "RUSTCHAT_CURR_UID";
export const KEY_DEVICE_KEY = "RUSTCHAT_DEVICE_KEY";
export const KEY_USERS_VERSION = "RUSTCHAT_USERS_VERSION";
export const KEY_AFTER_MID = "RUSTCHAT_AFTER_MID";
export const KEY_PWA_INSTALLED = "RUSTCHAT_PWA_INSTALLED";
export const KEY_TOKEN = "VOCECHAT_TOKEN";
export const KEY_EXPIRE = "VOCECHAT_TOKEN_EXPIRE";
export const KEY_REFRESH_TOKEN = "VOCECHAT_REFRESH_TOKEN";
export const KEY_UID = "VOCECHAT_CURR_UID";
export const KEY_DEVICE_KEY = "VOCECHAT_DEVICE_KEY";
export const KEY_USERS_VERSION = "VOCECHAT_USERS_VERSION";
export const KEY_AFTER_MID = "VOCECHAT_AFTER_MID";
export const KEY_PWA_INSTALLED = "VOCECHAT_PWA_INSTALLED";
export const KEY_LOCAL_MAGIC_TOKEN = "VOCECHAT_LOCAL_MAGIC_TOKEN";
export const Emojis = ["👍", "❤️", "😄", "👀", "👎", "🎉", "🙁", "🚀"];
export const Views = {
item: "item",
+18 -13
View File
@@ -2,8 +2,8 @@ import { createApi } from "@reduxjs/toolkit/query/react";
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";
import { AuthData } from "../../types/auth";
import BASE_URL, { KEY_DEVICE_KEY, KEY_LOCAL_MAGIC_TOKEN } from "../config";
import { AuthData, LoginCredential } from "../../types/auth";
const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY);
@@ -18,12 +18,12 @@ export const authApi = createApi({
reducerPath: "authApi",
baseQuery,
endpoints: (builder) => ({
login: builder.mutation<AuthData, string>({
query: (credentials) => ({
login: builder.mutation<AuthData, LoginCredential>({
query: (credential) => ({
url: "token/login",
method: "POST",
body: {
credential: credentials,
credential,
device: getDeviceId(),
device_token: "test"
}
@@ -32,13 +32,10 @@ export const authApi = createApi({
const { avatar_updated_at } = data.user;
return {
...data,
user: {
...data.user,
avatar:
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`
}
avatar:
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`
};
},
async onQueryStarted(params, { dispatch, queryFulfilled }) {
@@ -47,6 +44,8 @@ export const authApi = createApi({
if (data) {
dispatch(setAuthData(data));
}
// 从localstorage 去掉 magic token
localStorage.removeItem(KEY_LOCAL_MAGIC_TOKEN);
} catch {
console.log("login error");
}
@@ -139,6 +138,11 @@ export const authApi = createApi({
url: `/token/metamask/nonce?public_address=${address}`
})
}),
checkEmail: builder.query({
query: (email) => ({
url: `/user/check_email?email=${encodeURIComponent(email)}`
})
}),
getCredentials: builder.query({
query: () => ({ url: "/token/credentials" })
}),
@@ -153,7 +157,7 @@ export const authApi = createApi({
}
}
}),
getInitialized: builder.query({
getInitialized: builder.query<boolean, void>({
query: () => ({ url: "/admin/system/initialized" }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
@@ -168,6 +172,7 @@ export const authApi = createApi({
});
export const {
useLazyCheckEmailQuery,
useGetInitializedQuery,
useSendLoginMagicLinkMutation,
useSendRegMagicLinkMutation,
+12 -14
View File
@@ -9,6 +9,7 @@ const whiteList = [
"register",
"sendLoginMagicLink",
"sendRegMagicLink",
"checkEmail",
"checkMagicTokenValid",
"getGoogleAuthConfig",
"getGithubAuthConfig",
@@ -76,16 +77,6 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
toast.error(`${api.endpoint}: Failed to fetch`);
}
break;
case 404:
{
toast.error("Request Not Found");
}
break;
case 500:
{
toast.error(result.error.data || "server error");
}
break;
case 401:
{
if (api.endpoint !== "login") {
@@ -93,15 +84,22 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
location.href = "/#/login";
toast.error("API Not Authenticated");
}
// toast.error("token expired, please login again");
// } else {
// return;
// }
}
break;
case 403:
toast.error("Request Not Allowed");
break;
case 404:
{
toast.error("Request Not Found");
}
break;
case 500:
{
toast.error(result.error.data || "Server Error");
}
break;
default:
break;
}
+48 -49
View File
@@ -1,26 +1,31 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import BASE_URL from "../config";
import { updateInfo } from "../slices/server";
import { updateInfo, StoredServer } from "../slices/server";
import baseQuery from "./base.query";
import { RootState } from "../store";
import { User } from "../../types/auth";
import {
FirebaseConfig,
GoogleAuthConfig,
LoginConfig,
Server,
TestEmailDTO,
NewAdminDTO,
SMTPConfig,
AgoraConfig,
GithubAuthConfig
} from "../../types/server";
const defaultExpireDuration = 7 * 24 * 60 * 60;
// todo: doc
interface GetServerResponse {}
interface GetThirdPartySecretResponse {}
interface UpdateThirdPartySecretResponse {}
// interface GetServerVersionResponse {}
interface GetLoginConfigResponse {}
export const serverApi = createApi({
reducerPath: "serverApi",
baseQuery,
endpoints: (builder) => ({
getServer: builder.query<GetServerResponse, void>({
getServer: builder.query<StoredServer, void>({
query: () => ({ url: `admin/system/organization` }),
transformResponse: (data) => {
data.logo = `${BASE_URL}/resource/organization/logo?t=${+new Date()}`;
return data;
transformResponse: (data: Server) => {
const logo = `${BASE_URL}/resource/organization/logo?t=${+new Date()}`;
return { ...data, logo };
},
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
@@ -31,98 +36,94 @@ export const serverApi = createApi({
}
}
}),
getThirdPartySecret: builder.query<GetThirdPartySecretResponse, void>({
getThirdPartySecret: builder.query<string, void>({
query: () => ({
// headers: {
// "content-type": "text/plain",
// accept: "text/plain",
// },
url: `/admin/system/third_party_secret`,
responseHandler: (response) => response.text()
responseHandler: (response: Response) => response.text()
}),
keepUnusedDataFor: 0
}),
updateThirdPartySecret: builder.mutation<UpdateThirdPartySecretResponse, void>({
updateThirdPartySecret: builder.mutation<string, void>({
query: () => ({
url: `/admin/system/third_party_secret`,
method: "POST",
responseHandler: (response) => response.text()
responseHandler: (response: Response) => response.text()
})
}),
getMetrics: builder.query({
query: () => ({ url: `/admin/system/metrics` })
}),
getServerVersion: builder.query<string, void>({
query: () => ({
headers: { accept: "text/plain" },
headers: {
// "content-type": "text/plain",
accept: "text/plain"
},
url: `/admin/system/version`,
responseHandler: (response: Response) => response.text()
})
}),
getFirebaseConfig: builder.query({
getFirebaseConfig: builder.query<FirebaseConfig, void>({
query: () => ({ url: `admin/fcm/config` })
}),
getGoogleAuthConfig: builder.query({
getGoogleAuthConfig: builder.query<GoogleAuthConfig, void>({
query: () => ({ url: `admin/google_auth/config` })
}),
updateGoogleAuthConfig: builder.mutation({
updateGoogleAuthConfig: builder.mutation<void, GoogleAuthConfig>({
query: (data) => ({
url: `admin/google_auth/config`,
method: "POST",
body: data
})
}),
getGithubAuthConfig: builder.query({
getGithubAuthConfig: builder.query<GithubAuthConfig, void>({
query: () => ({ url: `admin/github_auth/config` })
}),
updateGithubAuthConfig: builder.mutation({
updateGithubAuthConfig: builder.mutation<void, GithubAuthConfig>({
query: (data) => ({
url: `admin/github_auth/config`,
method: "POST",
body: data
})
}),
sendTestEmail: builder.mutation({
sendTestEmail: builder.mutation<void, TestEmailDTO>({
query: (data) => ({
url: `/admin/system/send_mail`,
method: "POST",
body: data
})
}),
updateFirebaseConfig: builder.mutation({
updateFirebaseConfig: builder.mutation<void, FirebaseConfig>({
query: (data) => ({
url: `admin/fcm/config`,
method: "POST",
body: data
})
}),
getAgoraConfig: builder.query({
getAgoraConfig: builder.query<AgoraConfig, void>({
query: () => ({ url: `admin/agora/config` })
}),
updateAgoraConfig: builder.mutation({
updateAgoraConfig: builder.mutation<void, AgoraConfig>({
query: (data) => ({
url: `admin/agora/config`,
method: "POST",
body: data
})
}),
getSMTPConfig: builder.query({
getSMTPConfig: builder.query<SMTPConfig, void>({
query: () => ({ url: `admin/smtp/config` })
}),
getSMTPStatus: builder.query({
getSMTPStatus: builder.query<boolean, void>({
query: () => ({ url: `/admin/smtp/enabled` })
}),
updateSMTPConfig: builder.mutation({
updateSMTPConfig: builder.mutation<void, SMTPConfig>({
query: (data) => ({
url: `admin/smtp/config`,
method: "POST",
body: data
})
}),
getLoginConfig: builder.query<GetLoginConfigResponse, void>({
getLoginConfig: builder.query<LoginConfig, void>({
query: () => ({ url: `admin/login/config` })
}),
updateLoginConfig: builder.mutation({
updateLoginConfig: builder.mutation<void, LoginConfig>({
query: (data) => ({
url: `admin/login/config`,
method: "POST",
@@ -151,29 +152,30 @@ export const serverApi = createApi({
}
}
}),
createInviteLink: builder.query({
createInviteLink: builder.query<string, number>({
query: (expired_in = defaultExpireDuration) => ({
headers: {
"content-type": "text/plain",
accept: "text/plain"
},
url: `/admin/system/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text()
responseHandler: (response: Response) => response.text()
}),
transformResponse: (link) => {
transformResponse: (link: string) => {
// 替换掉域名
const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
}
}),
updateServer: builder.mutation({
updateServer: builder.mutation<void, Server>({
query: (data) => ({
url: `admin/system/organization`,
method: "POST",
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
const { name: prevName, description: prevDesc } = getState().server;
const rootStore = getState() as RootState;
const { name: prevName, description: prevDesc } = rootStore.server;
dispatch(updateInfo(data));
try {
await queryFulfilled;
@@ -182,14 +184,14 @@ export const serverApi = createApi({
}
}
}),
createAdmin: builder.mutation({
createAdmin: builder.mutation<User, NewAdminDTO>({
query: (data) => ({
url: `/admin/system/create_admin`,
method: "POST",
body: data
})
}),
getInitialized: builder.query({
getInitialized: builder.query<boolean, void>({
query: () => ({
url: `/admin/system/initialized`
})
@@ -210,15 +212,12 @@ export const {
useLazyGetAgoraConfigQuery,
useLazyGetSMTPConfigQuery,
useLazyGetLoginConfigQuery,
// useGetFirebaseConfigQuery,
// useGetAgoraConfigQuery,
useGetLoginConfigQuery,
useUpdateLoginConfigMutation,
useGetSMTPConfigQuery,
useUpdateSMTPConfigMutation,
useUpdateAgoraConfigMutation,
useGetServerQuery,
useGetMetricsQuery,
useLazyGetServerQuery,
useUpdateServerMutation,
useUpdateLogoMutation,
+6 -8
View File
@@ -1,16 +1,14 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Server } from "../../types/server";
interface State {
name: string;
description: string;
export interface StoredServer extends Server {
logo: string;
inviteLink: {
inviteLink?: {
link: string;
expire: number;
};
}
const initialState: State = {
const initialState: StoredServer = {
name: "",
description: "",
logo: "",
@@ -27,7 +25,7 @@ const serverSlice = createSlice({
resetServer() {
return initialState;
},
fullfillServer(state, action: PayloadAction<State>) {
fullfillServer(state, action: PayloadAction<StoredServer>) {
const {
inviteLink = {
link: "",
@@ -39,7 +37,7 @@ const serverSlice = createSlice({
} = action.payload || {};
return { name, logo, description, inviteLink };
},
updateInfo(state, action: PayloadAction<Partial<State>>) {
updateInfo(state, action: PayloadAction<Partial<StoredServer>>) {
const values = action.payload || {};
// todo: check and remove old logic
// Object.keys(values).forEach((_key) => {
+10 -10
View File
@@ -12,20 +12,20 @@
:root {
/* border radius */
--br: 8px;
--rustchat-navs-bg: #e5e7eb;
--rustchat-body-bg: #f5f6f7;
--rustchat-chat-bg: #fff;
--rustchat-channel-text-color: #1c1c1e;
--rustchat-msg-text-color: #374151;
---navs-bg: #e5e7eb;
---body-bg: #f5f6f7;
---chat-bg: #fff;
---channel-text-color: #1c1c1e;
---msg-text-color: #374151;
}
/* @media (prefers-color-scheme: dark) {
:root{
--br:8px;
--rustchat-navs-bg:#121926;
--rustchat-body-bg:#1F2A37;
--rustchat-chat-bg:#384250;
--rustchat-channel-text-color:#fff;
--rustchat-msg-text-color:#fff;
---navs-bg:#121926;
---body-bg:#1F2A37;
---chat-bg:#384250;
---channel-text-color:#fff;
---msg-text-color:#fff;
}
} */
+48 -12
View File
@@ -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;
+25 -6
View File
@@ -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>
);
};
+2 -2
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
{
"name": "RustChat",
"name": "VoceChat",
"short_name": "Your private chat APP",
"icons": [
{
+1 -1
View File
@@ -10,7 +10,7 @@ export default function usePrompt() {
};
return {
setCanneled: setPrompt,
setCanceled: setPrompt,
prompted: !!localStorage.getItem(KEY_PWA_INSTALLED),
resetPrompt
};
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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);
+4 -4
View File
@@ -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
})
);
+1 -1
View File
@@ -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
View File
@@ -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}/`
+1 -1
View File
@@ -215,7 +215,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
>
<StyledChannelChat
// ref={scrollObserveRef}
id={`RUSTCHAT_FEED_channel_${cid}`}
id={`VOCECHAT_FEED_channel_${cid}`}
>
{/* <div className="anchor"></div> */}
{hasMore ? (
+1 -1
View File
@@ -85,7 +85,7 @@ export default function DMChat({ uid = "", dropFiles = [] }) {
</StyledHeader>
}
>
<StyledDMChat id={`RUSTCHAT_FEED_user_${uid}`}>
<StyledDMChat id={`VOCECHAT_FEED_user_${uid}`}>
{hasMore ? <LoadMore pullUp={pullUp} /> : null}
{[...feeds].map((mid, idx) => {
const curr = messageData[mid];
+1 -1
View File
@@ -3,7 +3,7 @@ const StyledWrapper = styled.div`
display: flex;
width: 100vw;
height: 100vh;
background-color: var(--rustchat-navs-bg);
background-color: var(---navs-bg);
> .col {
height: 100%;
display: flex;
+1 -1
View File
@@ -122,7 +122,7 @@ export default function InvitePage() {
<div className="form animate__animated animate__fadeInDown animate__faster">
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Sign Up to Rustchat</h2>
<h2 className="title">Sign Up to VoceChat</h2>
<span className="desc">Please enter your details.</span>
</div>
<form onSubmit={handleReg}>
+1 -1
View File
@@ -58,7 +58,7 @@ export default function MetamaskLoginButton({ login }) {
console.log("get sn");
const signature = await ethereum.request({
method: "personal_sign",
params: [nonce, address, "hello from rustchat"]
params: [nonce, address, "hello from "]
});
return signature;
};
+9 -18
View File
@@ -38,14 +38,6 @@ export default function LoginPage() {
const exists = query.get("exists");
if (oauth) {
switch (oauth) {
case "github":
if (code) {
login({
code: code,
type: "github"
});
}
break;
case "oidc":
if (code && state) {
login({
@@ -78,19 +70,18 @@ export default function LoginPage() {
useEffect(() => {
if (error) {
console.log(error);
console.log("login err", error);
switch (error.status) {
case "PARSING_ERROR":
toast.error(error.data);
break;
case 401:
toast.error("username or password incorrect");
toast.error("Username or Password incorrect");
break;
case 404:
toast.error("account not exsit");
case 410:
toast.error(
"No associated account found, please contact admin for an invitation link to join."
);
break;
default:
toast.error("something error");
toast.error("Something Error");
break;
}
return;
@@ -143,7 +134,7 @@ export default function LoginPage() {
<div className="form">
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Login to Rustchat</h2>
<h2 className="title">Login to VoceChat</h2>
<span className="desc">Please enter your details.</span>
</div>
<form onSubmit={handleLogin}>
@@ -173,7 +164,7 @@ export default function LoginPage() {
{hasDivider && <hr className="or" />}
{enableMagicLink && <MagicLinkLogin />}
{googleLogin && <GoogleLoginButton clientId={clientId} />}
{enableGithubLogin && <GithubLoginButton config={githubAuthConfig} />}
{enableGithubLogin && <GithubLoginButton client_id={githubAuthConfig?.client_id} />}
{enableMetamaskLogin && <MetamaskLoginButton login={login} />}
{oidc.length > 0 && <OidcLoginButton issuers={oidc} />}
{whoCanSignUp === "EveryOne" && <SignUpLink />}
+1 -1
View File
@@ -49,7 +49,7 @@ export default function OnboardingPage() {
return (
<>
<Helmet>
<title>Rustchat Setup</title>
<title>VoceChat Setup</title>
</Helmet>
<StyledOnboardingPage>
<Navigator {...serverSetup} />
+1 -1
View File
@@ -63,7 +63,7 @@ export default function DonePage({ serverName }) {
return (
<StyledWrapper>
<span className="primaryText">Welcome to {serverName}</span>
<span className="secondaryText">Proudly presented by Rustchat</span>
<span className="secondaryText">Proudly presented by VoceChat</span>
<span className="tip">
More settings, including domain resolution, privileges, securities, and invites are
available in <span className="strong">Settings</span>
+1 -1
View File
@@ -46,7 +46,7 @@ const StyledWrapper = styled.div`
export default function WelcomePage({ nextStep }) {
return (
<StyledWrapper>
<span className="primaryText">Welcome to your Rustchat!</span>
<span className="primaryText">Welcome to your VoceChat!</span>
<span className="secondaryText">
Everything in this space is owned by you. Lets set up your space!
</span>
+1 -1
View File
@@ -28,7 +28,7 @@ export default function ExpiredTip() {
return (
<Styled>
<div className="title">Magic link expired</div>
<p className="desc">Go back to your original Rustchat tab and request a new magic link.</p>
<p className="desc">Go back to your original VoceChat tab and request a new magic link.</p>
<p className="desc">You can close this window now.</p>
</Styled>
);
+21 -7
View File
@@ -14,10 +14,14 @@ import { useRegisterMutation } from "../../app/services/auth";
export default function RegWithUsername() {
const [checkTokenInvalid, { data: isTokenValid, isLoading: checkingToken }] =
useCheckMagicTokenValidMutation();
const [login, { isLoading: loginLoading, error, isSuccess: loginSuccess, data: loginData }] =
useLoginMutation();
const [register, { isLoading: regLoading, isSuccess: regSuccess, data: regData }] =
useRegisterMutation();
const [
login,
{ isLoading: loginLoading, error: loginError, isSuccess: loginSuccess, data: loginData }
] = useLoginMutation();
const [
register,
{ isLoading: regLoading, isSuccess: regSuccess, data: regData, error: regError }
] = useRegisterMutation();
// const navigateTo = useNavigate();
const { from = "reg" } = useParams();
const dispatch = useDispatch();
@@ -32,8 +36,7 @@ export default function RegWithUsername() {
}, [token]);
useEffect(() => {
console.log("errr", error);
switch (error?.status) {
switch (loginError?.status) {
case 401:
toast.error("Invalided Token");
break;
@@ -41,7 +44,17 @@ export default function RegWithUsername() {
default:
break;
}
}, [error]);
}, [loginError]);
useEffect(() => {
switch (regError?.status) {
case 409:
toast.error("Something Conflicted!");
break;
default:
break;
}
}, [regError]);
useEffect(() => {
const isSuccess = loginSuccess || regSuccess;
const data = loginData || regData;
@@ -74,6 +87,7 @@ export default function RegWithUsername() {
const { value } = evt.target;
setUsername(value);
};
console.log("ffff", from);
if (!token) return "No Token";
if (checkingToken) return "Checking Magic Link...";
if (!isTokenValid) return <ExpiredTip />;
+106 -41
View File
@@ -1,13 +1,23 @@
import { useState, useEffect, FormEvent, ChangeEvent } from "react";
import { useNavigate } from "react-router-dom";
import { useState, useEffect } 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";
import { useSendRegMagicLinkMutation } from "../../app/services/auth";
import { useLazyCheckEmailQuery, useSendRegMagicLinkMutation } from "../../app/services/auth";
import EmailNextTip from "./EmailNextStepTip";
import SignInLink from "./SignInLink";
import { useGetLoginConfigQuery } from "../../app/services/server";
import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig";
import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig";
import GoogleLoginButton from "../../common/component/GoogleLoginButton";
import GithubLoginButton from "../../common/component/GithubLoginButton";
export default function Reg() {
const [sendRegMagicLink, { isLoading, data, isSuccess }] = useSendRegMagicLinkMutation();
const navigateTo = useNavigate();
const [sendRegMagicLink, { isLoading: signingUp, data, isSuccess }] =
useSendRegMagicLinkMutation();
const [checkEmail, { isLoading: checkingEmail }] = useLazyCheckEmailQuery();
// const navigateTo = useNavigate();
const [magicToken, setMagicToken] = useState("");
const [input, setInput] = useState({
email: "",
@@ -20,6 +30,8 @@ export default function Reg() {
// const githubCode = query.get("gcode");
const token = query.get("magic_token");
if (token) {
//本地存一下 magic token 后续oauth流程用到
localStorage.setItem(KEY_LOCAL_MAGIC_TOKEN, token);
setMagicToken(token);
}
}, []);
@@ -29,23 +41,39 @@ export default function Reg() {
const { new_magic_token, mail_is_sent } = data;
if (!mail_is_sent && new_magic_token) {
// 直接进入set_name流程
navigateTo(`?magic_token=${new_magic_token}#/register/set_name`);
location.href = `?magic_token=${new_magic_token}#/register/set_name`;
// navigateTo(`/register/set_name?magic_token=${new_magic_token}`);
}
}
}, [isSuccess, data]);
const handleReg = (evt: FormEvent<HTMLFormElement>) => {
const handleReg = async (evt) => {
evt.preventDefault();
const { email, password } = input;
sendRegMagicLink({
magic_token: magicToken,
email,
password
});
const { email, password, confirmPassword } = input;
if (password !== confirmPassword) {
toast.error("Not Same Password!");
return;
}
const { data: canReg } = await checkEmail(email);
console.log("can reg", canReg);
if (canReg) {
sendRegMagicLink({
magic_token: magicToken,
email,
password
});
} else {
toast.error("Email already registered!");
}
// sendMagicLink(email);
};
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
const handleCompare = () => {
const { password, confirmPassword } = input;
if (password !== confirmPassword) {
toast.error("Not Same Password!");
}
};
const handleInput = (evt) => {
const { type } = evt.target.dataset;
const { value } = evt.target;
// console.log(type, value);
@@ -54,33 +82,70 @@ export default function Reg() {
return { ...prev };
});
};
const { email, password } = input;
const { clientId } = useGoogleAuthConfig();
const { config: githubAuthConfig } = useGithubAuthConfig();
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
if (!loginConfigSuccess) return null;
const {
github: enableGithubLogin,
google: enableGoogleLogin,
who_can_sign_up: whoCanSignUp
} = loginConfig;
const googleLogin = enableGoogleLogin && clientId;
// magic token 没有并且没有开放注册
if (whoCanSignUp !== "EveryOne" && !magicToken)
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 (
<form onSubmit={handleReg}>
<Input
className="large"
name="email"
value={email}
required
placeholder="Enter your email"
data-type="email"
onChange={handleInput}
/>
<Input
className="large"
type="password"
value={password}
name="password"
required
data-type="password"
onChange={handleInput}
placeholder="Enter your password"
/>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Signing Up" : `Sign Up`}
</Button>
</form>
<>
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Sign Up to VoceChat</h2>
<span className="desc">Please enter your details.</span>
</div>
<form onSubmit={handleReg} autoSave={"false"} autoComplete={"true"}>
<Input
className="large"
name="email"
value={email}
required
placeholder="Enter email"
data-type="email"
onChange={handleInput}
/>
<Input
className="large"
type="password"
value={password}
name="password"
required
data-type="password"
onChange={handleInput}
placeholder="Enter password"
/>
<Input
required
onBlur={handleCompare}
type="password"
name={"confirmPassword"}
value={confirmPassword}
data-type="confirmPassword"
onChange={handleInput}
placeholder="Confirm Password"
></Input>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Signing Up" : `Sign Up`}
</Button>
</form>
<hr className="or" />
{googleLogin && <GoogleLoginButton type="register" clientId={clientId} />}
{enableGithubLogin && (
<GithubLoginButton type="register" client_id={githubAuthConfig?.client_id} />
)}
<SignInLink />
</>
);
}
+3 -39
View File
@@ -1,49 +1,13 @@
import { Outlet } from "react-router-dom";
import { useMatch } from "react-router-dom";
import BASE_URL from "../../app/config";
import SignInLink from "./SignInLink";
import { useGetLoginConfigQuery } from "../../app/services/server";
import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig";
import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig";
import GoogleLoginButton from "../../common/component/GoogleLoginButton";
import GithubLoginButton from "../../common/component/GithubLoginButton";
import StyledWrapper from "./styled";
export default function Reg() {
const isRegHome = useMatch(`/register`);
const { clientId } = useGoogleAuthConfig();
const { config: githubAuthConfig } = useGithubAuthConfig();
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
if (!loginConfigSuccess) return null;
const {
github: enableGithubLogin,
google: enableGoogleLogin,
who_can_sign_up: whoCanSignUp
} = loginConfig;
const googleLogin = enableGoogleLogin && clientId;
// 没有开放注册
if (whoCanSignUp !== "EveryOne") return `Open Register is Closed!`;
export default function RegContainer() {
// const isRegHome = useMatch(`/register`);
return (
<StyledWrapper>
<div className="form">
{isRegHome && (
<>
<div className="tips">
<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>
</>
)}
<Outlet />
{isRegHome && (
<>
<hr className="or" />
{googleLogin && <GoogleLoginButton clientId={clientId} />}
{enableGithubLogin && <GithubLoginButton config={githubAuthConfig} />}
<SignInLink />
</>
)}
</div>
</StyledWrapper>
);
+1 -1
View File
@@ -35,7 +35,7 @@ const checkFilter = (data, filter, channelMessage) => {
return selected;
};
let msnry = null;
let msnry: typeof Masonry | null;
function ResourceManagement({ fileMessages }) {
const listContainerRef = useRef(null);
const [filter, setFilter] = useState({});
+1 -1
View File
@@ -71,7 +71,7 @@ export default function SendMagicLinkPage() {
<>
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Login to Rustchat</h2>
<h2 className="title">Login to VoceChat</h2>
<span className="desc">Please enter your Email</span>
</div>
<form onSubmit={handleLogin}>
+2 -2
View File
@@ -99,8 +99,8 @@ export default function APIConfig() {
<Button>Update Secret</Button>
</Tippy>
<div className="tip">
Tip: The security key agreed between the rustchat server and the third-party app is used to
encrypt the communication data.{" "}
Tip: The security key agreed between the server and the third-party app is used to encrypt
the communication data.{" "}
</div>
</Styled>
);
+4 -4
View File
@@ -107,7 +107,7 @@ export default function Logins() {
<div className="title">
<div className="txt">
<Label>Google</Label>
<Tooltip link="https://doc.rustchat.com/en-us/login-google.html" />
<Tooltip link="https://doc.voce.chat/en-us/login-google.html" />
</div>
<span className="desc">Allows members login with Google.</span>
</div>
@@ -130,7 +130,7 @@ export default function Logins() {
<div className="title">
<div className="txt">
<Label>Github</Label>
<Tooltip link="https://doc.rustchat.com/en-us/login-github.html" />
<Tooltip link="https://doc.voce.chat/en-us/login-github.html" />
</div>
<span className="desc">Allows members login with Github.</span>
</div>
@@ -161,7 +161,7 @@ export default function Logins() {
<div className="title">
<div className="txt">
<Label>Metamask</Label>
<Tooltip link="https://doc.rustchat.com/en-us/login-metamask.html" />
<Tooltip link="https://doc.voce.chat/en-us/login-metamask.html" />
</div>
<span className="desc">Allows members login with Metamask.</span>
</div>
@@ -176,7 +176,7 @@ export default function Logins() {
<div className="title">
<div className="txt">
<Label htmlFor="desc">OIDC</Label>
<Tooltip link="https://doc.rustchat.com/en-us/login-webid.html" />
<Tooltip link="https://doc.voce.chat/en-us/login-webid.html" />
</div>
<span className="desc">Save my login details for next time.</span>
</div>
+1 -1
View File
@@ -125,7 +125,7 @@ export default function ConfigSMTP() {
<div className="tip">
<img src={iconQuestion} alt="question icon" />
<a
href="https://rustchat.com/doc/smtp-setting"
href="https://voce.chat/doc/smtp-setting"
target="_blank"
className="link"
rel="noreferrer"
+46
View File
@@ -23,3 +23,49 @@ export interface AuthData extends AuthToken {
initialized?: boolean;
user: User;
}
export type PasswordCredential = {
email: string;
password: string;
type: "password";
};
export type MagicLinkCredential = {
magic_token: string;
extra_name?: string;
type: "magiclink";
};
export type GoogleCredential = {
id_token: string;
magic_token?: string | null;
type: "google";
};
export type GithubCredential = {
code: string;
magic_token?: string | null;
type: "github";
};
export type OIDCCredential = {
code: string;
state: string;
magic_token?: string | null;
type: "oidc";
};
export type MetamaskCredential = {
public_address: string;
nonce: string;
signature: string;
magic_token?: string | null;
type: "metamask";
};
export type ThirdPartyCredential = {
key: string;
type: "thirdparty";
};
export type LoginCredential =
| PasswordCredential
| OIDCCredential
| MetamaskCredential
| GithubCredential
| GoogleCredential
| ThirdPartyCredential
| MagicLinkCredential;
+1 -1
View File
@@ -2,7 +2,7 @@ export interface ChannelMember {}
export interface Message {}
export type ContentType = "text/plain" | "text/markdown" | "rustchat/file" | "rustchat/archive";
export type ContentType = "text/plain" | "text/markdown" | "vocechat/file" | "vocechat/archive";
export interface PinnedMessage {
mid: number;
+61
View File
@@ -0,0 +1,61 @@
import { User } from "./auth";
export interface Server {
name: string;
description: string;
}
export interface GithubAuthConfig {
client_id: string;
client_secret: string;
}
export interface FirebaseConfig {
enabled: boolean;
token_url: string;
project_id: string;
private_key: string;
client_email: string;
}
export interface AgoraConfig {
enabled: boolean;
url: string;
project_id: string;
app_id: string;
app_certificate: string;
rtm_key: string;
rtm_secret: string;
}
export interface SMTPConfig {
enabled: boolean;
host: string;
port: number;
from: string;
username: string;
password: string;
}
export interface GoogleAuthConfig {
client_id: string;
}
export interface TestEmailDTO {
to: string;
content: string;
subject: string;
}
export type WhoCanSignUp = "EveryOne" | "InvitationOnly";
export type OIDCSetting = {
enable: boolean;
favicon: string;
domain: string;
};
export interface LoginConfig {
who_can_sign_up: WhoCanSignUp;
password: boolean;
magic_link: boolean;
google: boolean;
github: boolean;
oidc: OIDCSetting[];
metamask: boolean;
third_party: boolean;
}
export interface NewAdminDTO extends Pick<User, "email" | "name" | "gender"> {
password: string;
}