diff --git a/src/app/services/base.query.js b/src/app/services/base.query.js index 70c03cf6..457fe2b5 100644 --- a/src/app/services/base.query.js +++ b/src/app/services/base.query.js @@ -4,110 +4,106 @@ import dayjs from "dayjs"; import { updateToken, resetAuthData } from "../slices/auth.data"; import BASE_URL, { tokenHeader } from "../config"; const whiteList = [ - "login", - "register", - "sendMagicLink", - "checkInviteTokenValid", - "getGoogleAuthConfig", - "getGithubAuthConfig", - "getSMTPStatus", - "getLoginConfig", - "getServerVersion", - "getServer", - "getOpenid", - "getMetamaskNonce", - "renew", + "login", + "register", + "sendMagicLink", + "checkInviteTokenValid", + "getGoogleAuthConfig", + "getGithubAuthConfig", + "getSMTPStatus", + "getLoginConfig", + "getServerVersion", + "getServer", + "getOpenid", + "getMetamaskNonce", + "renew", + "getInitialized", + "createAdmin", + "updateLoginConfig" ]; const baseQuery = fetchBaseQuery({ - baseUrl: BASE_URL, - prepareHeaders: (headers, { getState, endpoint }) => { - console.log("req", endpoint); - const { token } = getState().authData; - if (token && !whiteList.includes(endpoint)) { - headers.set(tokenHeader, token); - } - return headers; - }, + baseUrl: BASE_URL, + prepareHeaders: (headers, { getState, endpoint }) => { + console.log("req", endpoint); + const { token } = getState().authData; + if (token && !whiteList.includes(endpoint)) { + headers.set(tokenHeader, token); + } + return headers; + } }); let waitingForRenew = null; const baseQueryWithTokenCheck = async (args, api, extraOptions) => { - if (waitingForRenew) { - await waitingForRenew; - } - // 先检查token是否过期,过期则renew - const { - token, - refreshToken, - expireTime = new Date().getTime(), - } = api.getState().authData; - let result = null; - if ( - !whiteList.includes(api.endpoint) && - dayjs().isAfter(new Date(expireTime - 20 * 1000)) - ) { - // 快过期了,renew - waitingForRenew = baseQuery( - { - url: "/token/renew", - method: "POST", - body: { - token, - refresh_token: refreshToken, - }, - }, - api, - extraOptions - ); - result = await waitingForRenew; - waitingForRenew = null; - if (result.data) { - // store the new token - api.dispatch(updateToken(result.data)); - // retry the initial query - result = await baseQuery(args, api, extraOptions); + if (waitingForRenew) { + await waitingForRenew; + } + // 先检查token是否过期,过期则renew + const { token, refreshToken, expireTime = new Date().getTime() } = api.getState().authData; + let result = null; + if (!whiteList.includes(api.endpoint) && dayjs().isAfter(new Date(expireTime - 20 * 1000))) { + // 快过期了,renew + waitingForRenew = baseQuery( + { + url: "/token/renew", + method: "POST", + body: { + token, + refresh_token: refreshToken } - } else { - result = await baseQuery(args, api, extraOptions); + }, + api, + extraOptions + ); + result = await waitingForRenew; + waitingForRenew = null; + if (result.data) { + // store the new token + api.dispatch(updateToken(result.data)); + // retry the initial query + result = await baseQuery(args, api, extraOptions); } - if (result?.error) { - console.log("api error", result.error, api.endpoint); - switch (result.error.originalStatus || result.error.status) { - case "FETCH_ERROR": - { - 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") { - api.dispatch(resetAuthData()); - 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; - default: - break; + } else { + result = await baseQuery(args, api, extraOptions); + } + if (result?.error) { + console.log("api error", result.error, api.endpoint); + switch (result.error.originalStatus || result.error.status) { + case "FETCH_ERROR": + { + 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") { + api.dispatch(resetAuthData()); + 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; + default: + break; } - return result; + } + return result; }; export default baseQueryWithTokenCheck; diff --git a/src/app/services/server.js b/src/app/services/server.js index 21925753..6cbe4831 100644 --- a/src/app/services/server.js +++ b/src/app/services/server.js @@ -4,204 +4,218 @@ import { updateInfo } from "../slices/server"; import baseQuery from "./base.query"; const defaultExpireDuration = 7 * 24 * 60 * 60; export const serverApi = createApi({ - reducerPath: "serverApi", - baseQuery, - endpoints: (builder) => ({ - getServer: builder.query({ - query: () => ({ url: `admin/system/organization` }), - transformResponse: (data) => { - data.logo = `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`; - return data; - }, - async onQueryStarted(data, { dispatch, queryFulfilled }) { - try { - const { data: server } = await queryFulfilled; - dispatch(updateInfo(server)); - } catch { - console.log("get server info error"); - } - }, - }), - getThirdPartySecret: builder.query({ - query: () => ({ - // headers: { - // "content-type": "text/plain", - // accept: "text/plain", - // }, - url: `/admin/system/third_party_secret`, - responseHandler: (response) => response.text(), - }), - keepUnusedDataFor: 0, - }), - updateThirdPartySecret: builder.mutation({ - query: () => ({ - url: `/admin/system/third_party_secret`, - method: "POST", - responseHandler: (response) => response.text(), - }), - }), - getMetrics: builder.query({ - query: () => ({ url: `/admin/system/metrics` }), - }), - getServerVersion: builder.query({ - query: () => ({ - headers: { - // "content-type": "text/plain", - accept: "text/plain", - }, - url: `/admin/system/version`, - responseHandler: (response) => response.text(), - }), - }), - getFirebaseConfig: builder.query({ - query: () => ({ url: `admin/fcm/config` }), - }), - getGoogleAuthConfig: builder.query({ - query: () => ({ url: `admin/google_auth/config` }), - }), - updateGoogleAuthConfig: builder.mutation({ - query: (data) => ({ - url: `admin/google_auth/config`, - method: "POST", - body: data, - }), - }), - getGithubAuthConfig: builder.query({ - query: () => ({ url: `admin/github_auth/config` }), - }), - updateGithubAuthConfig: builder.mutation({ - query: (data) => ({ - url: `admin/github_auth/config`, - method: "POST", - body: data, - }), - }), - sendTestEmail: builder.mutation({ - query: (data) => ({ - url: `/admin/system/send_mail`, - method: "POST", - body: data, - }), - }), - updateFirebaseConfig: builder.mutation({ - query: (data) => ({ - url: `admin/fcm/config`, - method: "POST", - body: data, - }), - }), - getAgoraConfig: builder.query({ - query: () => ({ url: `admin/agora/config` }), - }), - updateAgoraConfig: builder.mutation({ - query: (data) => ({ - url: `admin/agora/config`, - method: "POST", - body: data, - }), - }), - getSMTPConfig: builder.query({ - query: () => ({ url: `admin/smtp/config` }), - }), - getSMTPStatus: builder.query({ - query: () => ({ url: `/admin/smtp/enabled` }), - }), - updateSMTPConfig: builder.mutation({ - query: (data) => ({ - url: `admin/smtp/config`, - method: "POST", - body: data, - }), - }), - getLoginConfig: builder.query({ - query: () => ({ url: `admin/login/config` }), - }), - updateLoginConfig: builder.mutation({ - query: (data) => ({ - url: `admin/login/config`, - method: "POST", - body: data, - }), - }), - updateLogo: builder.mutation({ - query: (data) => ({ - headers: { - "content-type": "image/png", - }, - url: `admin/system/organization/logo`, - method: "POST", - body: data, - }), - async onQueryStarted(data, { dispatch, queryFulfilled }) { - try { - await queryFulfilled; - dispatch( - updateInfo({ - logo: `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`, - }) - ); - } catch { - console.log("update server logo error"); - } - }, - }), - createInviteLink: builder.query({ - 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(), - }), - transformResponse: (link) => { - // 替换掉域名 - const invite = new URL(link); - return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`; - }, - }), - updateServer: builder.mutation({ - query: (data) => ({ - url: `admin/system/organization`, - method: "POST", - body: data, - }), - async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { - const { name: prevName, description: prevDesc } = getState().server; - dispatch(updateInfo(data)); - try { - await queryFulfilled; - } catch { - dispatch(updateInfo({ name: prevName, description: prevDesc })); - } - }, - }), + reducerPath: "serverApi", + baseQuery, + endpoints: (builder) => ({ + getServer: builder.query({ + query: () => ({ url: `admin/system/organization` }), + transformResponse: (data) => { + data.logo = `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`; + return data; + }, + async onQueryStarted(data, { dispatch, queryFulfilled }) { + try { + const { data: server } = await queryFulfilled; + dispatch(updateInfo(server)); + } catch { + console.log("get server info error"); + } + } }), + getThirdPartySecret: builder.query({ + query: () => ({ + // headers: { + // "content-type": "text/plain", + // accept: "text/plain", + // }, + url: `/admin/system/third_party_secret`, + responseHandler: (response) => response.text() + }), + keepUnusedDataFor: 0 + }), + updateThirdPartySecret: builder.mutation({ + query: () => ({ + url: `/admin/system/third_party_secret`, + method: "POST", + responseHandler: (response) => response.text() + }) + }), + getMetrics: builder.query({ + query: () => ({ url: `/admin/system/metrics` }) + }), + getServerVersion: builder.query({ + query: () => ({ + headers: { + // "content-type": "text/plain", + accept: "text/plain" + }, + url: `/admin/system/version`, + responseHandler: (response) => response.text() + }) + }), + getFirebaseConfig: builder.query({ + query: () => ({ url: `admin/fcm/config` }) + }), + getGoogleAuthConfig: builder.query({ + query: () => ({ url: `admin/google_auth/config` }) + }), + updateGoogleAuthConfig: builder.mutation({ + query: (data) => ({ + url: `admin/google_auth/config`, + method: "POST", + body: data + }) + }), + getGithubAuthConfig: builder.query({ + query: () => ({ url: `admin/github_auth/config` }) + }), + updateGithubAuthConfig: builder.mutation({ + query: (data) => ({ + url: `admin/github_auth/config`, + method: "POST", + body: data + }) + }), + sendTestEmail: builder.mutation({ + query: (data) => ({ + url: `/admin/system/send_mail`, + method: "POST", + body: data + }) + }), + updateFirebaseConfig: builder.mutation({ + query: (data) => ({ + url: `admin/fcm/config`, + method: "POST", + body: data + }) + }), + getAgoraConfig: builder.query({ + query: () => ({ url: `admin/agora/config` }) + }), + updateAgoraConfig: builder.mutation({ + query: (data) => ({ + url: `admin/agora/config`, + method: "POST", + body: data + }) + }), + getSMTPConfig: builder.query({ + query: () => ({ url: `admin/smtp/config` }) + }), + getSMTPStatus: builder.query({ + query: () => ({ url: `/admin/smtp/enabled` }) + }), + updateSMTPConfig: builder.mutation({ + query: (data) => ({ + url: `admin/smtp/config`, + method: "POST", + body: data + }) + }), + getLoginConfig: builder.query({ + query: () => ({ url: `admin/login/config` }) + }), + updateLoginConfig: builder.mutation({ + query: (data) => ({ + url: `admin/login/config`, + method: "POST", + body: data + }) + }), + updateLogo: builder.mutation({ + query: (data) => ({ + headers: { + "content-type": "image/png" + }, + url: `admin/system/organization/logo`, + method: "POST", + body: data + }), + async onQueryStarted(data, { dispatch, queryFulfilled }) { + try { + await queryFulfilled; + dispatch( + updateInfo({ + logo: `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}` + }) + ); + } catch { + console.log("update server logo error"); + } + } + }), + createInviteLink: builder.query({ + 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() + }), + transformResponse: (link) => { + // 替换掉域名 + const invite = new URL(link); + return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`; + } + }), + updateServer: builder.mutation({ + query: (data) => ({ + url: `admin/system/organization`, + method: "POST", + body: data + }), + async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { + const { name: prevName, description: prevDesc } = getState().server; + dispatch(updateInfo(data)); + try { + await queryFulfilled; + } catch { + dispatch(updateInfo({ name: prevName, description: prevDesc })); + } + } + }), + createAdmin: builder.mutation({ + query: (data) => ({ + url: `/admin/system/createAdmin`, + method: "POST", + body: data + }) + }), + getInitialized: builder.query({ + query: () => ({ + url: `/admin/system/initialized` + }) + }) + }) }); export const { - useGetServerVersionQuery, - useGetGithubAuthConfigQuery, - useUpdateGithubAuthConfigMutation, - useGetGoogleAuthConfigQuery, - useUpdateGoogleAuthConfigMutation, - useGetSMTPStatusQuery, - useSendTestEmailMutation, - useUpdateFirebaseConfigMutation, - useGetFirebaseConfigQuery, - useGetLoginConfigQuery, - useUpdateLoginConfigMutation, - useGetSMTPConfigQuery, - useUpdateSMTPConfigMutation, - useGetAgoraConfigQuery, - useUpdateAgoraConfigMutation, - useGetServerQuery, - useGetMetricsQuery, - useLazyGetServerQuery, - useUpdateServerMutation, - useUpdateLogoMutation, - useCreateInviteLinkQuery, - useLazyCreateInviteLinkQuery, - useGetThirdPartySecretQuery, - useUpdateThirdPartySecretMutation, + useGetServerVersionQuery, + useGetGithubAuthConfigQuery, + useUpdateGithubAuthConfigMutation, + useGetGoogleAuthConfigQuery, + useUpdateGoogleAuthConfigMutation, + useGetSMTPStatusQuery, + useSendTestEmailMutation, + useUpdateFirebaseConfigMutation, + useGetFirebaseConfigQuery, + useGetLoginConfigQuery, + useUpdateLoginConfigMutation, + useGetSMTPConfigQuery, + useUpdateSMTPConfigMutation, + useGetAgoraConfigQuery, + useUpdateAgoraConfigMutation, + useGetServerQuery, + useGetMetricsQuery, + useLazyGetServerQuery, + useUpdateServerMutation, + useUpdateLogoMutation, + useCreateInviteLinkQuery, + useLazyCreateInviteLinkQuery, + useGetThirdPartySecretQuery, + useUpdateThirdPartySecretMutation, + useCreateAdminMutation, + useGetInitializedQuery } = serverApi; diff --git a/src/common/component/RequireInitialized.js b/src/common/component/RequireInitialized.js new file mode 100644 index 00000000..035b2d16 --- /dev/null +++ b/src/common/component/RequireInitialized.js @@ -0,0 +1,8 @@ +import { Navigate } from "react-router-dom"; +import { useGetInitializedQuery } from "../../app/services/server"; + +export default function RequireInitialized({ children, redirectTo = "/" }) { + const { data } = useGetInitializedQuery(); + console.log("initialized?", data); + return data === false ? : children; +} diff --git a/src/routes/index.js b/src/routes/index.js index 58d40f9e..b8a4d70d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -24,6 +24,7 @@ import SettingChannelPage from "./settingChannel"; import OnboardingPage from "./onboarding"; import toast from "react-hot-toast"; import ResourceManagement from "./resources"; +import RequireInitialized from "../common/component/RequireInitialized"; const PageRoutes = () => { const { @@ -49,9 +50,11 @@ const PageRoutes = () => { - - + + + + + } /> { - - + + + + + } > diff --git a/src/routes/onboarding/index.js b/src/routes/onboarding/index.js index 042e5346..e588b09b 100644 --- a/src/routes/onboarding/index.js +++ b/src/routes/onboarding/index.js @@ -11,11 +11,7 @@ import StyledOnboardingPage from "./styled"; export default function OnboardingPage() { const [step, setStep] = useState(0); const [data, setData] = useState({ - spaceName: "", - adminEmail: "", - adminPassword: "", - adminPassword2: "", - inviteRule: null + spaceName: "" }); const props = { step, setStep, data, setData }; diff --git a/src/routes/onboarding/steps/3-adminCredentials.js b/src/routes/onboarding/steps/3-adminCredentials.js index 3b581816..cadb2c20 100644 --- a/src/routes/onboarding/steps/3-adminCredentials.js +++ b/src/routes/onboarding/steps/3-adminCredentials.js @@ -1,7 +1,10 @@ +import { useEffect, useState } from "react"; import styled from "styled-components"; +import toast from "react-hot-toast"; import StyledInput from "../../../common/component/styled/Input"; import StyledButton from "../../../common/component/styled/Button"; -import toast from "react-hot-toast"; +import { useCreateAdminMutation } from "../../../app/services/server"; +import { useLoginMutation } from "../../../app/services/auth"; const StyledAdminCredentialsStep = styled.div` height: 100%; @@ -19,7 +22,34 @@ const StyledAdminCredentialsStep = styled.div` } `; -export default function AdminCredentialsStep({ step, setStep, data, setData }) { +export default function AdminCredentialsStep({ step, setStep }) { + const [ + createAdmin, + { isLoading: isSignUpLoading, isSuccess: isSignUpSuccess, error: signUpError } + ] = useCreateAdminMutation(); + const [login, { isLoading: isLoginLoading, isSuccess: isLoginSuccess, error: loginError }] = + useLoginMutation(); + + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirm, setConfirm] = useState(""); + + // Display error + useEffect(() => { + if (signUpError === undefined) return; + toast.error(`Failed to sign up: ${signUpError.data}`); + }, [signUpError]); + + useEffect(() => { + if (loginError === undefined) return; + toast.error(`Login failed: ${loginError.data}`); + }, [loginError]); + + // Increment `step` when both signing up and logging in have completed + useEffect(() => { + if (isSignUpSuccess && isLoginSuccess) setStep(step + 1); + }, [isSignUpSuccess, isLoginSuccess]); + return ( Now let’s set up your admin account @@ -27,56 +57,51 @@ export default function AdminCredentialsStep({ step, setStep, data, setData }) { - setData({ - ...data, - adminEmail: e.target.value - }) - } + value={email} + onChange={(e) => setEmail(e.target.value)} /> - setData({ - ...data, - adminPassword: e.target.value - }) - } + value={password} + onChange={(e) => setPassword(e.target.value)} /> - setData({ - ...data, - adminPassword2: e.target.value - }) - } + value={confirm} + onChange={(e) => setConfirm(e.target.value)} /> { + onClick={async () => { // Verification for admin credentials - if (data.adminEmail === "") { + if (email === "") { toast.error("Please enter admin email!"); return; - } else if (data.adminPassword === "") { + } else if (password === "") { toast.error("Please enter admin password!"); return; - } else if (data.adminPassword !== data.adminPassword2) { + } else if (password !== confirm) { toast.error("Two passwords do not match!"); return; } - setStep(step + 1); + await createAdmin({ + email, + name: "Admin", + password, + gender: 0 + }); + await login({ + email, + password, + type: "password" + }); }} > - Sign Up + {!(isSignUpLoading || isLoginLoading) ? "Sign Up" : "..."} ); diff --git a/src/routes/onboarding/steps/4-inviteRule.js b/src/routes/onboarding/steps/4-inviteRule.js index 7738f25c..9b1aa36f 100644 --- a/src/routes/onboarding/steps/4-inviteRule.js +++ b/src/routes/onboarding/steps/4-inviteRule.js @@ -1,6 +1,9 @@ +import { useState, useEffect } from "react"; +import toast from "react-hot-toast"; import styled from "styled-components"; import StyledRadio from "../../../common/component/styled/Radio"; import StyledButton from "../../../common/component/styled/Button"; +import { useGetLoginConfigQuery, useUpdateLoginConfigMutation } from "../../../app/services/server"; const StyledInviteRuleStep = styled.div` height: 100%; @@ -14,22 +17,39 @@ const StyledInviteRuleStep = styled.div` } `; -export default function InviteRuleStep({ step, setStep, data, setData }) { +export default function InviteRuleStep({ step, setStep }) { + const { data: loginConfig } = useGetLoginConfigQuery(); + const [updateLoginConfig, { isSuccess, error }] = useUpdateLoginConfigMutation(); + + const [value, setValue] = useState(null); + + // Display error + useEffect(() => { + if (error === undefined) return; + toast.error(`Failed to update invitation rule: ${error.data}`); + }, [error]); + + // Increment `step` when updating has completed + useEffect(() => { + if (isSuccess) setStep(step + 1); + }, [isSuccess]); + return ( Last step: invite others! Firstly, who can sign up to this server? { - setData({ - ...data, - inviteRule: v - }); - setTimeout(() => { - setStep(step + 1); - }, 750); + setValue(v); + if (loginConfig !== undefined) { + const whoCanSignUp = ["EveryOne", "InvitationOnly"][v]; + updateLoginConfig({ + ...loginConfig, + who_can_sign_up: whoCanSignUp + }); + } }} /> setStep(step + 1)}>