Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(flat-pages): add merge account #1997

Merged
merged 1 commit into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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