Merge branch 'IDTEAM-1.26.0' into 'main'
[ProtonMail-WebClient.git] / packages / pass / lib / spotlight / rules.ts
blobf245a66eb57abaec36fd1690addae2f4a4658117
1 import type { Store } from 'redux';
3 import { ITEM_COUNT_RATING_PROMPT } from '@proton/pass/constants';
4 import {
5     selectCreatedItemsCount,
6     selectFeatureFlag,
7     selectInAppNotificationsEnabled,
8     selectLockEnabled,
9     selectPassPlan,
10     selectUserData,
11     selectUserPlan,
12     selectUserState,
13 } from '@proton/pass/store/selectors';
14 import type { State } from '@proton/pass/store/types';
15 import { type Maybe, type MaybeNull, PlanType, SpotlightMessage } from '@proton/pass/types';
16 import { PassFeature } from '@proton/pass/types/api/features';
17 import { UserPassPlan } from '@proton/pass/types/api/plan';
18 import { merge } from '@proton/pass/utils/object/merge';
19 import { UNIX_DAY, UNIX_MONTH, UNIX_WEEK } from '@proton/pass/utils/time/constants';
20 import { getEpoch } from '@proton/pass/utils/time/epoch';
22 import { createSpotlightRule } from './service';
24 export const createPendingShareAccessRule = (store: Store<State>) =>
25     createSpotlightRule({
26         message: SpotlightMessage.PENDING_SHARE_ACCESS,
27         when: (previous) => {
28             if (previous) return false;
29             const state = store.getState();
30             const { waitingNewUserInvites } = selectUserState(state);
31             return (waitingNewUserInvites ?? 0) > 0;
32         },
33     });
35 export const createWelcomeRule = () =>
36     createSpotlightRule({
37         message: SpotlightMessage.WELCOME,
38         when: (previous) => {
39             if (!DESKTOP_BUILD) return false;
40             return !previous;
41         },
42     });
44 export const createPermissionsRule = (checkPermissionsGranted: () => boolean) =>
45     createSpotlightRule({
46         message: SpotlightMessage.PERMISSIONS_REQUIRED,
47         when: () => !checkPermissionsGranted(),
48     });
50 export const createStorageIssueRule = (checkStorageFull: () => boolean) =>
51     createSpotlightRule({
52         message: SpotlightMessage.STORAGE_ISSUE,
53         when: () => checkStorageFull(),
54     });
56 export const createUpdateRule = (getAvailableUpdate: () => MaybeNull<string>) =>
57     createSpotlightRule({
58         message: SpotlightMessage.UPDATE_AVAILABLE,
59         /* keep a reference to the current available update so as
60          * not to re-prompt the user with this update if ignored */
61         onAcknowledge: (ack) => merge(ack, { extraData: { version: getAvailableUpdate() } }),
62         when: (previous) => {
63             const availableVersion = getAvailableUpdate();
64             const previousAckVersion = previous?.extraData?.version as MaybeNull<Maybe<string>>;
65             const shouldPrompt = !previous || previousAckVersion !== availableVersion;
67             return availableVersion !== null && shouldPrompt;
68         },
69     });
71 export const createTrialRule = (store: Store<State>) =>
72     createSpotlightRule({
73         message: SpotlightMessage.TRIAL,
74         when: (previous) => {
75             const passPlan = selectPassPlan(store.getState());
76             return !previous && passPlan === UserPassPlan.TRIAL;
77         },
78     });
80 export const createB2BRule = (store: Store<State>) =>
81     createSpotlightRule({
82         message: SpotlightMessage.B2B_ONBOARDING,
83         when: (previous) => {
84             const passPlan = selectPassPlan(store.getState());
85             return !previous && passPlan === UserPassPlan.BUSINESS;
86         },
87     });
89 export const createSecurityRule = (store: Store<State>) =>
90     createSpotlightRule({
91         message: SpotlightMessage.SECURE_EXTENSION,
92         when: (previous, { installedOn }) => {
93             /* should prompt only if user has no extension lock AND
94              * message was not previously acknowledged AND user has
95              * installed at least one day ago */
96             const now = getEpoch();
97             const lockEnabled = selectLockEnabled(store.getState());
98             const shouldPrompt = !previous && now - installedOn > UNIX_DAY;
100             return !lockEnabled && shouldPrompt;
101         },
102     });
104 export const createUserRatingRule = (store: Store<State>) =>
105     createSpotlightRule({
106         message: SpotlightMessage.USER_RATING,
107         when: (previous) => {
108             const createdItemsCount = selectCreatedItemsCount(store.getState());
109             return !previous && createdItemsCount >= ITEM_COUNT_RATING_PROMPT;
110         },
111     });
113 export const createMonitorRule = () =>
114     createSpotlightRule({
115         message: SpotlightMessage.PASS_MONITOR,
116         when: (previous) => !previous,
117     });
119 export const createMonitorLearnMoreRule = () =>
120     createSpotlightRule({
121         message: SpotlightMessage.PASS_MONITOR_LEARN_MORE,
122         onAcknowledge: (ack) => merge(ack, { extraData: { expanded: !ack.extraData?.expanded } }),
123         when: (previous) => !previous || !previous.extraData?.expanded,
124     });
126 export const createFamilyPlanPromo2024Rule = (store: Store<State>) =>
127     createSpotlightRule({
128         message: SpotlightMessage.FAMILY_PLAN_PROMO_2024,
129         when: (previous) => {
130             if (previous) return false;
132             const state = store.getState();
133             if (!selectInAppNotificationsEnabled(state)) return false;
135             const enabled = selectFeatureFlag(PassFeature.PassFamilyPlanPromo2024)(state);
136             const plan = selectUserPlan(state);
137             const cohortPass2023 = plan?.InternalName === 'pass2023';
138             const cohortPassFree = plan?.InternalName === 'free';
140             return enabled && (cohortPass2023 || cohortPassFree);
141         },
142     });
144 export const createAliasSyncEnableRule = (store: Store<State>) =>
145     createSpotlightRule({
146         message: SpotlightMessage.ALIAS_SYNC_ENABLE,
147         when: (previous) => {
148             const state = store.getState();
149             const enabled = selectFeatureFlag(PassFeature.PassSimpleLoginAliasesSync)(state);
150             const { pendingAliasToSync } = selectUserData(state);
152             return enabled && !previous && pendingAliasToSync > 0;
153         },
154     });
156 export const createUserRenewalRule = (store: Store<State>) =>
157     createSpotlightRule({
158         message: SpotlightMessage.USER_RENEWAL,
159         when: (previous) => {
160             const plan = selectUserPlan(store.getState());
162             // Do not show if it's not a Plus plan or can't manage subscription or is already renewing
163             if (plan?.Type !== PlanType.plus || !plan?.ManageSubscription || plan?.SubscriptionRenewal) return false;
165             const now = getEpoch();
167             // If acknowledged, show it again after two weeks
168             if (previous) return now - previous.acknowledgedOn > UNIX_WEEK * 2;
170             const subscriptionEnd = plan?.SubscriptionEnd ?? 0;
172             // Prevent showing the banner if the plan type has not been
173             // updated to 'Free' after the subscription date has passed
174             if (subscriptionEnd < now) return false;
176             return subscriptionEnd - now < UNIX_MONTH;
177         },
178     });