Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / account / UsernameSection.tsx
blob8102f0f81043865ef4231873190b23f89bbef7eb
1 import { useState } from 'react';
2 import { useLocation } from 'react-router-dom';
4 import { c } from 'ttag';
6 import { useAddresses } from '@proton/account/addresses/hooks';
7 import { useUser } from '@proton/account/user/hooks';
8 import { Button, Card, CircleLoader, Href, InlineLinkButton } from '@proton/atoms';
9 import Badge from '@proton/components/components/badge/Badge';
10 import Icon from '@proton/components/components/icon/Icon';
11 import AppLink from '@proton/components/components/link/AppLink';
12 import Info from '@proton/components/components/link/Info';
13 import useModalState from '@proton/components/components/modalTwo/useModalState';
14 import Tooltip from '@proton/components/components/tooltip/Tooltip';
15 import { PromotionBanner } from '@proton/components/containers/banner/PromotionBanner';
16 import useApi from '@proton/components/hooks/useApi';
17 import useConfig from '@proton/components/hooks/useConfig';
18 import useNotifications from '@proton/components/hooks/useNotifications';
19 import useLoading from '@proton/hooks/useLoading';
20 import { postVerifySend } from '@proton/shared/lib/api/verify';
21 import { getAppHref } from '@proton/shared/lib/apps/helper';
22 import { stripLocalBasenameFromPathname } from '@proton/shared/lib/authentication/pathnameHelper';
23 import type { APP_NAMES } from '@proton/shared/lib/constants';
24 import {
25     ADDRESS_TYPE,
26     APPS,
27     BRAND_NAME,
28     CALENDAR_SHORT_APP_NAME,
29     MAIL_APP_NAME,
30     MAIL_SHORT_APP_NAME,
31     SETUP_ADDRESS_PATH,
32     SSO_PATHS,
33 } from '@proton/shared/lib/constants';
34 import { getIsAddressEnabled } from '@proton/shared/lib/helpers/address';
35 import { wait } from '@proton/shared/lib/helpers/promise';
36 import { stripLeadingAndTrailingSlash } from '@proton/shared/lib/helpers/string';
37 import type { Address } from '@proton/shared/lib/interfaces';
38 import { AddressConfirmationState, SessionRecoveryState, UserType } from '@proton/shared/lib/interfaces';
39 import { getIsExternalAccount } from '@proton/shared/lib/keys';
40 import clsx from '@proton/utils/clsx';
42 import { getVerificationSentText } from '../../containers/recovery/email/VerifyRecoveryEmailModal';
43 import getBoldFormattedText from '../../helpers/getBoldFormattedText';
44 import useSearchParamsEffect from '../../hooks/useSearchParamsEffect';
45 import { useIsSessionRecoveryAvailable, useSessionRecoveryState } from '../../hooks/useSessionRecovery';
46 import EditDisplayNameModal from './EditDisplayNameModal';
47 import EditExternalAddressModal from './EditExternalAddressModal';
48 import SettingsLayout from './SettingsLayout';
49 import SettingsLayoutLeft from './SettingsLayoutLeft';
50 import SettingsLayoutRight from './SettingsLayoutRight';
51 import SettingsSection from './SettingsSection';
52 import mailCalendar from './mail-calendar.svg';
53 import PasswordResetAvailableCard from './sessionRecovery/statusCards/PasswordResetAvailableCard';
54 import SessionRecoveryInProgressCard from './sessionRecovery/statusCards/SessionRecoveryInProgressCard';
55 import unverified from './unverified.svg';
57 interface Props {
58     app: APP_NAMES;
61 const UsernameSection = ({ app }: Props) => {
62     const { APP_NAME } = useConfig();
63     const api = useApi();
64     const [loading, withLoading] = useLoading();
65     const { createNotification } = useNotifications();
66     const [user] = useUser();
67     const location = useLocation();
68     const [addresses, loadingAddresses] = useAddresses();
69     const [tmpAddress, setTmpAddress] = useState<Address>();
70     const [modalProps, setModalOpen, renderModal] = useModalState();
71     const [editAddressModalProps, setEditAddressModalOpen, renderEditAddressModal] = useModalState();
73     const [isSessionRecoveryAvailable] = useIsSessionRecoveryAvailable();
74     const sessionRecoveryStatus = useSessionRecoveryState();
76     const primaryAddress = addresses?.find(getIsAddressEnabled);
78     const BRAND_NAME_TWO = BRAND_NAME;
80     const fromPath = `/${stripLeadingAndTrailingSlash(stripLocalBasenameFromPathname(location.pathname))}`;
82     const handleSendVerificationEmail = async (destination: string) => {
83         await wait(500);
84         await api(
85             postVerifySend({
86                 Type: 'external_email',
87                 Destination: destination,
88             })
89         );
90         createNotification({
91             type: 'success',
92             text: getVerificationSentText(destination),
93         });
94     };
96     const isPrimaryAddressExternal = primaryAddress?.Type === ADDRESS_TYPE.TYPE_EXTERNAL;
97     const isExternalUser = getIsExternalAccount(user);
98     const isExternalUserAndPrimaryAddressExternal = isExternalUser && isPrimaryAddressExternal;
100     const isPrimaryAddressExternalAndVerified =
101         isPrimaryAddressExternal &&
102         primaryAddress?.ConfirmationState === AddressConfirmationState.CONFIRMATION_CONFIRMED;
104     const canVerifyExternalAddress = isExternalUserAndPrimaryAddressExternal && !isPrimaryAddressExternalAndVerified;
106     const canEditExternalAddress = canVerifyExternalAddress && (user.isPrivate || user.isAdmin);
108     const canSetupProtonAddress = isExternalUser && user.isPrivate && APP_NAME === APPS.PROTONACCOUNT;
110     useSearchParamsEffect(
111         (params) => {
112             if (!canEditExternalAddress || !primaryAddress) {
113                 return;
114             }
115             const actionParam = params.get('action');
116             if (!actionParam) {
117                 return;
118             }
120             if (actionParam === 'edit-email') {
121                 params.delete('action');
122                 setTmpAddress(primaryAddress);
123                 setEditAddressModalOpen(true);
124                 return params;
125             }
126         },
127         [primaryAddress]
128     );
130     return (
131         <>
132             {renderModal && tmpAddress && <EditDisplayNameModal {...modalProps} address={tmpAddress} />}
133             {renderEditAddressModal && tmpAddress && (
134                 <EditExternalAddressModal {...editAddressModalProps} address={tmpAddress} />
135             )}
136             <SettingsSection>
137                 {isSessionRecoveryAvailable && sessionRecoveryStatus === SessionRecoveryState.GRACE_PERIOD && (
138                     <SessionRecoveryInProgressCard className="mb-6" />
139                 )}
140                 {isSessionRecoveryAvailable && sessionRecoveryStatus === SessionRecoveryState.INSECURE && (
141                     <PasswordResetAvailableCard className="mb-6" />
142                 )}
143                 {canSetupProtonAddress && (
144                     <div className="mb-6">
145                         <AppLink
146                             toApp={APPS.PROTONACCOUNT}
147                             to={`${SETUP_ADDRESS_PATH}?to=${APPS.PROTONMAIL}&from=${app}&from-type=settings&from-path=${fromPath}`}
148                             className="text-no-decoration"
149                             data-testid="get-proton-address"
150                         >
151                             <PromotionBanner
152                                 mode="banner"
153                                 rounded
154                                 contentCentered={false}
155                                 icon={<img width="40" src={mailCalendar} alt="" className="shrink-0" />}
156                                 description={getBoldFormattedText(
157                                     c('Info')
158                                         .t`**Get a ${BRAND_NAME} address** to use all ${BRAND_NAME_TWO} services including ${MAIL_SHORT_APP_NAME} and ${CALENDAR_SHORT_APP_NAME}.`
159                                 )}
160                                 cta={
161                                     <div className="mr-4">
162                                         <Icon name="chevron-right" size={4} />
163                                     </div>
164                                 }
165                             />
166                         </AppLink>
167                     </div>
168                 )}
170                 {canVerifyExternalAddress && (
171                     <Card className="mb-8" rounded bordered={true} background={false}>
172                         <div className="h3 text-bold mb-6">{c('Info').t`Secure your ${BRAND_NAME} Account`}</div>
173                         <div className="flex gap-4 flex-nowrap items-start">
174                             <img className="shrink-0" width="40" height="40" src={unverified} alt="" />
175                             <div>
176                                 <div className="mb-2 text-lg text-semibold flex">
177                                     <div className="mr-2 text-ellipsis">{primaryAddress.Email}</div>
178                                     <Badge type="warning">{c('Info').t`Unverified`}</Badge>
179                                 </div>
180                                 <div>
181                                     <div className="mb-2">
182                                         {c('Info').t`Increase your account security by verifying your email address.`}
183                                     </div>
184                                     <Button
185                                         color="norm"
186                                         loading={loading}
187                                         onClick={() => {
188                                             withLoading(handleSendVerificationEmail(primaryAddress.Email));
189                                         }}
190                                     >
191                                         {c('Info').t`Resend verification email`}
192                                     </Button>
193                                 </div>
194                             </div>
195                         </div>
196                     </Card>
197                 )}
199                 <SettingsLayout>
200                     <SettingsLayoutLeft>
201                         <div className="text-semibold">{c('Label').t`Username`}</div>
202                     </SettingsLayoutLeft>
203                     <SettingsLayoutRight className="pt-2">
204                         {isExternalUserAndPrimaryAddressExternal ? (
205                             <div>
206                                 {isPrimaryAddressExternalAndVerified ? (
207                                     <div className="flex">
208                                         {primaryAddress.Email}
209                                         <Tooltip title={c('Tooltip').t`Verified email address`} openDelay={0}>
210                                             <Icon
211                                                 name="checkmark-circle-filled"
212                                                 size={4}
213                                                 className="ml-2 color-success self-center"
214                                             />
215                                         </Tooltip>
216                                     </div>
217                                 ) : (
218                                     <>
219                                         {canEditExternalAddress ? (
220                                             <div className="flex">
221                                                 <span className="mr-2">{primaryAddress.Email}</span>
222                                                 <InlineLinkButton
223                                                     className="mr-1"
224                                                     onClick={() => {
225                                                         setTmpAddress(primaryAddress);
226                                                         setEditAddressModalOpen(true);
227                                                     }}
228                                                     aria-label={c('Action').t`Edit email address`}
229                                                 >
230                                                     {c('Action').t`Edit`}
231                                                 </InlineLinkButton>
232                                                 <Info
233                                                     className="self-center"
234                                                     title={c('Info')
235                                                         .t`You can edit this once to ensure the correct email address for verification.`}
236                                                 />
237                                             </div>
238                                         ) : (
239                                             <div>{primaryAddress.Email}</div>
240                                         )}
241                                         <div className="flex">
242                                             <Icon
243                                                 name="exclamation-circle-filled"
244                                                 size={4}
245                                                 className="mr-1 color-danger self-center"
246                                             />
247                                             <span className="color-weak mr-1">
248                                                 {c('Info').t`Unverified email address.`}
249                                             </span>
250                                         </div>
251                                     </>
252                                 )}
253                             </div>
254                         ) : (
255                             user.Name
256                         )}
257                     </SettingsLayoutRight>
258                 </SettingsLayout>
259                 {(primaryAddress || loadingAddresses) && (
260                     <SettingsLayout>
261                         <SettingsLayoutLeft>
262                             <div className="text-semibold">{c('Label').t`Display name`}</div>
263                         </SettingsLayoutLeft>
264                         <SettingsLayoutRight className="pt-2">
265                             {!primaryAddress || loadingAddresses ? (
266                                 <div className="flex flex-nowrap">
267                                     <CircleLoader />
268                                 </div>
269                             ) : (
270                                 <div className="flex flex-nowrap">
271                                     <div
272                                         className={clsx(
273                                             'text-ellipsis user-select',
274                                             primaryAddress.DisplayName && 'mr-2'
275                                         )}
276                                     >
277                                         {primaryAddress.DisplayName}
278                                     </div>
279                                     <InlineLinkButton
280                                         onClick={() => {
281                                             setTmpAddress(primaryAddress);
282                                             setModalOpen(true);
283                                         }}
284                                         aria-label={c('Action').t`Edit display name`}
285                                     >
286                                         {c('Action').t`Edit`}
287                                     </InlineLinkButton>
288                                 </div>
289                             )}
290                         </SettingsLayoutRight>
291                     </SettingsLayout>
292                 )}
293                 {APP_NAME === APPS.PROTONVPN_SETTINGS && user.Type === UserType.PROTON && (
294                     <SettingsLayout>
295                         <SettingsLayoutLeft>
296                             <div className="text-semibold">{c('Label').t`${MAIL_APP_NAME} address`}</div>
297                         </SettingsLayoutLeft>
298                         <SettingsLayoutRight className="pt-2">
299                             {(() => {
300                                 if (loadingAddresses) {
301                                     return (
302                                         <div className="flex flex-nowrap">
303                                             <CircleLoader />
304                                         </div>
305                                     );
306                                 }
308                                 if (primaryAddress?.Email) {
309                                     return (
310                                         <div className="text-pre-wrap break user-select">{primaryAddress.Email}</div>
311                                     );
312                                 }
314                                 return (
315                                     <Href
316                                         href={`${getAppHref(SSO_PATHS.SWITCH, APPS.PROTONACCOUNT)}?product=mail`}
317                                         title={c('Info').t`Sign in to ${MAIL_APP_NAME} to activate your address`}
318                                     >
319                                         {c('Link').t`Not activated`}
320                                     </Href>
321                                 );
322                             })()}
323                         </SettingsLayoutRight>
324                     </SettingsLayout>
325                 )}
326             </SettingsSection>
327         </>
328     );
331 export default UsernameSection;