Skip to content

Commit

Permalink
feat(flat-pages): add password login & register (#1984)
Browse files Browse the repository at this point in the history
* feat(flat-components): add login with password

feat(flat-components): add reset password page

feat(flat-pages): add sign up page

refactor(flat-pages): refactor login logic to useLogin

refactor(flat-pages): refactor QRCode & BindingPhone components

refactor(flat-pages): replace type with enum

refactor(flat-pages): merge register to login page & optimize login logic

refactor(storybook): add storybook for components

refactor(storybook): add storybook for components

refactor(library): remove useless types

* update lockfile

---------

Co-authored-by: hyrious <hyrious@outlook.com>
  • Loading branch information
syt-honey and hyrious committed Aug 4, 2023
1 parent 55489ec commit 3908376
Show file tree
Hide file tree
Showing 50 changed files with 2,079 additions and 997 deletions.
2 changes: 2 additions & 0 deletions config/CN/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ CLOUD_STORAGE_OSS_ALIBABA_REGION=oss-cn-hangzhou
CLOUD_STORAGE_DOMAIN=https://flat-storage-[region].whiteboard.agora.io

FLAT_SERVER_DOMAIN=flat-api-dev.whiteboard.agora.io
FLAT_SERVER_DOMAIN_US=flat-api-dev-sg.whiteboard.agora.io

UPDATE_DOMAIN=https://flat-storage.oss-cn-hangzhou.aliyuncs.com/versions
FLAT_WEB_DOMAIN=flat-web-dev.whiteboard.agora.io

Expand Down
2 changes: 2 additions & 0 deletions config/US/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ CLOUD_STORAGE_OSS_ALIBABA_REGION=oss-cn-hangzhou
CLOUD_STORAGE_DOMAIN=https://flat-storage-[region].whiteboard.agora.io

FLAT_SERVER_DOMAIN=api-dev.flat.agora.io
FLAT_SERVER_DOMAIN_US=flat-api-dev-sg.whiteboard.agora.io

UPDATE_DOMAIN=https://flat-storage-us-sv.oss-us-west-1.aliyuncs.com/versions
FLAT_WEB_DOMAIN=web-dev.flat.agora.io

Expand Down
1 change: 1 addition & 0 deletions cspell.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ module.exports = {
"EXTINF",
"mpegurl", // hls.js
"wopjs", // @wopjs/dom
"xstate",

// less
"isstring",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Meta, Story } from "@storybook/react";
import { message } from "antd";
import React from "react";
import { BindingPhonePanelProps, BindingPhonePanel } from ".";

const storyMeta: Meta = {
title: "LoginPage/BindingPhonePanel",
component: BindingPhonePanel,
};

export default storyMeta;

export const Overview: Story<BindingPhonePanelProps> = props => {
return <BindingPhonePanel {...props} />;
};

Overview.args = {
cancelBindingPhone: () => {
message.info("back to previous step");
},
bindingPhone: (countryCode: string, phone: string, code: string) => {
message.info("bind phone with " + countryCode + " " + phone + " " + code);
return new Promise(resolve => setTimeout(() => resolve(false), 1000));
},
sendBindingPhoneCode: (countryCode: string, phone: string) => {
message.info("send verification code with " + countryCode + " " + phone);
return new Promise(resolve => setTimeout(() => resolve(false), 1000));
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useTranslate } from "@netless/flat-i18n";
import { Input, Select, Button, message } from "antd";

import { validatePhone, validateCode } from "../LoginWithCode";
import { COUNTRY_CODES } from "../LoginWithCode/data";
import { useIsUnMounted, useSafePromise } from "../../../utils/hooks";
import React, { useCallback, useState } from "react";
import { LoginTitle } from "../LoginTitle";

import checkedSVG from "../icons/checked.svg";

export interface BindingPhonePanelProps {
cancelBindingPhone: () => void;
bindingPhone?: (countryCode: string, phone: string, code: string) => Promise<boolean>;
sendBindingPhoneCode?: (countryCode: string, phone: string) => Promise<boolean>;
}

export const BindingPhonePanel: React.FC<BindingPhonePanelProps> = ({
sendBindingPhoneCode,
cancelBindingPhone,
bindingPhone,
}) => {
const sp = useSafePromise();
const t = useTranslate();

const isUnMountRef = useIsUnMounted();
const [phone, setPhone] = useState("");
const [code, setCode] = useState(false);
const [countdown, setCountdown] = useState(0);
const [countryCode, setCountryCode] = useState("+86");
const [clickedBinding, setClickedBinding] = useState(false);
const [bindingPhoneCode, setBindingPhoneCode] = useState("");

const canBinding = !clickedBinding && validatePhone(phone) && validateCode(bindingPhoneCode);

const sendBindingCode = useCallback(async () => {
if (validatePhone(phone) && sendBindingPhoneCode) {
setCode(true);
const sent = await sp(sendBindingPhoneCode(countryCode, phone));
setCode(false);
if (sent) {
void message.info(t("sent-verify-code-to-phone"));
let count = 60;
setCountdown(count);
const timer = setInterval(() => {
if (isUnMountRef.current) {
clearInterval(timer);
return;
}
setCountdown(--count);
if (count === 0) {
clearInterval(timer);
}
}, 1000);
}
}
}, [countryCode, isUnMountRef, phone, sendBindingPhoneCode, sp, t]);

const bindPhone = useCallback(async () => {
if (canBinding && bindingPhone) {
setClickedBinding(true);
const success = await sp(bindingPhone(countryCode, phone, bindingPhoneCode));
if (success) {
await sp(new Promise(resolve => setTimeout(resolve, 60000)));
} else {
message.error(t("bind-phone-failed"));
}
setClickedBinding(false);
}
}, [bindingPhone, bindingPhoneCode, canBinding, countryCode, phone, sp, t]);

return (
<div className="login-with-phone binding">
<div className="login-width-limiter">
<LoginTitle subtitle={t("need-bind-phone")} title={t("bind-phone")} />
<Input
placeholder={t("enter-phone")}
prefix={
<Select bordered={false} defaultValue="+86" onChange={setCountryCode}>
{COUNTRY_CODES.map(code => (
<Select.Option
key={code}
value={`+${code}`}
>{`+${code}`}</Select.Option>
))}
</Select>
}
size="small"
status={!phone || validatePhone(phone) ? undefined : "error"}
value={phone}
onChange={ev => setPhone(ev.currentTarget.value)}
/>
<Input
placeholder={t("enter-code")}
prefix={<img alt="checked" draggable={false} src={checkedSVG} />}
status={
!bindingPhoneCode || validateCode(bindingPhoneCode) ? undefined : "error"
}
suffix={
countdown > 0 ? (
<span className="login-countdown">
{t("seconds-to-resend", { seconds: countdown })}
</span>
) : (
<Button
disabled={code || !validatePhone(phone)}
loading={code}
size="small"
type="link"
onClick={sendBindingCode}
>
{t("send-verify-code")}
</Button>
)
}
value={bindingPhoneCode}
onChange={ev => setBindingPhoneCode(ev.currentTarget.value)}
/>
<Button
className="login-big-button"
disabled={!canBinding}
loading={clickedBinding}
type="primary"
onClick={bindPhone}
>
{t("confirm")}
</Button>
<Button className="login-btn-back" type="link" onClick={cancelBindingPhone}>
{t("back")}
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import { Meta, Story } from "@storybook/react";
import { LoginPanel } from ".";
import { Overview as StoryLoginWithPhone } from "./LoginWithPhone/LoginWithPhone.stories";
import { Overview as StoryLoginWithEmail } from "./LoginWithEmail/LoginWithEmail.stories";
import { Overview as StoryLoginWithCode } from "./LoginWithCode/LoginWithCode.stories";
import { Overview as StoryLoginWithEmail } from "./LoginWithPassword/LoginWithPassword.stories";

import { LoginWithPhone } from "./LoginWithPhone";
import { LoginWithEmail } from "./LoginWithEmail";
import { LoginWithCode } from "./LoginWithCode";
import { LoginWithPassword } from "./LoginWithPassword";

const storyMeta: Meta = {
title: "LoginPage/LoginPanel",
Expand Down Expand Up @@ -36,8 +36,8 @@ export default storyMeta;
export const PlayableExample: Story<{ region: "CN" | "US" }> = ({ region }) => {
return (
<LoginPanel>
{region === "CN" && <LoginWithPhone {...(StoryLoginWithPhone.args as any)} />}
{region === "US" && <LoginWithEmail {...(StoryLoginWithEmail.args as any)} />}
{region === "CN" && <LoginWithCode {...(StoryLoginWithCode.args as any)} />}
{region === "US" && <LoginWithPassword {...(StoryLoginWithEmail.args as any)} />}
</LoginPanel>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Meta, Story } from "@storybook/react";
import { message } from "antd";
import React from "react";
import { LoginWithCode, LoginWithCodeProps } from ".";

const storyMeta: Meta = {
title: "LoginPage/LoginWithCode",
component: LoginWithCode,
};

export default storyMeta;

export const Overview: Story<LoginWithCodeProps> = props => {
return <LoginWithCode {...props} />;
};

Overview.args = {
onClickButton: provider => {
message.info("login with " + provider);
},
sendVerificationCode: (country, phone) => {
message.info("send verification code with " + country + " " + phone);
return new Promise(resolve => setTimeout(() => resolve(phone === "123456"), 1000));
},
loginOrRegister: (country, phone, code) => {
message.info("login with " + country + " " + phone + " " + code);
return new Promise(resolve => setTimeout(() => resolve(false), 1000));
},
loginWithPassword: () => {
message.info("login with password");
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.login-with-phone {
padding-top: 96px;
padding-top: 87px;
padding-bottom: 32px;

&.binding {
Expand All @@ -12,6 +12,14 @@
}
}

.login-with-phone-btn-wrapper {
margin-bottom: 16px;

.ant-btn-link {
padding: 0;
}
}

.login-countdown {
user-select: none;
-webkit-user-select: none;
Expand Down
Loading

0 comments on commit 3908376

Please sign in to comment.