1 import { useEffect, useState } from 'react';
3 import { c, msgid } from 'ttag';
5 import type { ThemeColorUnion } from '@proton/colors';
6 import type { IconName } from '@proton/components';
15 } from '@proton/components';
16 import { getAppHref } from '@proton/shared/lib/apps/helper';
17 import { getSlugFromApp } from '@proton/shared/lib/apps/slugHelper';
18 import { APPS, BRAND_NAME, DRIVE_APP_NAME } from '@proton/shared/lib/constants';
19 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
20 import { ChecklistKey } from '@proton/shared/lib/interfaces';
21 import spotlightIcon from '@proton/styles/assets/img/illustrations/spotlight-stars.svg';
22 import clsx from '@proton/utils/clsx';
24 import { useActiveShare } from '../../hooks/drive/useActiveShare';
25 import { useFileUploadInput } from '../../store';
26 import { useFileSharingModal } from '../modals/SelectLinkToShareModal/SelectLinkToShareModal';
27 import { useLinkSharingModal } from '../modals/ShareLinkModal/ShareLinkModal';
28 import useChecklist from './useChecklist';
30 import './GiftFloatingButton.scss';
32 export default function GiftFloatingButton() {
33 const checklist = useChecklist();
34 const { viewportWidth } = useActiveBreakpoint();
36 if (viewportWidth['<=small'] || checklist.isLoading || checklist.expiresInDays === 0 || !checklist.isVisible) {
40 if (checklist.isCompleted) {
41 return <WelcomeActionsDoneSpotlight onSeen={checklist.markCompletedAsSeen} />;
45 <WelcomeActionsSpotlight
46 reloadChecklist={checklist.reload}
47 completedActions={checklist.completedActions}
48 expiresInDays={checklist.expiresInDays}
53 function WelcomeActionsDoneSpotlight({ onSeen }: { onSeen: (dismiss?: boolean) => void }) {
54 const [show, setShow] = useState(false);
56 // Wait a bit so user can see it opening it up (if opened right away,
57 // it might feel as part of the page), and also it allows JS to properly
58 // calculate position of the modal when everything is rendered.
62 // Product wants to mark it seen on backend automatically.
67 const spotlightContent = (
68 <div className="flex flex-nowrap">
69 <figure className="shrink-0 mr-4">
70 <img src={spotlightIcon} alt="" />
73 <h6 className="text-semibold">{c('Title').t`You’ve got 5 GB of storage`}</h6>
74 <div className="mb-4 color-weak">{c('Info')
75 .t`Way to go, you've just increased your storage to 5 GB!`}</div>
81 <FloatingSpotlight content={spotlightContent} show={show} onClick={onSeen} color="success" icon="checkmark" />
85 function WelcomeActionsSpotlight({
90 reloadChecklist: () => void;
91 completedActions: ChecklistKey[];
92 expiresInDays: number;
94 const [showPopup, setShowPopup] = useLocalState(true, 'welcome-actions-spotlight');
95 const [showList, setShowList] = useState(false);
96 const [fileSharingModal, showFileSharingModal] = useFileSharingModal();
97 const [linkSharingModal, showLinkSharingModal] = useLinkSharingModal();
99 const toggleOpen = () => {
100 setShowPopup(false); // Any click will remove automatic popup.
101 setShowList(!showList);
102 // Load latest progress before opening checklist again os user doesnt
103 // need to reload the whole page.
109 const spotlightContent = showPopup ? (
110 <div className="flex flex-nowrap">
111 <figure className="shrink-0 mr-4">
112 <img src={spotlightIcon} alt="" />
115 <h6 className="text-semibold">{c('Title').t`Your 3 GB bonus`}</h6>
116 {/* translator: You have X days left to claim your 3 GB welcome bonus and upgrade your storage */}
118 msgid`You have ${expiresInDays} day left to claim your 3 GB welcome bonus and `,
119 `You have ${expiresInDays} days left to claim your 3 GB welcome bonus and `,
122 <a href={getKnowledgeBaseUrl('/more-storage-proton-drive')} target="_blank">{c('Info')
123 .t`upgrade your storage`}</a>
128 <h6 className="text-semibold">{c('Title').t`Your welcome actions`}</h6>
129 <div className="mb-4 pr-4 color-weak">
130 {c('Info').t`Get to know ${DRIVE_APP_NAME} and earn your 3 GB storage bonus! Take action today.`}
133 completedActions={completedActions}
134 onActionDone={toggleOpen}
135 showFileSharingModal={showFileSharingModal}
136 showLinkSharingModal={showLinkSharingModal}
143 content={spotlightContent}
145 show={showPopup || showList}
147 color={showList ? 'weak' : 'norm'}
148 icon={showList ? 'cross' : 'gift'}
156 function FloatingSpotlight({
164 content: React.ReactNode;
166 color: ThemeColorUnion;
175 originalPlacement="top-end"
176 className="max-w-custom z-up"
177 style={{ '--max-w-custom': '25em' }}
181 // Only way to override fab classes
182 id="gift-floating-button"
183 title={c('Action').t`Your 3 GB bonus`}
186 data-testid="gift-floating-button"
188 <Icon size={5} name={icon} className="m-auto" />
194 function WelcomeActions({
197 showFileSharingModal,
198 showLinkSharingModal,
200 completedActions: ChecklistKey[];
201 onActionDone: () => void;
202 showFileSharingModal: ReturnType<typeof useFileSharingModal>[1];
203 showLinkSharingModal: ReturnType<typeof useLinkSharingModal>[1];
205 const getIconName = (actionName: ChecklistKey, iconName: IconName) => {
206 return completedActions.includes(actionName) ? 'checkmark' : iconName;
209 const { activeFolder } = useActiveShare();
213 handleChange: handleFileChange,
214 } = useFileUploadInput(activeFolder.shareId, activeFolder.linkId);
215 const { getLocalID } = useAuthentication();
219 <WelcomeAction icon="checkmark" title={c('Label').t`Create ${BRAND_NAME} account`} />
231 icon={getIconName(ChecklistKey.DriveUpload, 'arrow-up-line')}
232 title={c('Label').t`Upload your first file`}
233 text={c('Info').t`And access it from anywhere`}
239 icon={getIconName(ChecklistKey.DriveShare, 'user-plus')}
240 title={c('Label').t`Share a file`}
241 text={c('Info').t`It’s easy and secure`}
243 void showFileSharingModal({ shareId: activeFolder.shareId, showLinkSharingModal });
248 icon={getIconName(ChecklistKey.RecoveryMethod, 'key-skeleton')}
249 title={c('Label').t`Set recovery method`}
250 text={c('Info').t`Makes your account safer`}
252 const slug = getSlugFromApp(APPS.PROTONDRIVE);
253 const url = `/${slug}/recovery`;
254 window.open(getAppHref(url, APPS.PROTONACCOUNT, getLocalID()));
262 function WelcomeAction({
273 const [onHover, setOnHover] = useState(false);
275 const isDone = icon === 'checkmark';
279 className={clsx(['flex items-center rounded', !isDone && 'cursor-pointer'])}
280 onClick={isDone ? undefined : action}
281 onMouseEnter={() => setOnHover(true)}
282 onMouseLeave={() => setOnHover(false)}
283 data-testid="welcome-actions"
287 'h-custom w-custom rounded mr-2',
288 'flex justify-center items-center',
289 isDone ? 'bg-success' : 'bg-weak',
291 style={{ '--w-custom': '2.5em', '--h-custom': '2.5em' }}
292 data-testid="welcome-actions-icons"
296 <div className={clsx(['flex-1', isDone && 'text-strike color-weak'])} data-testid="welcome-actions-text">
298 {!isDone && text && <div className="color-weak">{text}</div>}
301 <Icon name={onHover ? 'arrow-right' : 'chevron-right'} className={onHover ? '' : 'color-weak'} />