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';
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();
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] =
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] =
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) {
99 const hasMemberCapablePlan = getHasMemberCapablePlan(organization, subscription);
103 {authModal((props) => {
108 config={unlockPasswordChanges()}
109 onCancel={props.onReject}
110 onSuccess={props.onResolve}
115 {renderNewDomain && <DomainModal {...newDomainModalProps} />}
117 {renderOrganizationLogoModal && (
118 <OrganizationLogoModal app={app} size="large" organization={organization} {...organizationLogoModal} />
120 {renderOrganizationLogoRemovalModal && (
121 <OrganizationLogoRemovalModal
124 organization={organization}
125 {...organizationLogoRemovalModal}
128 {renderOrganizationLogoTipsModal && (
129 <OrganizationLogoTipsModal
131 onUploadClick={() => setOrganizationLogoModal(true)}
132 {...organizationLogoTipsModal}
135 {renderEditOrganizationNameModal && (
136 <OrganizationNameModal organization={organization} {...editOrganizationNameProps} />
138 {renderEditOrganizationIdentityModal && (
139 <EditOrganizationIdentityModal
140 signatureAddress={organizationIdentity.signatureAddress}
141 {...editOrganizationIdentityProps}
144 {renderSetupOrganizationModal && <SetupOrganizationModal {...setupOrganizationModalProps} />}
147 if (!hasMemberCapablePlan || !isOrgActive) {
148 return <OrganizationSectionUpsell app={app} />;
151 if (organization.RequiresDomain && customDomains.length === 0) {
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). `}
161 setNewDomainModalOpen(true);
164 {c('Action').t`Add domain`}
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';
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}.`}
193 onClick={async () => {
194 if (!hasMemberCapablePlan) {
195 return createNotification({
198 .t`Please upgrade to a business plan with more than 1 user to get multi-user support`,
203 await showAuthModal();
204 setSetupOrganizationModal(true);
214 if (organization.RequiresKey && !organization.HasKeys) {
217 <SettingsParagraph learnMoreUrl={getKnowledgeBaseUrl('/proton-for-business')}>
219 .t`Create and manage sub-accounts and assign them email addresses on your custom domain.`}
223 onClick={async () => {
224 if (!hasMemberCapablePlan) {
225 return createNotification({
228 .t`Please upgrade to a business plan with more than 1 user to get multi-user support`,
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([
241 }>(queryAvailableDomains('signup')).then(({ Domains }) => Domains),
243 const preAuthKTVerifier = createPreAuthKTVerifier(ktActivation);
244 const passphrase = await handleSetupAddressKeys({
248 password: authResult.credentials.password,
250 preAuthKTVerify: preAuthKTVerifier.preAuthKTVerify,
253 await innerMutatePassword({
256 keyPassword: passphrase,
257 clearKeyPassword: authResult.credentials.password,
260 await preAuthKTVerifier.preAuthKTCommit(user.ID, api);
263 setSetupOrganizationModal(true);
266 withLoading(run().catch(noop));
268 >{c('Action').t`Enable multi-user support`}</PrimaryButton>
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);
282 {canAccessLightLabelling ? (
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>
291 c('Info').t`The name will be visible to your users while they are signed in.`
297 <Label htmlFor="organization-name-edit-button" className="text-bold pt-0 mb-2 md:mb-0">
300 </SettingsLayoutLeft>
301 <SettingsLayoutRight className="pt-2">
302 <div className="flex flex-nowrap gap-2">
303 {organizationName && <div className="text-ellipsis">{organizationName}</div>}
305 id="organization-name-edit-button"
307 setEditOrganizationNameModal(true);
309 aria-label={c('Action').t`Edit organization name`}
311 {c('Action').t`Edit`}
314 </SettingsLayoutRight>
317 {showOrganizationIdentity && (
321 htmlFor="organization-identity-edit-button"
322 className="text-bold pt-0 mb-2 md:mb-0"
324 {c('orgidentity').t`Organization identity`}{' '}
327 .t`This email address will be shown to all organization members when performing account management operations.`}
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">
337 className="text-ellipsis"
338 data-testid="organization-identity:address"
340 {organizationIdentity.signatureAddress}
342 <div className="ml-0.5 shrink-0">
343 {organizationIdentity.state.result ? (
346 title={organizationIdentity.state.result.label}
349 data-testid="organization-identity:icon"
350 name={organizationIdentity.state.result.icon}
351 className={organizationIdentity.state.result.className}
361 id="organization-identity-edit-button"
363 setEditOrganizationIdentityModal(true);
365 aria-label={c('orgidentity').t`Edit organization identity`}
367 {c('Action').t`Edit`}
370 </SettingsLayoutRight>
374 {app === APPS.PROTONACCOUNT && (
375 <OrganizationLogoUploadUpsellBanner
376 organization={organization}
377 canAccessLightLabelling={canAccessLightLabelling}
378 isPartOfFamily={isPartOfFamily}
382 {canAccessLightLabelling && (
385 <Label htmlFor="organization-logo-edit-button" className="text-bold mb-2 md:mb-0">
386 {c('Label').t`Logo`}{' '}
389 .t`Users will see your logo instead of the ${BRAND_NAME} icon when signed in on our web apps.`}
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">
398 src={organizationTheme.logoURL}
400 className="w-custom h-custom border rounded bg-weak"
401 style={{ '--w-custom': '5rem', '--h-custom': '5rem' }}
403 <ButtonGroup shape="ghost">
405 id="organization-logo-edit-button"
406 onClick={() => setOrganizationLogoModal(true)}
408 <Icon name="pen" /> {c('Action').t`Change`}
412 id="organization-logo-remove-button"
413 onClick={() => setOrganizationLogoRemovalModal(true)}
415 <Icon name="trash" /> {c('Action').t`Remove`}
421 id="organization-logo-edit-button"
424 onClick={() => setOrganizationLogoModal(true)}
425 >{c('Action').t`Upload`}</Button>
427 </SettingsLayoutRight>
432 <ButtonLike as={SettingsLink} path="/users-addresses">
433 {c('familyOffer_2023:Action').t`Invite a user`}
444 export default OrganizationSection;