Skip to content

Commit

Permalink
refactor(flat-pages): add merge account (#1997)
Browse files Browse the repository at this point in the history
refactor(flat-pages): update type

refactor(flat-pages): refactor rebinding name
  • Loading branch information
syt-honey authored Aug 15, 2023
1 parent 5aca935 commit dc2d0da
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "./index.less";

import { useTranslate } from "@netless/flat-i18n";
import { Button, message, Form } from "antd";
import { FlatI18nTFunction, useTranslate } from "@netless/flat-i18n";
import { Button, message, Form, Modal } from "antd";
import React, { useCallback, useState } from "react";

import { useSafePromise } from "../../../utils/hooks";
Expand All @@ -10,11 +10,16 @@ import { LoginAccount, PasswordLoginType, defaultCountryCode } from "../LoginAcc
import { LoginSendCode } from "../LoginSendCode";
import { codeValidator } from "../LoginWithCode/validators";
import { phoneValidator } from "../LoginWithPassword/validators";
import { BindingPhoneSendCodeResult } from "@netless/flat-server-api";

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

interface BindingFormValues {
Expand All @@ -26,6 +31,7 @@ export const BindingPhonePanel: React.FC<BindingPhonePanelProps> = ({
sendBindingPhoneCode,
cancelBindingPhone,
bindingPhone,
needRebindingPhone,
}) => {
const sp = useSafePromise();
const t = useTranslate();
Expand Down Expand Up @@ -57,7 +63,7 @@ export const BindingPhonePanel: React.FC<BindingPhonePanelProps> = ({
}
}, [bindingPhone, form, countryCode, isFormValidated, sp, t]);

const handleSendVerificationCode = async (): Promise<boolean> => {
const sendVerificationCode = async (): Promise<BindingPhoneSendCodeResult> => {
const { phone } = form.getFieldsValue();
return sendBindingPhoneCode(countryCode, phone);
};
Expand All @@ -75,6 +81,12 @@ export const BindingPhonePanel: React.FC<BindingPhonePanelProps> = ({
}
}, [form]);

const handleSendVerificationCode = async (): Promise<void> => {
if (await requestRebinding({ t })) {
needRebindingPhone();
}
};

return (
<div className="login-with-phone-binding">
<div className="login-width-limiter">
Expand All @@ -96,8 +108,9 @@ export const BindingPhonePanel: React.FC<BindingPhonePanelProps> = ({

<Form.Item name="code" rules={[codeValidator]}>
<LoginSendCode
handleSendVerificationCode={handleSendVerificationCode}
isAccountValidated={isAccountValidated}
sendVerificationCode={handleSendVerificationCode}
sendVerificationCode={sendVerificationCode}
type={type}
/>
</Form.Item>
Expand All @@ -118,3 +131,17 @@ export const BindingPhonePanel: React.FC<BindingPhonePanelProps> = ({
</div>
);
};

export interface RequestRebindingParams {
t: FlatI18nTFunction;
}

export function requestRebinding({ t }: RequestRebindingParams): Promise<boolean> {
return new Promise<boolean>(resolve =>
Modal.confirm({
content: <div>{t("rebinding-phone-tips")}</div>,
onOk: () => resolve(true),
onCancel: () => resolve(false),
}),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import { useTranslate } from "@netless/flat-i18n";
import { useIsUnMounted, useSafePromise } from "../../../utils/hooks";
import { PasswordLoginType, isPhone } from "../LoginAccount";
import checkedSVG from "../icons/checked.svg";
import { BindingPhoneSendCodeResult, RequestErrorCode } from "@netless/flat-server-api";

export interface LoginSendCodeProps {
isAccountValidated: boolean;
type: PasswordLoginType;
sendVerificationCode: () => Promise<boolean>;
// BindingPhoneSendCodeResult for binding phone page
sendVerificationCode: () => Promise<boolean | BindingPhoneSendCodeResult>;

// for rebinding phone
handleSendVerificationCode?: () => void;
}

export const LoginSendCode: React.FC<LoginSendCodeProps> = ({
type,
isAccountValidated,
sendVerificationCode,
handleSendVerificationCode,
...restProps
}) => {
const isUnMountRef = useIsUnMounted();
Expand All @@ -29,10 +35,11 @@ export const LoginSendCode: React.FC<LoginSendCodeProps> = ({

const sendCode = useCallback(async () => {
if (isAccountValidated) {
setSendingCode(true);
const sent = await sp(sendVerificationCode());
setSendingCode(false);
if (sent) {
try {
setSendingCode(true);
await sp(sendVerificationCode());

setSendingCode(false);
void message.info(
t(isPhone(type) ? "sent-verify-code-to-phone" : "sent-verify-code-to-email"),
);
Expand All @@ -48,11 +55,33 @@ export const LoginSendCode: React.FC<LoginSendCodeProps> = ({
clearInterval(timer);
}
}, 1000);
} else {
} catch (error) {
if (!isUnMountRef.current) {
setSendingCode(false);
}

// we say the phone is already binding when error message contains `RequestErrorCode.SMSAlreadyBinding`
// and then we can enter rebinding page to rebind.
if (
error.message.indexOf(RequestErrorCode.SMSAlreadyBinding) > -1 &&
handleSendVerificationCode
) {
handleSendVerificationCode();
return;
}

message.error(t("send-verify-code-failed"));
}
}
}, [isUnMountRef, isAccountValidated, type, sendVerificationCode, sp, t]);
}, [
isAccountValidated,
sp,
sendVerificationCode,
t,
type,
isUnMountRef,
handleSendVerificationCode,
]);

return (
<Input
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 { RebindingPhonePanelProps, RebindingPhonePanel } from ".";

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

export default storyMeta;

export const Overview: Story<RebindingPhonePanelProps> = props => {
return <RebindingPhonePanel {...props} />;
};

Overview.args = {
cancelRebindingPhone: () => {
message.info("back to previous step");
},
rebindingPhone: (countryCode: string, phone: string, code: string) => {
message.info("merge phone with " + countryCode + " " + phone + " " + code);
return new Promise(resolve => setTimeout(() => resolve(false), 1000));
},
sendRebindingPhoneCode: (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,9 @@
.login-with-phone-rebinding {
padding-top: 184px;
padding-bottom: 32px;
}

.login-countdown {
user-select: none;
-webkit-user-select: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import "./index.less";

import { useTranslate } from "@netless/flat-i18n";
import { Button, message, Form } from "antd";
import React, { useCallback, useState } from "react";

import { useSafePromise } from "../../../utils/hooks";
import { LoginTitle } from "../LoginTitle";
import { LoginAccount, PasswordLoginType, defaultCountryCode } from "../LoginAccount";
import { LoginSendCode } from "../LoginSendCode";
import { codeValidator } from "../LoginWithCode/validators";
import { phoneValidator } from "../LoginWithPassword/validators";

export interface RebindingPhonePanelProps {
cancelRebindingPhone: () => void;
rebindingPhone: (countryCode: string, phone: string, code: string) => Promise<boolean>;
sendRebindingPhoneCode: (countryCode: string, phone: string) => Promise<any>;
}

interface RebindingFormValues {
phone: string;
code: string;
}

export const RebindingPhonePanel: React.FC<RebindingPhonePanelProps> = ({
sendRebindingPhoneCode,
rebindingPhone,
cancelRebindingPhone,
}) => {
const sp = useSafePromise();
const t = useTranslate();

const [form] = Form.useForm<RebindingFormValues>();
const [isFormValidated, setIsFormValidated] = useState(false);
const [isAccountValidated, setIsAccountValidated] = useState(false);
const type = PasswordLoginType.Phone;

const defaultValues = {
phone: "",
code: "",
};

const [countryCode, setCountryCode] = useState(defaultCountryCode);
const [clickedRebinding, setClickedRebinding] = useState(false);

const handleRebindingPhone = useCallback(async () => {
if (isFormValidated && rebindingPhone) {
setClickedRebinding(true);
const { phone, code } = form.getFieldsValue();
const success = await sp(rebindingPhone(countryCode, phone, code));
if (success) {
await sp(new Promise(resolve => setTimeout(resolve, 60000)));
} else {
message.error(t("rebinding-phone-failed"));
}
setClickedRebinding(false);
}
}, [isFormValidated, form, sp, countryCode, rebindingPhone, t]);

const sendVerificationCode = async (): Promise<any> => {
const { phone } = form.getFieldsValue();
return sendRebindingPhoneCode(countryCode, phone);
};

const formValidateStatus = useCallback(() => {
setIsFormValidated(
form.getFieldsError().every(field => field.errors.length <= 0) &&
Object.values(form.getFieldsValue()).every(v => !!v),
);

if (form.getFieldValue("phone") && !form.getFieldError("phone").length) {
setIsAccountValidated(true);
} else {
setIsAccountValidated(false);
}
}, [form]);

return (
<div className="login-with-phone-rebinding">
<div className="login-width-limiter">
<LoginTitle subtitle=" " title={t("rebinding-phone")} />

<Form
form={form}
initialValues={defaultValues}
name="rebindingPhoneForm"
onFieldsChange={formValidateStatus}
>
<Form.Item name="phone" rules={[phoneValidator]}>
<LoginAccount
countryCode={countryCode}
handleCountryCode={code => setCountryCode(code)}
placeholder={t("enter-phone")}
/>
</Form.Item>

<Form.Item name="code" rules={[codeValidator]}>
<LoginSendCode
isAccountValidated={isAccountValidated}
sendVerificationCode={sendVerificationCode}
type={type}
/>
</Form.Item>
</Form>
<Button
className="login-big-button"
disabled={!isFormValidated}
loading={clickedRebinding}
type="primary"
onClick={handleRebindingPhone}
>
{t("confirm")}
</Button>
<Button className="login-btn-back" type="link" onClick={cancelRebindingPhone}>
{t("back")}
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useLanguage } from "@netless/flat-i18n";
export * from "./LoginWithPassword";
export * from "./LoginWithCode";
export * from "./BindingPhonePanel";
export * from "./RebindingPhonePanel";
export * from "./QRCodePanel";
export * from "./LoginAccount";
export * from "./LoginPassword";
Expand Down
3 changes: 3 additions & 0 deletions packages/flat-i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,12 @@
"login-using-other-methods": "Use other methods to log in",
"login-phone-verification-code-invalid": "Invalid verification code",
"login-phone-already-exist": "The phone number already exists",
"rebinding-phone-tips": "Input mobile phone number has been bound to other accounts, whether the current New Account and existing account merger? The merge operation will erase all data from the current New Account.",
"phone-already-binding": "The phone is already bound",
"bind-phone-failed": "Failed to bind phone",
"rebinding-phone-failed": "Failed to rebind phone",
"bind-phone": "Bind phone",
"rebinding-phone": "Merge account",
"need-bind-phone": "Need to bind phone",
"online-interaction-to-synchronize-ideas": "Interact online to keep ideas in sync",
"privacy-agreement": "Privacy Policy",
Expand Down
3 changes: 3 additions & 0 deletions packages/flat-i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,12 @@
"login-using-other-methods": "使用其他方式登录",
"login-phone-verification-code-invalid": "无效的验证码",
"login-phone-already-exist": "该手机号已注册",
"rebinding-phone-tips": "输入手机号已绑定其他账号,是否将当前新建账号和已有账号合并?合并操作将会清除当前新建账号所有数据。",
"phone-already-binding": "该手机号已绑定",
"bind-phone-failed": "绑定手机号失败",
"rebinding-phone-failed": "账号合并失败",
"bind-phone": "绑定手机号",
"rebinding-phone": "合并账号",
"need-bind-phone": "根据相关政策法规,需要绑定手机号",
"back": "返回",
"remove-room": "移除房间",
Expand Down
Loading

0 comments on commit dc2d0da

Please sign in to comment.