1 import type { Store } from 'redux';
3 import { ITEM_COUNT_RATING_PROMPT } from '@proton/pass/constants';
5 selectCreatedItemsCount,
7 selectInAppNotificationsEnabled,
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>) =>
26 message: SpotlightMessage.PENDING_SHARE_ACCESS,
28 if (previous) return false;
29 const state = store.getState();
30 const { waitingNewUserInvites } = selectUserState(state);
31 return (waitingNewUserInvites ?? 0) > 0;
35 export const createWelcomeRule = () =>
37 message: SpotlightMessage.WELCOME,
39 if (!DESKTOP_BUILD) return false;
44 export const createPermissionsRule = (checkPermissionsGranted: () => boolean) =>
46 message: SpotlightMessage.PERMISSIONS_REQUIRED,
47 when: () => !checkPermissionsGranted(),
50 export const createStorageIssueRule = (checkStorageFull: () => boolean) =>
52 message: SpotlightMessage.STORAGE_ISSUE,
53 when: () => checkStorageFull(),
56 export const createUpdateRule = (getAvailableUpdate: () => MaybeNull<string>) =>
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() } }),
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;
71 export const createTrialRule = (store: Store<State>) =>
73 message: SpotlightMessage.TRIAL,
75 const passPlan = selectPassPlan(store.getState());
76 return !previous && passPlan === UserPassPlan.TRIAL;
80 export const createB2BRule = (store: Store<State>) =>
82 message: SpotlightMessage.B2B_ONBOARDING,
84 const passPlan = selectPassPlan(store.getState());
85 return !previous && passPlan === UserPassPlan.BUSINESS;
89 export const createSecurityRule = (store: Store<State>) =>
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;
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;
113 export const createMonitorRule = () =>
114 createSpotlightRule({
115 message: SpotlightMessage.PASS_MONITOR,
116 when: (previous) => !previous,
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,
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);
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;
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;