1 import { useEffect, useLayoutEffect, useState } from 'react';
3 import { c } from 'ttag';
5 import { Button, ButtonLike } from '@proton/atoms';
6 import type { ModalProps } from '@proton/components';
7 import { DriveLogo, Icon, Tooltip, useActiveBreakpoint, useModalTwoStatic } from '@proton/components';
8 import Dialog from '@proton/components/components/dialog/Dialog';
9 import { Portal } from '@proton/components/components/portal';
10 import usePrevious from '@proton/hooks/usePrevious';
11 import { modalTwoRootClassName } from '@proton/shared/lib/busy';
12 import { DRIVE_APP_NAME } from '@proton/shared/lib/constants';
13 import { DRIVE_SIGNUP } from '@proton/shared/lib/drive/urls';
14 import clsx from '@proton/utils/clsx';
16 import { useDownload, usePublicSessionUser } from '../../../store';
18 import './UpsellFloatingModal.scss';
20 interface ChildProps {
26 const UpsellFloatingModalContent = ({ onClose }: Pick<ChildProps, 'onClose'>) => {
28 <div className="upsell-floating-modal-container relative">
30 className="upsell-floating-modal-tooltip absolute right-0 top-0 mr-1"
31 title={c('Action').t`Close`}
34 <Button className="shrink-0" icon shape="ghost" data-testid="upsell-floating-modal:close">
35 <Icon className="modal-close-icon" name="cross-big" alt={c('Action').t`Close`} />
38 <div className="py-3 px-4">
39 <div className="flex flex-nowrap items-center gap-2">
40 <DriveLogo variant="glyph-only" />
41 <div className="flex-1">
42 <h4 className="text-bold">{c('Info').t`Try ${DRIVE_APP_NAME}: Free forever`}</h4>
43 <div className="flex flex-nowrap items-center gap-6">
44 <p className="m-0 mt-1 flex-1 max-w-custom" style={{ '--max-w-custom': '12.5em' }}>
45 {c('Info').t`With ${DRIVE_APP_NAME} your data is protected by end-to-end encryption.`}
47 <ButtonLike as="a" href={DRIVE_SIGNUP} target="_blank" color="norm" shape="outline">{c(
49 ).t`Get Started`}</ButtonLike>
64 const UpsellFloatingModal = ({ open, onClose }: ChildProps & ModalProps) => {
65 const [exit, setExit] = useState(() => (open ? ExitState.idle : ExitState.exited));
66 const active = exit !== ExitState.exited;
67 const previousOpen = usePrevious(open);
68 const { hasDownloads } = useDownload();
70 // Hide Upsell if download is in progress
74 setExit(ExitState.exited);
76 }, [onClose, hasDownloads]);
78 useLayoutEffect(() => {
79 if (!previousOpen && open) {
80 setExit(ExitState.idle);
81 } else if (previousOpen && !open) {
82 setExit(ExitState.exiting);
84 }, [previousOpen, open, active]);
90 const exiting = exit === ExitState.exiting;
95 modalTwoRootClassName,
96 'upsell-floating-modal mr-4 mb-2 p-0',
97 exiting && 'modal-two--out'
99 onAnimationEnd={({ animationName }) => {
100 if (exiting && animationName === 'anime-modal-two-out') {
101 setExit(ExitState.exited);
104 data-testid="upsell-floating-modal"
106 <Dialog className="modal-two-dialog upsell-floating-modal-dialog border border-primary rounded">
107 <div className="modal-two-dialog-container">
108 <UpsellFloatingModalContent onClose={onClose} />
116 export default UpsellFloatingModal;
118 export const useUpsellFloatingModal = () => {
119 const user = usePublicSessionUser();
120 const [renderUpsellFloatingModal, showUpsellFloatingModal] = useModalTwoStatic(UpsellFloatingModal);
122 const { viewportWidth } = useActiveBreakpoint();
124 // If user is proton user or on mobile we disable upsell modal
125 const hideModal = viewportWidth['<=small'] || !!user;
131 showUpsellFloatingModal({});
132 }, [hideModal, showUpsellFloatingModal]);
134 return [hideModal ? null : renderUpsellFloatingModal, showUpsellFloatingModal] as const;