diff --git a/config/.env.development b/config/.env.development index 9e6a0018f0e..870710b6204 100644 --- a/config/.env.development +++ b/config/.env.development @@ -2,6 +2,7 @@ NETLESS_APP_IDENTIFIER=n9q1oBxDEeyuBMn1qc0iFw/fLgNSEvdwKjlig AGORA_APP_ID=931b86d6781e49a2a255db4ce6e8e804 GITHUB_CLIENT_ID=9821657775fbc74773f1 WECHAT_APP_ID=wx1133c2153a45e9b8 +AGORA_OAUTH_CLIENT_ID=flat-dev CLOUD_STORAGE_OSS_ALIBABA_ACCESS_KEY=LTAI5t9Gb6tzQzzLmB6cTVf7 CLOUD_STORAGE_OSS_ALIBABA_BUCKET=flat-storage diff --git a/config/.env.production b/config/.env.production index 9dfbbb1c5f1..647907399da 100644 --- a/config/.env.production +++ b/config/.env.production @@ -2,6 +2,7 @@ NETLESS_APP_IDENTIFIER=cFjxAJjiEeuUQ0211QCRBw/mO9uJB_DiCIqug AGORA_APP_ID=931b86d6781e49a2a255db4ce6e8e804 GITHUB_CLIENT_ID=71a29285a437998bdfe0 WECHAT_APP_ID=wx96d522d69d384cce +AGORA_OAUTH_CLIENT_ID=flat CLOUD_STORAGE_OSS_ALIBABA_ACCESS_KEY=LTAI5t9Gb6tzQzzLmB6cTVf7 CLOUD_STORAGE_OSS_ALIBABA_BUCKET=flat-storage diff --git a/desktop/renderer-app/src/pages/LoginPage/index.tsx b/desktop/renderer-app/src/pages/LoginPage/index.tsx index 9482e59755f..1087b4679f5 100644 --- a/desktop/renderer-app/src/pages/LoginPage/index.tsx +++ b/desktop/renderer-app/src/pages/LoginPage/index.tsx @@ -1,10 +1,10 @@ import "./index.less"; -import React, { useContext, useEffect, useRef, useState } from "react"; +import React, { useCallback, useContext, useEffect, useRef, useState } from "react"; import { constants } from "flat-types"; import { observer } from "mobx-react-lite"; import { ipcAsyncByMainWindow, ipcSyncByApp } from "../../utils/ipc"; -import { LoginChannelType, LoginPanel } from "flat-components"; +import { LoginPanel, LoginButton, LoginButtonProviderType } from "flat-components"; import { LoginDisposer } from "./utils"; import { githubLogin } from "./githubLogin"; import { RouteNameType, usePushHistory } from "../../utils/routes"; @@ -12,7 +12,6 @@ import { GlobalStoreContext } from "../../components/StoreProvider"; import { AppUpgradeModal, AppUpgradeModalProps } from "../../components/AppUpgradeModal"; import { runtime } from "../../utils/runtime"; import { useSafePromise } from "../../utils/hooks/lifecycle"; -import { WeChatLogin } from "./WeChatLogin"; import { useTranslation } from "react-i18next"; import { PRIVACY_URL_EN, @@ -20,6 +19,8 @@ import { SERVICE_URL_EN, SERVICE_URL_CN, } from "../../constants/process"; +import { message } from "antd"; +import WeChatLogin from "./WeChatLogin"; export const LoginPage = observer(function LoginPage() { const { i18n } = useTranslation(); @@ -27,6 +28,9 @@ export const LoginPage = observer(function LoginPage() { const globalStore = useContext(GlobalStoreContext); const loginDisposer = useRef(); const [updateInfo, setUpdateInfo] = useState(null); + const [isWeChatLogin, setWeChatLogin] = useState(false); + const [agreementChecked, setAgreementChecked] = useState(false); + const sp = useSafePromise(); useEffect(() => { @@ -61,35 +65,76 @@ export const LoginPage = observer(function LoginPage() { }); }, [sp]); - const handleLogin = (loginChannel: LoginChannelType): React.ReactElement | undefined => { - if (loginDisposer.current) { - loginDisposer.current(); - loginDisposer.current = void 0; - } - - switch (loginChannel) { - case "github": { - loginDisposer.current = githubLogin(authData => { - globalStore.updateUserInfo(authData); - pushHistory(RouteNameType.HomePage); - }); - return; - } - case "wechat": { - return ; + const handleLogin = useCallback( + (loginChannel: LoginButtonProviderType) => { + if (loginDisposer.current) { + loginDisposer.current(); + loginDisposer.current = void 0; } - default: { - return; + + const doLogin = (loginChannel: LoginButtonProviderType): void => { + switch (loginChannel) { + case "github": { + loginDisposer.current = githubLogin(authData => { + globalStore.updateUserInfo(authData); + pushHistory(RouteNameType.HomePage); + }); + return; + } + case "wechat": { + setWeChatLogin(true); + return; + } + default: { + return; + } + } + }; + if (agreementChecked) { + doLogin(loginChannel); + } else { + void message.info(i18n.t("agree-terms")); } - } - }; + }, + [agreementChecked, globalStore, i18n, pushHistory], + ); const privacyURL = i18n.language.startsWith("zh") ? PRIVACY_URL_CN : PRIVACY_URL_EN; const serviceURL = i18n.language.startsWith("zh") ? SERVICE_URL_CN : SERVICE_URL_EN; + function renderButtonList(): React.ReactNode { + return ( + <> + + + + ); + } + + function renderQRCode(): React.ReactNode { + return ; + } + return (
- + setAgreementChecked(!agreementChecked)} + handleHideQRCode={() => setWeChatLogin(false)} + privacyURL={privacyURL} + renderButtonList={renderButtonList} + renderQRCode={renderQRCode} + serviceURL={serviceURL} + showQRCode={isWeChatLogin} + /> setUpdateInfo(null)} />
); diff --git a/packages/flat-components/src/components/LoginPage/LoginButton/LoginButton.stories.tsx b/packages/flat-components/src/components/LoginPage/LoginButton/LoginButton.stories.tsx new file mode 100644 index 00000000000..bf8d6cac191 --- /dev/null +++ b/packages/flat-components/src/components/LoginPage/LoginButton/LoginButton.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, Story } from "@storybook/react"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { LoginButton, LoginButtonProviderType, LoginButtonProps } from ".."; +import { message } from "antd"; + +const storyMeta: Meta = { + title: "LoginPage/LoginButton", + component: LoginButton, +}; + +export default storyMeta; + +export const Overview: Story = () => { + const { i18n } = useTranslation(); + + const handleLogin = (type: LoginButtonProviderType): void => { + void message.info(type); + }; + + return ( + <> + + + + ); +}; diff --git a/packages/flat-components/src/components/LoginPage/LoginButton/icons/agora.svg b/packages/flat-components/src/components/LoginPage/LoginButton/icons/agora.svg new file mode 100644 index 00000000000..999295af554 --- /dev/null +++ b/packages/flat-components/src/components/LoginPage/LoginButton/icons/agora.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/flat-components/src/components/LoginPage/LoginChannel/icons/github.svg b/packages/flat-components/src/components/LoginPage/LoginButton/icons/github.svg similarity index 100% rename from packages/flat-components/src/components/LoginPage/LoginChannel/icons/github.svg rename to packages/flat-components/src/components/LoginPage/LoginButton/icons/github.svg diff --git a/packages/flat-components/src/components/LoginPage/LoginChannel/icons/google.svg b/packages/flat-components/src/components/LoginPage/LoginButton/icons/google.svg similarity index 100% rename from packages/flat-components/src/components/LoginPage/LoginChannel/icons/google.svg rename to packages/flat-components/src/components/LoginPage/LoginButton/icons/google.svg diff --git a/packages/flat-components/src/components/LoginPage/LoginChannel/icons/wechat.svg b/packages/flat-components/src/components/LoginPage/LoginButton/icons/wechat.svg similarity index 100% rename from packages/flat-components/src/components/LoginPage/LoginChannel/icons/wechat.svg rename to packages/flat-components/src/components/LoginPage/LoginButton/icons/wechat.svg diff --git a/packages/flat-components/src/components/LoginPage/LoginChannel/index.less b/packages/flat-components/src/components/LoginPage/LoginButton/index.less similarity index 62% rename from packages/flat-components/src/components/LoginPage/LoginChannel/index.less rename to packages/flat-components/src/components/LoginPage/LoginButton/index.less index 9fc4f5a655c..d8c37505c38 100644 --- a/packages/flat-components/src/components/LoginPage/LoginChannel/index.less +++ b/packages/flat-components/src/components/LoginPage/LoginButton/index.less @@ -1,9 +1,11 @@ -.login-channel-container { - display: flex; - flex-direction: column; - align-items: center; +.login-channel-lg { - > .ant-btn { + + &.ant-btn { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; width: 272px; height: 44px; color: #fff; @@ -18,6 +20,21 @@ margin-right: 3px; } } + + +.login-channel-agora { + &.ant-btn { + background: #089CFD; + + &:hover { + background: #45B4FD; + } + &:active { + background: #0675BE; + } + } +} + .login-channel-wechat { margin-bottom: 12px; @@ -34,6 +51,8 @@ } .login-channel-github { + margin-bottom: 12px; + &.ant-btn { background: #24292e; diff --git a/packages/flat-components/src/components/LoginPage/LoginButton/index.tsx b/packages/flat-components/src/components/LoginPage/LoginButton/index.tsx new file mode 100644 index 00000000000..e1b84be2966 --- /dev/null +++ b/packages/flat-components/src/components/LoginPage/LoginButton/index.tsx @@ -0,0 +1,32 @@ +import { Button } from "antd"; +import React from "react"; +import wechatSVG from "./icons/wechat.svg"; +import agoraSVG from "./icons/agora.svg"; +import githubSVG from "./icons/github.svg"; +import "./index.less"; + +export type LoginButtonProviderType = "wechat" | "github" | "agora"; + +export type LoginButtonProps = { + provider: LoginButtonProviderType; + onLogin: (type: LoginButtonProviderType) => void; + text: string; +}; + +const svgDict: Record = { + wechat: wechatSVG, + agora: agoraSVG, + github: githubSVG, +}; + +export const LoginButton: React.FC = ({ provider, onLogin, text }) => { + return ( + + ); +}; diff --git a/packages/flat-components/src/components/LoginPage/LoginChannel/LoginChannel.stories.tsx b/packages/flat-components/src/components/LoginPage/LoginChannel/LoginChannel.stories.tsx deleted file mode 100644 index 003f7b0a35b..00000000000 --- a/packages/flat-components/src/components/LoginPage/LoginChannel/LoginChannel.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Meta, Story } from "@storybook/react"; -import React from "react"; -import { LoginChannel, LoginChannelProps } from "../LoginChannel"; - -const storyMeta: Meta = { - title: "LoginPage/LoginChannel", - component: LoginChannel, -}; - -export default storyMeta; - -export const Overview: Story = args => ( -
- -
-); -Overview.args = {}; diff --git a/packages/flat-components/src/components/LoginPage/LoginChannel/index.tsx b/packages/flat-components/src/components/LoginPage/LoginChannel/index.tsx deleted file mode 100644 index a03b61ed5af..00000000000 --- a/packages/flat-components/src/components/LoginPage/LoginChannel/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import wechatSVG from "./icons/wechat.svg"; -import githubSVG from "./icons/github.svg"; -import "./index.less"; - -import React from "react"; -import { Button } from "antd"; -import { useTranslation } from "react-i18next"; - -export type LoginChannelType = "wechat" | "github"; - -export interface LoginChannelProps { - onLogin: (loginChannel: LoginChannelType) => void; -} - -export const LoginChannel: React.FC = ({ onLogin }) => { - const { t } = useTranslation(); - return ( -
- - -
- ); -}; diff --git a/packages/flat-components/src/components/LoginPage/LoginContent/LoginContent.stories.tsx b/packages/flat-components/src/components/LoginPage/LoginContent/LoginContent.stories.tsx index d6a1e164a61..047dd012623 100644 --- a/packages/flat-components/src/components/LoginPage/LoginContent/LoginContent.stories.tsx +++ b/packages/flat-components/src/components/LoginPage/LoginContent/LoginContent.stories.tsx @@ -1,6 +1,9 @@ import { Meta, Story } from "@storybook/react"; -import React from "react"; -import { LoginContent, LoginContentProps } from "."; +import React, { useState, useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { LoginContent, LoginContentProps } from "./index"; +import { message } from "antd"; +import { LoginButton } from ".."; const storyMeta: Meta = { title: "LoginPage/LoginContent", @@ -9,8 +12,41 @@ const storyMeta: Meta = { export default storyMeta; -export const Overview: Story = args => ( -
- -
-); +export const Overview: Story = () => { + const { i18n } = useTranslation(); + const [showQRCode, updateShowQRCode] = useState(false); + + const [agreement, updateAgreement] = useState(false); + + const handleClickAgreement = (): void => { + updateAgreement(!agreement); + }; + + const handleLogin = useCallback(() => { + agreement === false && void message.info(i18n.t("agree-terms")); + }, [agreement, i18n]); + + + const { t } = useTranslation(); + + function renderButtonList(): React.ReactNode { + return ( + <> + + + + ); + } + + return ( +
+ updateAgreement(!agreement)} + handleHideQRCode={handleHideQRCode} + renderButtonList={renderButtonList} + showQRCode={showQRCode} + /> +
+ ); +}; diff --git a/packages/flat-components/src/components/LoginPage/icons/qr-code.svg b/packages/flat-components/src/components/LoginPage/LoginContent/icons/qr-code.svg similarity index 100% rename from packages/flat-components/src/components/LoginPage/icons/qr-code.svg rename to packages/flat-components/src/components/LoginPage/LoginContent/icons/qr-code.svg diff --git a/packages/flat-components/src/components/LoginPage/LoginContent/index.less b/packages/flat-components/src/components/LoginPage/LoginContent/index.less index c2c517d94d4..c7c2a3b9727 100644 --- a/packages/flat-components/src/components/LoginPage/LoginContent/index.less +++ b/packages/flat-components/src/components/LoginPage/LoginContent/index.less @@ -32,6 +32,9 @@ width: 21%; margin: 0 auto; margin-bottom: 24px; + display: flex; + align-items: center; + flex-direction: column; } .login-content-agreement { diff --git a/packages/flat-components/src/components/LoginPage/LoginContent/index.tsx b/packages/flat-components/src/components/LoginPage/LoginContent/index.tsx index 860ef5a3a51..72b8bb4338f 100644 --- a/packages/flat-components/src/components/LoginPage/LoginContent/index.tsx +++ b/packages/flat-components/src/components/LoginPage/LoginContent/index.tsx @@ -1,79 +1,80 @@ /* eslint react/jsx-no-target-blank: off */ -import logoSVG from "./icons/logo.svg"; import "./index.less"; -import React, { useState } from "react"; -import { LoginChannel, LoginChannelType } from "../LoginChannel"; +import logoSVG from "./icons/logo.svg"; +import QRCodeSVG from "./icons/qr-code.svg"; +import React from "react"; import { CSSTransition, SwitchTransition } from "react-transition-group"; -import { message, Checkbox } from "antd"; import { useTranslation } from "react-i18next"; +import { Checkbox } from "antd"; export interface LoginContentProps { - onLogin: (loginChannel: LoginChannelType) => React.ReactElement | undefined; + showQRCode: boolean; + handleHideQRCode: () => void; + agreementChecked: boolean; + handleClickAgreement: () => void; + renderButtonList: () => React.ReactNode; + renderQRCode?: () => React.ReactNode; privacyURL?: string; serviceURL?: string; } -export const LoginContent: React.FC = ({ onLogin, privacyURL, serviceURL }) => { +export const LoginContent: React.FC = ({ + showQRCode, + handleHideQRCode, + agreementChecked, + handleClickAgreement, + privacyURL, + serviceURL, + renderButtonList, + renderQRCode = () => , +}) => { const { t } = useTranslation(); - const [inPageLogin, setInPageLogin] = useState(); - const [isChecked, setIsChecked] = useState(false); - return ( - {inPageLogin ? ( -
+ - ) : ( -
-
- - {t("welcome-to-Flat")} - - {t("online-interaction-to-synchronize-ideas")} - -
-
- { - if (isChecked) { - setInPageLogin(onLogin(loginChannel)); - } else { - void message.info(t("agree-terms")); - } - }} - /> -
-
- setIsChecked(!isChecked)} - > - {t("have-read-and-agree")}{" "} - - {t("privacy-agreement")} - {" "} - {t("and")}{" "} - - {t("service-policy")} - - -
-
- )} + ) : ( + <> +
+ + {t("welcome-to-Flat")} + + {t("online-interaction-to-synchronize-ideas")} + +
+
{renderButtonList()}
+
+ + {t("have-read-and-agree")}{" "} + + {t("privacy-agreement")} + {" "} + {t("and")}{" "} + + {t("service-policy")} + + +
+ + )} +
); diff --git a/packages/flat-components/src/components/LoginPage/LoginPanel.stories.tsx b/packages/flat-components/src/components/LoginPage/LoginPanel.stories.tsx index b6b11c6c158..e0465983c33 100644 --- a/packages/flat-components/src/components/LoginPage/LoginPanel.stories.tsx +++ b/packages/flat-components/src/components/LoginPage/LoginPanel.stories.tsx @@ -1,9 +1,8 @@ -import QRCodeSVG from "./icons/qr-code.svg"; - import { Meta, Story } from "@storybook/react"; -import { Modal } from "antd"; -import React from "react"; -import { LoginChannelType, LoginPanel, LoginPanelProps } from "."; +import { message, Modal } from "antd"; +import React, { useState } from "react"; +import { LoginButton, LoginButtonProviderType, LoginPanel, LoginPanelProps } from "."; +import { useTranslation } from "react-i18next"; const storyMeta: Meta = { title: "LoginPage/LoginPanel", @@ -31,26 +30,64 @@ const storyMeta: Meta = { export default storyMeta; -const handleLogin = (loginChannel: LoginChannelType): React.ReactElement | undefined => { - switch (loginChannel) { - case "wechat": { - return ; - } - case "github": { - Modal.info({ content: "This is Github Login" }); - return; - } - default: { - return; - } +export const PlayableExample: Story = () => { + const [isWeChatLogin, setWeChatLogin] = useState(false); + + const handleHideQRCode = (): void => { + setWeChatLogin(!isWeChatLogin); + }; + + const [agreement, setAgreement] = useState(false); + + const { i18n } = useTranslation(); + + function renderButtonList(): React.ReactNode { + const handleLogin = (loginChannel: LoginButtonProviderType): void => { + if (!agreement) { + void message.info(i18n.t("agree-terms")); + return; + } + + switch (loginChannel) { + case "wechat": { + setWeChatLogin(true); + break; + } + case "github": { + Modal.info("need i18n"); + break; + } + default: { + break; + } + } + }; + + return ( + <> + + + + ); } -}; -export const PlayableExample: Story = args => ( -
- -
-); -PlayableExample.args = { - onLogin: handleLogin, + return ( +
+ setAgreement(!agreement)} + handleHideQRCode={handleHideQRCode} + renderButtonList={renderButtonList} + showQRCode={isWeChatLogin} + /> +
+ ); }; diff --git a/packages/flat-components/src/components/LoginPage/index.tsx b/packages/flat-components/src/components/LoginPage/index.tsx index 96f937695e9..ffc28a8e10b 100644 --- a/packages/flat-components/src/components/LoginPage/index.tsx +++ b/packages/flat-components/src/components/LoginPage/index.tsx @@ -5,15 +5,11 @@ import bgBottomLeftSVG from "./icons/bg-bottom-left.svg"; import "./index.less"; import React from "react"; -import { LoginContent, LoginContentProps } from "./LoginContent"; +import { LoginContent, LoginContentProps } from "./LoginContent/index"; -export type { LoginChannelType } from "./LoginChannel"; +export * from "./LoginButton"; -export interface LoginPanelProps { - onLogin: LoginContentProps["onLogin"]; - privacyURL?: string; - serviceURL?: string; -} +export interface LoginPanelProps extends LoginContentProps {} export const LoginPanel: React.FC = props => { return ( diff --git a/packages/flat-components/src/theme/antd.mod.stories.tsx b/packages/flat-components/src/theme/antd.mod.stories.tsx index ce9c074f2d4..6b40adafe55 100644 --- a/packages/flat-components/src/theme/antd.mod.stories.tsx +++ b/packages/flat-components/src/theme/antd.mod.stories.tsx @@ -27,15 +27,15 @@ export const Buttons: Story = () => { Dashed Text Link - + Danger - + Danger - }> - }> - }> + } shape="circle" type="primary"> + } shape="circle" type="default"> + } shape="circle" type="dashed">
Ghost
diff --git a/packages/flat-i18n/locales/en.json b/packages/flat-i18n/locales/en.json index 5cd62300779..c6b99adf4b3 100644 --- a/packages/flat-i18n/locales/en.json +++ b/packages/flat-i18n/locales/en.json @@ -110,6 +110,7 @@ "copy": "Copy", "login-github": "Sign In with GitHub", "login-wechat": "Sign In with WeChat", + "login-agora": "Sign In with Agora", "agree-terms": "Please agree to the terms of service first", "and": "and", "have-read-and-agree": "Have read and agree", diff --git a/packages/flat-i18n/locales/zh-CN.json b/packages/flat-i18n/locales/zh-CN.json index b01941506b9..37174419735 100644 --- a/packages/flat-i18n/locales/zh-CN.json +++ b/packages/flat-i18n/locales/zh-CN.json @@ -111,6 +111,7 @@ "copy": "复制", "login-wechat": "微信登录", "login-github": "GitHub 登录", + "login-agora": "声网登录", "welcome-to-Flat": "欢迎使用 Flat", "online-interaction-to-synchronize-ideas": "在线互动,让想法同步", "agree-terms": "请先同意服务条款", diff --git a/web/flat-web/src/api-middleware/flatServer/constants.ts b/web/flat-web/src/api-middleware/flatServer/constants.ts index 6791d4fb6f4..87277d46c80 100644 --- a/web/flat-web/src/api-middleware/flatServer/constants.ts +++ b/web/flat-web/src/api-middleware/flatServer/constants.ts @@ -7,6 +7,7 @@ export const FLAT_SERVER_VERSIONS = { } as const; export const FLAT_SERVER_LOGIN = { + AGORA_CALLBACK: `${FLAT_SERVER_VERSIONS.V1}/login/agora/callback`, GITHUB_CALLBACK: `${FLAT_SERVER_VERSIONS.V1}/login/github/callback?platform=web`, WECHAT_CALLBACK: `${FLAT_SERVER_VERSIONS.V1}/login/weChat/web/callback`, } as const; diff --git a/web/flat-web/src/constants/process.ts b/web/flat-web/src/constants/process.ts index c55b69d308b..50990be2927 100644 --- a/web/flat-web/src/constants/process.ts +++ b/web/flat-web/src/constants/process.ts @@ -16,6 +16,10 @@ export const AGORA = Object.freeze({ APP_ID: process.env.AGORA_APP_ID, }); +export const AGORA_OAUTH = Object.freeze({ + CLIENT_ID: process.env.AGORA_OAUTH_CLIENT_ID, +}); + export const WECHAT = Object.freeze({ APP_ID: process.env.WECHAT_APP_ID, }); diff --git a/web/flat-web/src/pages/LoginPage/agoraLogin.ts b/web/flat-web/src/pages/LoginPage/agoraLogin.ts new file mode 100644 index 00000000000..e6d04d02e62 --- /dev/null +++ b/web/flat-web/src/pages/LoginPage/agoraLogin.ts @@ -0,0 +1,47 @@ +import { setAuthUUID, loginProcess } from "../../api-middleware/flatServer"; +import { v4 as uuidv4 } from "uuid"; +import { LoginExecutor } from "./utils"; +import { errorTips } from "../../components/Tips/ErrorTips"; +import { FLAT_SERVER_LOGIN } from "../../api-middleware/flatServer/constants"; +import { AGORA_OAUTH } from "../../constants/process"; + +export const agoraLogin: LoginExecutor = onSuccess => { + let timer = NaN; + const authUUID = uuidv4(); + + function getAgoraURL(authUUID: string): string { + const redirectURL = encodeURIComponent(FLAT_SERVER_LOGIN.AGORA_CALLBACK); + return `http://sso2.agora.io/api/v0/oauth/authorize?response_type=code&client_id=${AGORA_OAUTH.CLIENT_ID}&redirect_uri=${redirectURL}&scope=basic_info&state=${authUUID}`; + } + + void (async () => { + try { + await setAuthUUID(authUUID); + } catch (err) { + errorTips(err); + } + + void window.open(getAgoraURL(authUUID)); + + const agoraLoginProcessRequest = async (): Promise => { + try { + const data = await loginProcess(authUUID); + + if (!data.name) { + timer = window.setTimeout(agoraLoginProcessRequest, 2000); + return; + } + + onSuccess(data); + } catch (err) { + errorTips(err); + } + }; + + void agoraLoginProcessRequest(); + })(); + + return () => { + window.clearTimeout(timer); + }; +}; diff --git a/web/flat-web/src/pages/LoginPage/index.tsx b/web/flat-web/src/pages/LoginPage/index.tsx index f5449e46a24..ec55a5d06f4 100644 --- a/web/flat-web/src/pages/LoginPage/index.tsx +++ b/web/flat-web/src/pages/LoginPage/index.tsx @@ -1,24 +1,31 @@ import "./style.less"; -import React, { useContext, useEffect, useRef } from "react"; +import React, { useCallback, useContext, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; -import { LoginChannelType, LoginPanel } from "flat-components"; +import { LoginButton, LoginButtonProviderType, LoginPanel } from "flat-components"; import { LoginDisposer } from "./utils"; import { githubLogin } from "./githubLogin"; -import { RouteNameType, usePushHistory } from "../../utils/routes"; +import { RouteNameType, usePushHistory, useURLParams } from "../../utils/routes"; import { GlobalStoreContext } from "../../components/StoreProvider"; import { WeChatLogin } from "./WeChatLogin"; import { joinRoomHandler } from "../utils/join-room-handler"; import { PRIVACY_URL, PRIVACY_URL_CN, SERVICE_URL, SERVICE_URL_CN } from "../../constants/process"; import { useTranslation } from "react-i18next"; +import { agoraLogin } from "./agoraLogin"; +import { message } from "antd"; export const LoginPage = observer(function LoginPage() { const { i18n } = useTranslation(); const pushHistory = usePushHistory(); const globalStore = useContext(GlobalStoreContext); const loginDisposer = useRef(); + + const [isWeChatLogin, setWeChatLogin] = useState(false); + const [agreement, setAgreement] = useState(false); const roomUUID = sessionStorage.getItem("roomUUID"); + const urlParams = useURLParams(); + useEffect(() => { return () => { if (loginDisposer.current) { @@ -29,43 +36,110 @@ export const LoginPage = observer(function LoginPage() { }; }, []); - const handleLogin = (loginChannel: LoginChannelType): React.ReactElement | undefined => { - if (loginDisposer.current) { - loginDisposer.current(); - loginDisposer.current = void 0; - } - - switch (loginChannel) { - case "github": { - loginDisposer.current = githubLogin(async authData => { - globalStore.updateUserInfo(authData); - if (roomUUID) { - if (globalStore.isTurnOffDeviceTest) { - await joinRoomHandler(roomUUID, pushHistory); - } else { - pushHistory(RouteNameType.DevicesTestPage, { roomUUID }); - } - } else { - pushHistory(RouteNameType.HomePage); - } - }); - return; - } - case "wechat": { - return ; + const handleLogin = useCallback( + (loginChannel: LoginButtonProviderType) => { + if (loginDisposer.current) { + loginDisposer.current(); + loginDisposer.current = void 0; } - default: { - return; + const doLogin = (loginChannel: LoginButtonProviderType): void => { + switch (loginChannel) { + case "agora": { + loginDisposer.current = agoraLogin(async authData => { + globalStore.updateUserInfo(authData); + if (!roomUUID) { + pushHistory(RouteNameType.HomePage); + return; + } + if (globalStore.isTurnOffDeviceTest) { + await joinRoomHandler(roomUUID, pushHistory); + } else { + pushHistory(RouteNameType.DevicesTestPage, { roomUUID }); + } + }); + return; + } + case "github": { + loginDisposer.current = githubLogin(async authData => { + globalStore.updateUserInfo(authData); + if (!roomUUID) { + pushHistory(RouteNameType.HomePage); + return; + } + if (globalStore.isTurnOffDeviceTest) { + await joinRoomHandler(roomUUID, pushHistory); + } else { + pushHistory(RouteNameType.DevicesTestPage, { roomUUID }); + } + }); + return; + } + case "wechat": { + setWeChatLogin(true); + return; + } + default: { + return; + } + } + }; + if (agreement) { + doLogin(loginChannel); + } else { + void message.info(i18n.t("agree-terms")); } - } - }; + }, + [agreement, globalStore, i18n, pushHistory, roomUUID], + ); const privacyURL = i18n.language.startsWith("zh") ? PRIVACY_URL_CN : PRIVACY_URL; const serviceURL = i18n.language.startsWith("zh") ? SERVICE_URL_CN : SERVICE_URL; + function renderButtonList({ utm_source }: Record): React.ReactNode { + if (utm_source === "agora") { + return ( + <> + + + ); + } else { + return ( + <> + + + + ); + } + } + + function renderQRCode(): React.ReactNode { + return ; + } + return (
- + setAgreement(!agreement)} + handleHideQRCode={() => setWeChatLogin(false)} + privacyURL={privacyURL} + renderButtonList={() => renderButtonList(urlParams)} + renderQRCode={renderQRCode} + serviceURL={serviceURL} + showQRCode={isWeChatLogin} + />
); }); diff --git a/web/flat-web/src/utils/routes.ts b/web/flat-web/src/utils/routes.ts index 1d5a58058f5..4569614d415 100644 --- a/web/flat-web/src/utils/routes.ts +++ b/web/flat-web/src/utils/routes.ts @@ -1,6 +1,6 @@ import { routeConfig, RouteConfig, RouteNameType, ExtraRouteConfig } from "../route-config"; import { generatePath, useHistory } from "react-router-dom"; -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; export { RouteNameType } from "../route-config"; @@ -77,3 +77,21 @@ export function useReplaceHistory(): ( return pushHistory; } + +/** + * Get url parameters + * This facility not involves react-router, so you can use it simply. + */ +export function useURLParams(): Record { + const urlSearchParams = new URLSearchParams(window.location.search); + + const params = useMemo(() => { + const res: Record = {}; + for (const [key, value] of urlSearchParams.entries()) { + res[key] = value; + } + return res; + }, [urlSearchParams]); + + return params; +} diff --git a/web/flat-web/tsconfig.json b/web/flat-web/tsconfig.json index c26a2597e3a..2e6c85a16dd 100644 --- a/web/flat-web/tsconfig.json +++ b/web/flat-web/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "target": "ESNext", "jsx": "react", - "lib": ["DOM", "ESNext"], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "module": "ESNext", "baseUrl": ".", "paths": { diff --git a/web/flat-web/typings/global.d.ts b/web/flat-web/typings/global.d.ts index 4817d42aa2e..30bebce73b8 100644 --- a/web/flat-web/typings/global.d.ts +++ b/web/flat-web/typings/global.d.ts @@ -19,6 +19,8 @@ declare namespace NodeJS { AGORA_APP_ID: string; + AGORA_OAUTH_CLIENT_ID: string; + GITHUB_CLIENT_ID: string; WECHAT_APP_ID: string;