Remove client-side isLoggedIn value
[ProtonMail-WebClient.git] / packages / pass / components / Spotlight / SpotlightProvider.tsx
blob1faa7a8c1ede6f98686172ef12c8870aa33648c8
1 import type { FC, PropsWithChildren } from 'react';
2 import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
4 import { c } from 'ttag';
6 import { usePassCore } from '@proton/pass/components/Core/PassCoreProvider';
7 import { useInviteContext } from '@proton/pass/components/Invite/InviteContext';
8 import { PendingNewUsersApprovalModal } from '@proton/pass/components/Share/PendingNewUsersApprovalModal';
9 import { PendingShareAccessModal } from '@proton/pass/components/Share/PendingShareAccessModal';
10 import type { SpotlightMessageDefinition } from '@proton/pass/components/Spotlight/SpotlightContent';
11 import type { UpsellType } from '@proton/pass/components/Upsell/UpsellingModal';
12 import { UpsellingModal } from '@proton/pass/components/Upsell/UpsellingModal';
13 import type { UpsellRef } from '@proton/pass/constants';
14 import { type MaybeNull, SpotlightMessage } from '@proton/pass/types';
15 import noop from '@proton/utils/noop';
17 import { InviteIcon } from './SpotlightIcon';
19 type UpsellingState = { type: UpsellType; upsellRef: UpsellRef };
21 type SpotlightState = {
22     open: boolean;
23     upselling: MaybeNull<UpsellingState>;
24     pendingShareAccess: boolean;
25     message: MaybeNull<SpotlightMessageDefinition>;
28 export type SpotlightContextValue = {
29     /** Acknowledges the provided spotlight message type.
30      * Resets the SpotlightContext's current message to `null` */
31     acknowledge: (messageType: SpotlightMessage) => void;
32     /** Controls the Pending Share Access modal */
33     setPendingShareAccess: (value: boolean) => void;
34     /** Controls the Upselling modal */
35     setUpselling: (value: MaybeNull<UpsellingState>) => void;
36     /** Sets the current message - if an invite  */
37     setSpotlight: (message: MaybeNull<SpotlightMessageDefinition>) => void;
38     state: SpotlightState;
41 const INITIAL_STATE: SpotlightState = { open: false, message: null, upselling: null, pendingShareAccess: false };
43 export const SpotlightContext = createContext<SpotlightContextValue>({
44     acknowledge: noop,
45     setUpselling: noop,
46     setPendingShareAccess: noop,
47     setSpotlight: noop,
48     state: INITIAL_STATE,
49 });
51 export const SpotlightProvider: FC<PropsWithChildren> = ({ children }) => {
52     const { spotlight } = usePassCore();
53     const { latestInvite, respondToInvite } = useInviteContext();
55     const [state, setState] = useState<SpotlightState>(INITIAL_STATE);
56     const [spotlightMessage, setSpotlightMessage] = useState<MaybeNull<SpotlightMessageDefinition>>(null);
58     const timer = useRef<NodeJS.Timeout>();
59     const messageRef = useRef(state.message);
60     messageRef.current = state.message;
62     const closeUpselling = () => {
63         state.message?.onClose?.();
64         setState((prev) => ({ ...prev, upselling: null }));
65     };
67     const closePendingShareAccess = () => {
68         state.message?.onClose?.();
69         setState((prev) => ({ ...prev, pendingShareAccess: false }));
70     };
72     const setMessage = useCallback((next: MaybeNull<SpotlightMessageDefinition>) => {
73         if (messageRef.current?.id !== next?.id) {
74             setState((curr) => ({ ...curr, open: false }));
75             timer.current = setTimeout(
76                 () => setState((curr) => ({ ...curr, message: next, open: next !== null })),
77                 500
78             );
79         }
80     }, []);
82     const acknowledge = useCallback((spotlightMessageType: SpotlightMessage) => {
83         void spotlight.acknowledge(spotlightMessageType);
84         setSpotlightMessage(null);
85     }, []);
87     const ctx = useMemo<SpotlightContextValue>(
88         () => ({
89             acknowledge,
90             setPendingShareAccess: (pendingShareAccess) => setState((prev) => ({ ...prev, pendingShareAccess })),
91             setUpselling: (upselling) => setState((prev) => ({ ...prev, upselling })),
92             setSpotlight: setSpotlightMessage,
93             state,
94         }),
95         [state]
96     );
98     const inviteMessage = useMemo<MaybeNull<SpotlightMessageDefinition>>(
99         () =>
100             latestInvite && !latestInvite.fromNewUser
101                 ? {
102                       type: SpotlightMessage.NOOP,
103                       mode: 'default',
104                       id: latestInvite.token,
105                       weak: true,
106                       dense: false,
107                       title: c('Title').t`Vault shared with you`,
108                       message: c('Info').t`You're invited to a vault.`,
109                       icon: InviteIcon,
110                       action: {
111                           label: c('Label').t`View details`,
112                           type: 'button',
113                           onClick: () => respondToInvite(latestInvite),
114                       },
115                   }
116                 : null,
117         [latestInvite]
118     );
120     useEffect(() => () => clearTimeout(timer.current), []);
122     useEffect(() => {
123         /** For now, always prioritize invites over spotlight messages */
124         const activeMessage = inviteMessage ?? spotlightMessage;
125         setMessage(activeMessage);
126     }, [inviteMessage, spotlightMessage]);
128     return (
129         <SpotlightContext.Provider value={ctx}>
130             {children}
132             {state.upselling && (
133                 <UpsellingModal
134                     open
135                     onClose={closeUpselling}
136                     upsellType={state.upselling.type}
137                     upsellRef={state.upselling.upsellRef}
138                 />
139             )}
141             <PendingShareAccessModal open={state.pendingShareAccess} onClose={closePendingShareAccess} />
142             <PendingNewUsersApprovalModal />
143         </SpotlightContext.Provider>
144     );
147 export const useSpotlight = () => useContext(SpotlightContext);