Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / organization / OrganizationSection.tsx
blobe00832e1d970755bd888cc1afa4efe4e7e695e8f
1 import { c } from 'ttag';
3 import { useGetAddresses } from '@proton/account/addresses/hooks';
4 import { useCustomDomains } from '@proton/account/domains/hooks';
5 import { useOrganizationKey } from '@proton/account/organizationKey/hooks';
6 import { useSubscription } from '@proton/account/subscription/hooks';
7 import { useUser } from '@proton/account/user/hooks';
8 import { Button, ButtonLike, CircleLoader, InlineLinkButton } from '@proton/atoms';
9 import ButtonGroup from '@proton/components/components/button/ButtonGroup';
10 import PrimaryButton from '@proton/components/components/button/PrimaryButton';
11 import Row from '@proton/components/components/container/Row';
12 import Icon from '@proton/components/components/icon/Icon';
13 import Label from '@proton/components/components/label/Label';
14 import Info from '@proton/components/components/link/Info';
15 import SettingsLink from '@proton/components/components/link/SettingsLink';
16 import Loader from '@proton/components/components/loader/Loader';
17 import useModalState from '@proton/components/components/modalTwo/useModalState';
18 import { useModalTwoPromise } from '@proton/components/components/modalTwo/useModalTwo';
19 import Tooltip from '@proton/components/components/tooltip/Tooltip';
20 import SettingsLayout from '@proton/components/containers/account/SettingsLayout';
21 import SettingsLayoutLeft from '@proton/components/containers/account/SettingsLayoutLeft';
22 import SettingsLayoutRight from '@proton/components/containers/account/SettingsLayoutRight';
23 import SettingsParagraph from '@proton/components/containers/account/SettingsParagraph';
24 import SettingsSection from '@proton/components/containers/account/SettingsSection';
25 import useKTActivation from '@proton/components/containers/keyTransparency/useKTActivation';
26 import AuthModal, { type AuthModalResult } from '@proton/components/containers/password/AuthModal';
27 import useApi from '@proton/components/hooks/useApi';
28 import useAuthentication from '@proton/components/hooks/useAuthentication';
29 import useConfig from '@proton/components/hooks/useConfig';
30 import useNotifications from '@proton/components/hooks/useNotifications';
31 import useLoading from '@proton/hooks/useLoading';
32 import { queryAvailableDomains } from '@proton/shared/lib/api/domains';
33 import { unlockPasswordChanges } from '@proton/shared/lib/api/user';
34 import innerMutatePassword from '@proton/shared/lib/authentication/mutate';
35 import type { APP_NAMES } from '@proton/shared/lib/constants';
36 import { APPS, BRAND_NAME, DRIVE_APP_NAME, MAIL_APP_NAME, ORGANIZATION_STATE } from '@proton/shared/lib/constants';
37 import { getHasMemberCapablePlan, hasDuo, hasFamily, hasPassFamily } from '@proton/shared/lib/helpers/subscription';
38 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
39 import type { Organization } from '@proton/shared/lib/interfaces';
40 import { createPreAuthKTVerifier } from '@proton/shared/lib/keyTransparency';
41 import { handleSetupAddressKeys } from '@proton/shared/lib/keys';
42 import { getOrganizationDenomination } from '@proton/shared/lib/organization/helper';
43 import noop from '@proton/utils/noop';
45 import DomainModal from '../domains/DomainModal';
46 import EditOrganizationIdentityModal from './EditOrganizationIdentityModal';
47 import OrganizationNameModal from './OrganizationNameModal';
48 import OrganizationSectionUpsell from './OrganizationSectionUpsell';
49 import SetupOrganizationModal from './SetupOrganizationModal';
50 import OrganizationLogoModal from './logoUpload/OrganizationLogoModal';
51 import OrganizationLogoRemovalModal from './logoUpload/OrganizationLogoRemovalModal';
52 import OrganizationLogoTipsModal from './logoUpload/OrganizationLogoTipsModal';
53 import { OrganizationLogoUploadUpsellBanner } from './logoUpload/OrganizationLogoUploadUpsellBanner';
54 import { useOrganizationTheme } from './logoUpload/useOrganizationTheme';
55 import useOrganizationIdentity from './useOrganizationIdentity';
57 interface Props {
58     app: APP_NAMES;
59     organization?: Organization;
62 const OrganizationSection = ({ app, organization }: Props) => {
63     const { APP_NAME } = useConfig();
64     const [organizationKey] = useOrganizationKey();
65     const [user] = useUser();
66     const getAddresses = useGetAddresses();
67     const api = useApi();
68     const [customDomains] = useCustomDomains();
69     const [subscription] = useSubscription();
70     const [loading, withLoading] = useLoading();
71     const ktActivation = useKTActivation();
72     const authentication = useAuthentication();
73     const [editOrganizationIdentityProps, setEditOrganizationIdentityModal, renderEditOrganizationIdentityModal] =
74         useModalState();
75     const [editOrganizationNameProps, setEditOrganizationNameModal, renderEditOrganizationNameModal] = useModalState();
76     const [newDomainModalProps, setNewDomainModalOpen, renderNewDomain] = useModalState();
77     const [setupOrganizationModalProps, setSetupOrganizationModal, renderSetupOrganizationModal] = useModalState();
79     const [authModal, showAuthModal] = useModalTwoPromise<undefined, AuthModalResult>();
81     const { createNotification } = useNotifications();
82     const isPartOfFamily = getOrganizationDenomination(organization) === 'familyGroup';
84     const [organizationLogoModal, setOrganizationLogoModal, renderOrganizationLogoModal] = useModalState();
85     const [organizationLogoTipsModal, setOrganizationLogoTipsModal, renderOrganizationLogoTipsModal] = useModalState();
86     const [organizationLogoRemovalModal, setOrganizationLogoRemovalModal, renderOrganizationLogoRemovalModal] =
87         useModalState();
89     const organizationTheme = useOrganizationTheme();
90     const canAccessLightLabelling = organizationTheme.access && APP_NAME === APPS.PROTONACCOUNT;
91     const organizationIdentity = useOrganizationIdentity();
93     const isOrgActive = organization?.State === ORGANIZATION_STATE.ACTIVE;
95     if (!organization || !user || !subscription || !customDomains) {
96         return <Loader />;
97     }
99     const hasMemberCapablePlan = getHasMemberCapablePlan(organization, subscription);
101     return (
102         <>
103             {authModal((props) => {
104                 return (
105                     <AuthModal
106                         {...props}
107                         scope="password"
108                         config={unlockPasswordChanges()}
109                         onCancel={props.onReject}
110                         onSuccess={props.onResolve}
111                     />
112                 );
113             })}
115             {renderNewDomain && <DomainModal {...newDomainModalProps} />}
117             {renderOrganizationLogoModal && (
118                 <OrganizationLogoModal app={app} size="large" organization={organization} {...organizationLogoModal} />
119             )}
120             {renderOrganizationLogoRemovalModal && (
121                 <OrganizationLogoRemovalModal
122                     app={app}
123                     size="small"
124                     organization={organization}
125                     {...organizationLogoRemovalModal}
126                 />
127             )}
128             {renderOrganizationLogoTipsModal && (
129                 <OrganizationLogoTipsModal
130                     size="small"
131                     onUploadClick={() => setOrganizationLogoModal(true)}
132                     {...organizationLogoTipsModal}
133                 />
134             )}
135             {renderEditOrganizationNameModal && (
136                 <OrganizationNameModal organization={organization} {...editOrganizationNameProps} />
137             )}
138             {renderEditOrganizationIdentityModal && (
139                 <EditOrganizationIdentityModal
140                     signatureAddress={organizationIdentity.signatureAddress}
141                     {...editOrganizationIdentityProps}
142                 />
143             )}
144             {renderSetupOrganizationModal && <SetupOrganizationModal {...setupOrganizationModalProps} />}
146             {(() => {
147                 if (!hasMemberCapablePlan || !isOrgActive) {
148                     return <OrganizationSectionUpsell app={app} />;
149                 }
151                 if (organization.RequiresDomain && customDomains.length === 0) {
152                     return (
153                         <SettingsSection>
154                             <SettingsParagraph>
155                                 {c('Info')
156                                     .t`Create email addresses for other people, manage ${MAIL_APP_NAME} for a business, school, or group. Get started by adding your organization name and custom domain (e.g. @yourcompany.com). `}
157                             </SettingsParagraph>
158                             <ButtonLike
159                                 color="norm"
160                                 onClick={() => {
161                                     setNewDomainModalOpen(true);
162                                 }}
163                             >
164                                 {c('Action').t`Add domain`}
165                             </ButtonLike>
166                         </SettingsSection>
167                     );
168                 }
170                 if (!organization.RequiresKey && !organization.Name) {
171                     const buttonCTA = isPartOfFamily
172                         ? c('familyOffer_2023:Action').t`Set up family group`
173                         : c('Action').t`Enable multi-user support`;
175                     let learnMoreLink = '/proton-for-business';
176                     if (hasFamily(subscription)) {
177                         learnMoreLink = '/get-started-proton-family';
178                     } else if (hasPassFamily(subscription)) {
179                         learnMoreLink = '/get-started-proton-pass-family';
180                     } else if (hasDuo(subscription)) {
181                         learnMoreLink = '/get-started-proton-duo';
182                     }
184                     return (
185                         <>
186                             <SettingsParagraph learnMoreUrl={getKnowledgeBaseUrl(learnMoreLink)}>
187                                 {hasPassFamily(subscription)
188                                     ? c('familyOffer_2023:Info').t`Create and manage family members.`
189                                     : c('familyOffer_2023:Info')
190                                           .t`Create and manage family members and assign them storage space shared between ${DRIVE_APP_NAME} and ${MAIL_APP_NAME}.`}
191                             </SettingsParagraph>
192                             <PrimaryButton
193                                 onClick={async () => {
194                                     if (!hasMemberCapablePlan) {
195                                         return createNotification({
196                                             type: 'error',
197                                             text: c('Error')
198                                                 .t`Please upgrade to a business plan with more than 1 user to get multi-user support`,
199                                         });
200                                     }
202                                     try {
203                                         await showAuthModal();
204                                         setSetupOrganizationModal(true);
205                                     } catch {}
206                                 }}
207                             >
208                                 {buttonCTA}
209                             </PrimaryButton>
210                         </>
211                     );
212                 }
214                 if (organization.RequiresKey && !organization.HasKeys) {
215                     return (
216                         <>
217                             <SettingsParagraph learnMoreUrl={getKnowledgeBaseUrl('/proton-for-business')}>
218                                 {c('Info')
219                                     .t`Create and manage sub-accounts and assign them email addresses on your custom domain.`}
220                             </SettingsParagraph>
221                             <PrimaryButton
222                                 loading={loading}
223                                 onClick={async () => {
224                                     if (!hasMemberCapablePlan) {
225                                         return createNotification({
226                                             type: 'error',
227                                             text: c('Error')
228                                                 .t`Please upgrade to a business plan with more than 1 user to get multi-user support`,
229                                         });
230                                     }
232                                     const run = async () => {
233                                         const authResult = await showAuthModal();
235                                         // VPN username only users might arrive here through the VPN business plan in protonvpn.com
236                                         if (user.isPrivate && !user.Keys.length && authResult.type === 'srp') {
237                                             const [addresses, domains] = await Promise.all([
238                                                 getAddresses(),
239                                                 api<{
240                                                     Domains: string[];
241                                                 }>(queryAvailableDomains('signup')).then(({ Domains }) => Domains),
242                                             ]);
243                                             const preAuthKTVerifier = createPreAuthKTVerifier(ktActivation);
244                                             const passphrase = await handleSetupAddressKeys({
245                                                 addresses,
246                                                 api,
247                                                 username: user.Name,
248                                                 password: authResult.credentials.password,
249                                                 domains,
250                                                 preAuthKTVerify: preAuthKTVerifier.preAuthKTVerify,
251                                                 productParam: app,
252                                             });
253                                             await innerMutatePassword({
254                                                 api,
255                                                 authentication,
256                                                 keyPassword: passphrase,
257                                                 clearKeyPassword: authResult.credentials.password,
258                                                 User: user,
259                                             });
260                                             await preAuthKTVerifier.preAuthKTCommit(user.ID, api);
261                                         }
263                                         setSetupOrganizationModal(true);
264                                     };
266                                     withLoading(run().catch(noop));
267                                 }}
268                             >{c('Action').t`Enable multi-user support`}</PrimaryButton>
269                         </>
270                     );
271                 }
273                 const organizationName = organization.Name;
274                 const inputLabel = isPartOfFamily
275                     ? c('familyOffer_2023:Label').t`Family name`
276                     : c('Label').t`Organization name`;
277                 const showOrganizationIdentity = Boolean(organizationKey?.privateKey);
279                 return (
280                     <SettingsSection>
281                         <SettingsParagraph>
282                             {canAccessLightLabelling ? (
283                                 <>
284                                     <p className="m-0">{c('Info')
285                                         .t`Add your name and logo to create a more personalized experience for your organization.`}</p>
286                                     <InlineLinkButton onClick={() => setOrganizationLogoTipsModal(true)}>{c(
287                                         'Organization logo upload'
288                                     ).t`Tips on choosing a good logo`}</InlineLinkButton>
289                                 </>
290                             ) : (
291                                 c('Info').t`The name will be visible to your users while they are signed in.`
292                             )}
293                         </SettingsParagraph>
295                         <SettingsLayout>
296                             <SettingsLayoutLeft>
297                                 <Label htmlFor="organization-name-edit-button" className="text-bold pt-0 mb-2 md:mb-0">
298                                     {inputLabel}
299                                 </Label>
300                             </SettingsLayoutLeft>
301                             <SettingsLayoutRight className="pt-2">
302                                 <div className="flex flex-nowrap gap-2">
303                                     {organizationName && <div className="text-ellipsis">{organizationName}</div>}
304                                     <InlineLinkButton
305                                         id="organization-name-edit-button"
306                                         onClick={() => {
307                                             setEditOrganizationNameModal(true);
308                                         }}
309                                         aria-label={c('Action').t`Edit organization name`}
310                                     >
311                                         {c('Action').t`Edit`}
312                                     </InlineLinkButton>
313                                 </div>
314                             </SettingsLayoutRight>
315                         </SettingsLayout>
317                         {showOrganizationIdentity && (
318                             <SettingsLayout>
319                                 <SettingsLayoutLeft>
320                                     <Label
321                                         htmlFor="organization-identity-edit-button"
322                                         className="text-bold pt-0 mb-2 md:mb-0"
323                                     >
324                                         {c('orgidentity').t`Organization identity`}{' '}
325                                         <Info
326                                             title={c('Tooltip')
327                                                 .t`This email address will be shown to all organization members when performing account management operations.`}
328                                             className="mb-1"
329                                         />
330                                     </Label>
331                                 </SettingsLayoutLeft>
332                                 <SettingsLayoutRight className="pt-2 w-full">
333                                     <div className="w-full flex flex-nowrap gap-2">
334                                         {organizationIdentity.signatureAddress && (
335                                             <div className="flex flex-nowrap items-center">
336                                                 <div
337                                                     className="text-ellipsis"
338                                                     data-testid="organization-identity:address"
339                                                 >
340                                                     {organizationIdentity.signatureAddress}
341                                                 </div>
342                                                 <div className="ml-0.5 shrink-0">
343                                                     {organizationIdentity.state.result ? (
344                                                         <Tooltip
345                                                             openDelay={0}
346                                                             title={organizationIdentity.state.result.label}
347                                                         >
348                                                             <Icon
349                                                                 data-testid="organization-identity:icon"
350                                                                 name={organizationIdentity.state.result.icon}
351                                                                 className={organizationIdentity.state.result.className}
352                                                             />
353                                                         </Tooltip>
354                                                     ) : (
355                                                         <CircleLoader />
356                                                     )}
357                                                 </div>
358                                             </div>
359                                         )}
360                                         <InlineLinkButton
361                                             id="organization-identity-edit-button"
362                                             onClick={() => {
363                                                 setEditOrganizationIdentityModal(true);
364                                             }}
365                                             aria-label={c('orgidentity').t`Edit organization identity`}
366                                         >
367                                             {c('Action').t`Edit`}
368                                         </InlineLinkButton>
369                                     </div>
370                                 </SettingsLayoutRight>
371                             </SettingsLayout>
372                         )}
374                         {app === APPS.PROTONACCOUNT && (
375                             <OrganizationLogoUploadUpsellBanner
376                                 organization={organization}
377                                 canAccessLightLabelling={canAccessLightLabelling}
378                                 isPartOfFamily={isPartOfFamily}
379                             />
380                         )}
382                         {canAccessLightLabelling && (
383                             <SettingsLayout>
384                                 <SettingsLayoutLeft>
385                                     <Label htmlFor="organization-logo-edit-button" className="text-bold mb-2 md:mb-0">
386                                         {c('Label').t`Logo`}{' '}
387                                         <Info
388                                             title={c('Tooltip')
389                                                 .t`Users will see your logo instead of the ${BRAND_NAME} icon when signed in on our web apps.`}
390                                             className="mb-1"
391                                         />
392                                     </Label>
393                                 </SettingsLayoutLeft>
394                                 <SettingsLayoutRight className="w-full">
395                                     {organizationTheme.logoURL ? (
396                                         <div className="flex items-center justify-start gap-2 border rounded-lg p-2 w-full">
397                                             <img
398                                                 src={organizationTheme.logoURL}
399                                                 alt=""
400                                                 className="w-custom h-custom border rounded bg-weak"
401                                                 style={{ '--w-custom': '5rem', '--h-custom': '5rem' }}
402                                             />
403                                             <ButtonGroup shape="ghost">
404                                                 <Button
405                                                     id="organization-logo-edit-button"
406                                                     onClick={() => setOrganizationLogoModal(true)}
407                                                 >
408                                                     <Icon name="pen" /> {c('Action').t`Change`}
409                                                 </Button>
411                                                 <Button
412                                                     id="organization-logo-remove-button"
413                                                     onClick={() => setOrganizationLogoRemovalModal(true)}
414                                                 >
415                                                     <Icon name="trash" /> {c('Action').t`Remove`}
416                                                 </Button>
417                                             </ButtonGroup>
418                                         </div>
419                                     ) : (
420                                         <Button
421                                             id="organization-logo-edit-button"
422                                             color="weak"
423                                             shape="outline"
424                                             onClick={() => setOrganizationLogoModal(true)}
425                                         >{c('Action').t`Upload`}</Button>
426                                     )}
427                                 </SettingsLayoutRight>
428                             </SettingsLayout>
429                         )}
430                         {isPartOfFamily && (
431                             <Row>
432                                 <ButtonLike as={SettingsLink} path="/users-addresses">
433                                     {c('familyOffer_2023:Action').t`Invite a user`}
434                                 </ButtonLike>
435                             </Row>
436                         )}
437                     </SettingsSection>
438                 );
439             })()}
440         </>
441     );
444 export default OrganizationSection;