Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / components / onboarding / GiftFloatingButton.tsx
blob28f7cd141c7f033138ab973569a58968a17decdf
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';
7 import {
8     FloatingButton,
9     Icon,
10     Row,
11     Spotlight,
12     useActiveBreakpoint,
13     useAuthentication,
14     useLocalState,
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) {
37         return null;
38     }
40     if (checklist.isCompleted) {
41         return <WelcomeActionsDoneSpotlight onSeen={checklist.markCompletedAsSeen} />;
42     }
44     return (
45         <WelcomeActionsSpotlight
46             reloadChecklist={checklist.reload}
47             completedActions={checklist.completedActions}
48             expiresInDays={checklist.expiresInDays}
49         />
50     );
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.
59     useEffect(() => {
60         setTimeout(() => {
61             setShow(true);
62             // Product wants to mark it seen on backend automatically.
63             onSeen(false);
64         }, 1000);
65     }, []);
67     const spotlightContent = (
68         <div className="flex flex-nowrap">
69             <figure className="shrink-0 mr-4">
70                 <img src={spotlightIcon} alt="" />
71             </figure>
72             <div>
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>
76             </div>
77         </div>
78     );
80     return (
81         <FloatingSpotlight content={spotlightContent} show={show} onClick={onSeen} color="success" icon="checkmark" />
82     );
85 function WelcomeActionsSpotlight({
86     reloadChecklist,
87     completedActions,
88     expiresInDays,
89 }: {
90     reloadChecklist: () => void;
91     completedActions: ChecklistKey[];
92     expiresInDays: number;
93 }) {
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.
104         if (!showList) {
105             reloadChecklist();
106         }
107     };
109     const spotlightContent = showPopup ? (
110         <div className="flex flex-nowrap">
111             <figure className="shrink-0 mr-4">
112                 <img src={spotlightIcon} alt="" />
113             </figure>
114             <div>
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 */}
117                 {c('Info').ngettext(
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 `,
120                     expiresInDays
121                 )}
122                 <a href={getKnowledgeBaseUrl('/more-storage-proton-drive')} target="_blank">{c('Info')
123                     .t`upgrade your storage`}</a>
124             </div>
125         </div>
126     ) : (
127         <div>
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.`}
131             </div>
132             <WelcomeActions
133                 completedActions={completedActions}
134                 onActionDone={toggleOpen}
135                 showFileSharingModal={showFileSharingModal}
136                 showLinkSharingModal={showLinkSharingModal}
137             />
138         </div>
139     );
140     return (
141         <>
142             <FloatingSpotlight
143                 content={spotlightContent}
144                 hasClose={showPopup}
145                 show={showPopup || showList}
146                 onClick={toggleOpen}
147                 color={showList ? 'weak' : 'norm'}
148                 icon={showList ? 'cross' : 'gift'}
149             />
150             {fileSharingModal}
151             {linkSharingModal}
152         </>
153     );
156 function FloatingSpotlight({
157     content,
158     show,
159     color,
160     icon,
161     hasClose = false,
162     onClick,
163 }: {
164     content: React.ReactNode;
165     show: boolean;
166     color: ThemeColorUnion;
167     icon: IconName;
168     hasClose?: boolean;
169     onClick: () => void;
170 }) {
171     return (
172         <Spotlight
173             content={content}
174             show={show}
175             originalPlacement="top-end"
176             className="max-w-custom z-up"
177             style={{ '--max-w-custom': '25em' }}
178             hasClose={hasClose}
179         >
180             <FloatingButton
181                 // Only way to override fab classes
182                 id="gift-floating-button"
183                 title={c('Action').t`Your 3 GB bonus`}
184                 onClick={onClick}
185                 color={color}
186                 data-testid="gift-floating-button"
187             >
188                 <Icon size={5} name={icon} className="m-auto" />
189             </FloatingButton>
190         </Spotlight>
191     );
194 function WelcomeActions({
195     completedActions,
196     onActionDone,
197     showFileSharingModal,
198     showLinkSharingModal,
199 }: {
200     completedActions: ChecklistKey[];
201     onActionDone: () => void;
202     showFileSharingModal: ReturnType<typeof useFileSharingModal>[1];
203     showLinkSharingModal: ReturnType<typeof useLinkSharingModal>[1];
204 }) {
205     const getIconName = (actionName: ChecklistKey, iconName: IconName) => {
206         return completedActions.includes(actionName) ? 'checkmark' : iconName;
207     };
209     const { activeFolder } = useActiveShare();
210     const {
211         inputRef: fileInput,
212         handleClick,
213         handleChange: handleFileChange,
214     } = useFileUploadInput(activeFolder.shareId, activeFolder.linkId);
215     const { getLocalID } = useAuthentication();
217     return (
218         <>
219             <WelcomeAction icon="checkmark" title={c('Label').t`Create ${BRAND_NAME} account`} />
220             <input
221                 multiple
222                 type="file"
223                 ref={fileInput}
224                 className="hidden"
225                 onChange={(e) => {
226                     handleFileChange(e);
227                     onActionDone();
228                 }}
229             />
230             <WelcomeAction
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`}
234                 action={() => {
235                     handleClick();
236                 }}
237             />
238             <WelcomeAction
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`}
242                 action={() => {
243                     void showFileSharingModal({ shareId: activeFolder.shareId, showLinkSharingModal });
244                     onActionDone();
245                 }}
246             />
247             <WelcomeAction
248                 icon={getIconName(ChecklistKey.RecoveryMethod, 'key-skeleton')}
249                 title={c('Label').t`Set recovery method`}
250                 text={c('Info').t`Makes your account safer`}
251                 action={() => {
252                     const slug = getSlugFromApp(APPS.PROTONDRIVE);
253                     const url = `/${slug}/recovery`;
254                     window.open(getAppHref(url, APPS.PROTONACCOUNT, getLocalID()));
255                     onActionDone();
256                 }}
257             />
258         </>
259     );
262 function WelcomeAction({
263     icon,
264     title,
265     text,
266     action,
267 }: {
268     icon: IconName;
269     title: string;
270     text?: string;
271     action?: () => void;
272 }) {
273     const [onHover, setOnHover] = useState(false);
275     const isDone = icon === 'checkmark';
277     return (
278         <Row
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"
284         >
285             <div
286                 className={clsx([
287                     'h-custom w-custom rounded mr-2',
288                     'flex justify-center items-center',
289                     isDone ? 'bg-success' : 'bg-weak',
290                 ])}
291                 style={{ '--w-custom': '2.5em', '--h-custom': '2.5em' }}
292                 data-testid="welcome-actions-icons"
293             >
294                 <Icon name={icon} />
295             </div>
296             <div className={clsx(['flex-1', isDone && 'text-strike color-weak'])} data-testid="welcome-actions-text">
297                 {title}
298                 {!isDone && text && <div className="color-weak">{text}</div>}
299             </div>
300             {!isDone && (
301                 <Icon name={onHover ? 'arrow-right' : 'chevron-right'} className={onHover ? '' : 'color-weak'} />
302             )}
303         </Row>
304     );