1 import type { FormEvent, ReactNode } from 'react';
2 import { useState } from 'react';
4 import { c } from 'ttag';
6 import { Button } from '@proton/atoms';
7 import Icon from '@proton/components/components/icon/Icon';
8 import useModalState from '@proton/components/components/modalTwo/useModalState';
9 import InputFieldTwo from '@proton/components/components/v2/field/InputField';
10 import PhoneInput from '@proton/components/components/v2/phone/LazyPhoneInput';
11 import useFormErrors from '@proton/components/components/v2/useFormErrors';
12 import useApi from '@proton/components/hooks/useApi';
13 import useEventManager from '@proton/components/hooks/useEventManager';
14 import useNotifications from '@proton/components/hooks/useNotifications';
15 import useLoading from '@proton/hooks/useLoading';
16 import { updatePhone } from '@proton/shared/lib/api/settings';
17 import type { UserSettings } from '@proton/shared/lib/interfaces';
18 import { SETTINGS_STATUS } from '@proton/shared/lib/interfaces';
19 import clsx from '@proton/utils/clsx';
21 import type { InputFieldProps } from '../../../components/v2/field/InputField';
22 import ConfirmRemovePhoneModal from './ConfirmRemovePhoneModal';
23 import VerifyRecoveryPhoneModal from './VerifyRecoveryPhoneModal';
25 interface RenderFormProps {
27 onSubmit: (e: FormEvent<HTMLFormElement>) => void;
36 const defaultRenderForm = ({ className, onSubmit, input, submitButtonProps }: RenderFormProps) => {
38 <form className={clsx(['flex flex-wrap flex-column md:flex-row', className])} onSubmit={onSubmit}>
39 <div className="mr-0 mb-4 md:mr-4 md:flex-1 min-w-custom" style={{ '--min-w-custom': '14em' }}>
42 <div className="mb-2">
43 <Button shape="outline" data-testid="account:recovery:phoneSubmit" {...submitButtonProps}>
52 phone: UserSettings['Phone'];
54 defaultCountry?: string;
56 onSuccess?: () => void;
58 renderForm?: (props: RenderFormProps) => ReactNode;
59 inputProps?: Partial<Pick<InputFieldProps<typeof PhoneInput>, 'label'>>;
60 disableVerifyCta?: boolean;
61 persistPasswordScope?: boolean;
64 const RecoveryPhone = ({
65 renderForm = defaultRenderForm,
74 persistPasswordScope = false,
77 const [input, setInput] = useState(phone.Value || '');
78 const { createNotification } = useNotifications();
79 const { call } = useEventManager();
80 const { onFormSubmit } = useFormErrors();
81 const [verifyRecoveryPhoneModal, setVerifyRecoveryPhoneModalOpen, renderVerifyRecoveryPhoneModal] = useModalState();
82 const [confirmModal, setConfirmModal, renderConfirmModal] = useModalState();
84 const [updatingPhone, withUpdatingPhone] = useLoading();
86 const confirmStep = !input && hasReset;
87 const loading = renderVerifyRecoveryPhoneModal || renderConfirmModal || updatingPhone;
89 const handleUpdatePhone = async () => {
93 PersistPasswordScope: persistPasswordScope,
98 createNotification({ text: c('Success').t`Phone number updated` });
104 {renderConfirmModal && (
105 <ConfirmRemovePhoneModal {...confirmModal} onConfirm={() => withUpdatingPhone(handleUpdatePhone)} />
107 {renderVerifyRecoveryPhoneModal && <VerifyRecoveryPhoneModal phone={phone} {...verifyRecoveryPhoneModal} />}
112 if (!onFormSubmit()) {
116 setConfirmModal(true);
118 void withUpdatingPhone(handleUpdatePhone);
125 disableChange={loading}
126 autoFocus={autoFocus}
127 defaultCountry={defaultCountry}
130 aria-label={c('label').t`Recovery phone number`}
134 (phone.Status !== SETTINGS_STATUS.VERIFIED ? (
137 className="color-danger shrink-0 aligntop mr-1"
138 name="exclamation-circle-filled"
140 <span className="color-norm mr-2">{c('Recovery Phone')
141 .t`Phone number not yet verified.`}</span>
145 onClick={() => setVerifyRecoveryPhoneModalOpen(true)}
146 aria-label={c('Recovery Phone')
147 .t`Verify this recovery phone number now: ${phone.Value}`}
149 {c('Recovery Phone').t`Verify now`}
155 className="color-success shrink-0 aligntop mr-1"
156 name="checkmark-circle-filled"
158 <span className="mr-2">{c('Recovery Phone')
159 .t`Phone number has been verified.`}</span>
168 disabled: (phone.Value || '') === input,
176 export default RecoveryPhone;