Remove payments API routing initialization
[ProtonMail-WebClient.git] / packages / components / containers / recovery / phone / RecoveryPhone.tsx
blob4bfa95b57b099e50a1b8363d459ffbb7f7fafb42
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 {
26     className?: string;
27     onSubmit: (e: FormEvent<HTMLFormElement>) => void;
28     input: ReactNode;
29     submitButtonProps: {
30         type: 'submit';
31         disabled: boolean;
32         loading: boolean;
33     };
36 const defaultRenderForm = ({ className, onSubmit, input, submitButtonProps }: RenderFormProps) => {
37     return (
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' }}>
40                 {input}
41             </div>
42             <div className="mb-2">
43                 <Button shape="outline" data-testid="account:recovery:phoneSubmit" {...submitButtonProps}>
44                     {c('Action').t`Save`}
45                 </Button>
46             </div>
47         </form>
48     );
51 interface Props {
52     phone: UserSettings['Phone'];
53     hasReset: boolean;
54     defaultCountry?: string;
55     className?: string;
56     onSuccess?: () => void;
57     autoFocus?: boolean;
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,
66     phone,
67     hasReset,
68     defaultCountry,
69     className,
70     onSuccess,
71     autoFocus,
72     inputProps,
73     disableVerifyCta,
74     persistPasswordScope = false,
75 }: Props) => {
76     const api = useApi();
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 () => {
90         await api(
91             updatePhone({
92                 Phone: input,
93                 PersistPasswordScope: persistPasswordScope,
94             })
95         );
96         await call();
98         createNotification({ text: c('Success').t`Phone number updated` });
99         onSuccess?.();
100     };
102     return (
103         <>
104             {renderConfirmModal && (
105                 <ConfirmRemovePhoneModal {...confirmModal} onConfirm={() => withUpdatingPhone(handleUpdatePhone)} />
106             )}
107             {renderVerifyRecoveryPhoneModal && <VerifyRecoveryPhoneModal phone={phone} {...verifyRecoveryPhoneModal} />}
108             {renderForm({
109                 className,
110                 onSubmit: (e) => {
111                     e.preventDefault();
112                     if (!onFormSubmit()) {
113                         return;
114                     }
115                     if (confirmStep) {
116                         setConfirmModal(true);
117                     } else {
118                         void withUpdatingPhone(handleUpdatePhone);
119                     }
120                 },
121                 input: (
122                     <InputFieldTwo
123                         as={PhoneInput}
124                         id="phoneInput"
125                         disableChange={loading}
126                         autoFocus={autoFocus}
127                         defaultCountry={defaultCountry}
128                         value={input}
129                         onChange={setInput}
130                         aria-label={c('label').t`Recovery phone number`}
131                         assistiveText={
132                             !disableVerifyCta &&
133                             phone.Value &&
134                             (phone.Status !== SETTINGS_STATUS.VERIFIED ? (
135                                 <>
136                                     <Icon
137                                         className="color-danger shrink-0 aligntop mr-1"
138                                         name="exclamation-circle-filled"
139                                     />
140                                     <span className="color-norm mr-2">{c('Recovery Phone')
141                                         .t`Phone number not yet verified.`}</span>
142                                     <button
143                                         className="link"
144                                         type="button"
145                                         onClick={() => setVerifyRecoveryPhoneModalOpen(true)}
146                                         aria-label={c('Recovery Phone')
147                                             .t`Verify this recovery phone number now: ${phone.Value}`}
148                                     >
149                                         {c('Recovery Phone').t`Verify now`}
150                                     </button>
151                                 </>
152                             ) : (
153                                 <>
154                                     <Icon
155                                         className="color-success shrink-0 aligntop mr-1"
156                                         name="checkmark-circle-filled"
157                                     />
158                                     <span className="mr-2">{c('Recovery Phone')
159                                         .t`Phone number has been verified.`}</span>
160                                 </>
161                             ))
162                         }
163                         {...inputProps}
164                     />
165                 ),
166                 submitButtonProps: {
167                     type: 'submit',
168                     disabled: (phone.Value || '') === input,
169                     loading,
170                 },
171             })}
172         </>
173     );
176 export default RecoveryPhone;