[DRVWEB-4160] Add telemetry for bookmarks feature
[ProtonMail-WebClient.git] / applications / drive / src / app / containers / PublicSharedLinkContainer.tsx
blob13b8f72a03f41f4cd789b8eccc19e2da6aef4c84
1 import { useEffect, useState } from 'react';
3 import { c } from 'ttag';
5 import { LocationErrorBoundary } from '@proton/components';
6 import { useLoading } from '@proton/hooks';
7 import metrics from '@proton/metrics';
8 import { getApiError } from '@proton/shared/lib/api/helpers/apiErrorHelper';
10 import { ErrorPage, LoadingPage, PasswordPage, SharedFilePage, SharedFolderPage } from '../components/SharedPage';
11 import { useSignupFlowModal } from '../components/modals/SignupFlowModal/SignupFlowModal';
12 import { useUpsellFloatingModal } from '../components/modals/UpsellFloatingModal';
13 import usePublicToken from '../hooks/drive/usePublicToken';
14 import type { DecryptedLink } from '../store';
15 import { PublicDriveProvider, useBookmarksPublicView, useDownload, usePublicAuth, usePublicShare } from '../store';
16 import { useDriveShareURLBookmarkingFeatureFlag } from '../store/_bookmarks/useDriveShareURLBookmarking';
17 import { sendErrorReport } from '../utils/errorHandling';
18 import { getErrorMetricType } from '../utils/errorHandling/apiErrors';
19 import { Actions, countActionWithTelemetry } from '../utils/telemetry';
20 import type { ErrorTuple } from '../utils/type/ErrorTuple';
21 import { deleteStoredUrlPassword } from '../utils/url/password';
23 export default function PublicSharedLinkContainer() {
24     return (
25         <LocationErrorBoundary>
26             <PublicDriveProvider>
27                 <PublicShareLinkInitContainer />
28             </PublicDriveProvider>
29         </LocationErrorBoundary>
30     );
33 /**
34  * PublicShareLinkInitContainer initiate public session for shared link.
35  * That is to initiate SRP handshake and ask for password if needed to
36  * initiate session itself.
37  */
38 function PublicShareLinkInitContainer() {
39     const { clearDownloads } = useDownload();
40     const { token, urlPassword } = usePublicToken();
41     const {
42         isLoading,
43         customPassword,
44         error: [authError, authErrorMessage],
45         isLegacy,
46         isPasswordNeeded,
47         submitPassword,
48     } = usePublicAuth(token, urlPassword);
49     const bookmarksPublicView = useBookmarksPublicView(customPassword);
50     const isDriveShareUrlBookmarkingEnabled = useDriveShareURLBookmarkingFeatureFlag();
51     const [isLoadingDecrypt, withLoading, setLoading] = useLoading(true);
52     const [[publicShareError, publicShareErrorMessage], setError] = useState<ErrorTuple>([, '']);
53     const [link, setLink] = useState<DecryptedLink>();
54     const { loadPublicShare, user, isUserLoading } = usePublicShare();
55     const [signUpFlowModal, showSignUpFlowModal] = useSignupFlowModal();
56     const [renderUpsellFloatingModal] = useUpsellFloatingModal();
58     const isLoggedIn = !!user;
59     const error: ErrorTuple[0] = authError || publicShareError;
60     const errorMessage: ErrorTuple[1] = authErrorMessage || publicShareErrorMessage;
62     // If password to the share was changed, page need to reload everything.
63     // In such case we need to also clear all downloads to not keep anything
64     // from before.
65     useEffect(() => {
66         if (isLoading) {
67             clearDownloads();
68         }
69     }, [isLoading]);
71     useEffect(() => {
72         // Always delete saved public share URL when browsing a public share url
73         deleteStoredUrlPassword();
74     }, []);
76     useEffect(() => {
77         if (errorMessage) {
78             sendErrorReport(new Error(errorMessage));
79         }
80         if (error) {
81             metrics.drive_public_share_load_error_total.increment({
82                 type: !link ? 'unknown' : link.isFile ? 'file' : 'folder',
83                 plan: user?.isPaid ? 'paid' : user?.isFree ? 'free' : 'not_recognized',
84                 error: getErrorMetricType(error),
85             });
86         }
87     }, [errorMessage, error]);
89     useEffect(() => {
90         const abortController = new AbortController();
92         if (token && !isLoading && !authErrorMessage && !isPasswordNeeded && !isUserLoading) {
93             void withLoading(
94                 loadPublicShare(abortController.signal)
95                     .then(({ link }) => {
96                         setLink(link);
97                         metrics.drive_public_share_load_success_total.increment({
98                             type: link.isFile ? 'file' : 'folder',
99                             plan: user?.isPaid ? 'paid' : user?.isFree ? 'free' : 'not_recognized',
100                         });
101                         countActionWithTelemetry(Actions.PublicLinkVisit);
102                     })
103                     .catch((error) => {
104                         console.error(error);
105                         const apiError = getApiError(error);
106                         setError([error, apiError.message || error.message || c('Info').t`Cannot load shared link`]);
107                     })
108             );
109         } else if (authErrorMessage) {
110             setLoading(false);
111         }
113         return () => {
114             abortController.abort();
115         };
116     }, [token, isLoading, authErrorMessage, isPasswordNeeded, isUserLoading]);
118     useEffect(() => {
119         /** If the navigation appears from a non proton user and the flag is enabled, we display a sign-up flow modal */
120         if (isDriveShareUrlBookmarkingEnabled && !isLoggedIn && !isUserLoading) {
121             showSignUpFlowModal({ urlPassword });
122         }
123     }, [isDriveShareUrlBookmarkingEnabled, isLoggedIn, isUserLoading]);
125     const showLoadingPage = isLoading || isLoadingDecrypt;
126     const showErrorPage = errorMessage || (showLoadingPage === false && link === undefined);
128     if (isPasswordNeeded) {
129         return <PasswordPage submitPassword={submitPassword} />;
130     }
132     if (showLoadingPage) {
133         return <LoadingPage />;
134     }
136     if (showErrorPage || !link) {
137         return <ErrorPage />;
138     }
140     return (
141         <>
142             {link.isFile ? (
143                 <SharedFilePage
144                     bookmarksPublicView={bookmarksPublicView}
145                     token={token}
146                     link={link}
147                     hideSaveToDrive={!isDriveShareUrlBookmarkingEnabled || isLegacy}
148                 />
149             ) : (
150                 <SharedFolderPage
151                     bookmarksPublicView={bookmarksPublicView}
152                     token={token}
153                     rootLink={link}
154                     hideSaveToDrive={!isDriveShareUrlBookmarkingEnabled || isLegacy}
155                 />
156             )}
157             {isDriveShareUrlBookmarkingEnabled && !isLoggedIn ? signUpFlowModal : renderUpsellFloatingModal}
158         </>
159     );