From d2f491ed661c4e3bf9a9600a45910f14a4ff4c25 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Tue, 15 Jul 2025 11:07:18 +0500 Subject: [PATCH] common: add feature limits for app lock and note versions --- .../common/src/utils/is-feature-available.ts | 191 +++++------------- 1 file changed, 56 insertions(+), 135 deletions(-) diff --git a/packages/common/src/utils/is-feature-available.ts b/packages/common/src/utils/is-feature-available.ts index 6c743e913..0a1cdd131 100644 --- a/packages/common/src/utils/is-feature-available.ts +++ b/packages/common/src/utils/is-feature-available.ts @@ -103,21 +103,10 @@ const features = { legacyPro: createLimit(true, alwaysTrue) } }), - // checkList: createFeature({ - // id: "checkList", - // title: "Check list", - // availability: { - // free: createLimit(true, alwaysTrue), - // essential: createLimit(true, alwaysTrue), - // pro: createLimit(true, alwaysTrue), - // believer: createLimit(true, alwaysTrue), - // legacyPro: createLimit(true, alwaysTrue) - // } - // }), - - noteLink: createFeature({ + blockLinking: createFeature({ id: "blockLinking", title: "Block-level note links", + error: () => `Block-level note links are not available on this plan.`, availability: { free: createLimit(false, alwaysFalse), essential: createLimit(true, alwaysTrue), @@ -241,17 +230,6 @@ const features = { legacyPro: createLimit("∞", alwaysInfinite) } }), - // noteHistory: createFeature({ - // id: "noteHistory", - // title: "Note history", - // availability: { - // free: createLimit("Local", alwaysTrue), - // essential: createLimit("Local", alwaysTrue), - // pro: createLimit("Synced", alwaysTrue), - // believer: createLimit("Synced", alwaysTrue), - // legacyPro: createLimit("Synced", alwaysTrue) - // } - // }), defaultNotebookAndTag: createFeature({ id: "defaultNotebookAndTag", title: "Default notebook & tag", @@ -264,17 +242,6 @@ const features = { legacyPro: createLimit(true, alwaysTrue) } }), - // securityKeyAppLock: createFeature({ - // id: "securityKeyAppLock", - // title: "Security key app lock", - // availability: { - // free: createLimit(false, alwaysFalse), - // essential: createLimit(false, alwaysFalse), - // pro: createLimit(true, alwaysTrue), - // believer: createLimit(true, alwaysTrue), - // legacyPro: createLimit(true, alwaysTrue) - // } - // }), recurringReminders: createFeature({ id: "recurringReminders", title: "Recurring reminders", @@ -319,6 +286,17 @@ const features = { legacyPro: createLimit(true, alwaysTrue) } }), + customHomepage: createFeature({ + id: "customHomepage", + title: "Custom homepage", + availability: { + free: createLimit(false, alwaysFalse), + essential: createLimit(false, alwaysFalse), + pro: createLimit(true, alwaysTrue), + believer: createLimit(true, alwaysTrue), + legacyPro: createLimit(true, alwaysTrue) + } + }), markdownShortcuts: createFeature({ id: "markdownShortcuts", title: "Markdown shortcuts", @@ -373,102 +351,36 @@ const features = { believer: createLimit(true, alwaysTrue), legacyPro: createLimit(true, alwaysTrue) } + }), + appLock: createFeature({ + id: "appLock", + title: "App lock", + availability: { + free: createLimit(false, alwaysFalse), + essential: createLimit(false, alwaysFalse), + pro: createLimit(true, alwaysTrue), + believer: createLimit(true, alwaysTrue), + legacyPro: createLimit(true, alwaysTrue) + } + }), + maxNoteVersions: createFeature({ + id: "maxNoteVersions", + title: "Maximum note versions", + availability: { + free: createLimit(100, lte(100)), + essential: createLimit(1000, lte(1000)), + pro: createLimit("∞", alwaysInfinite), + believer: createLimit("∞", alwaysInfinite), + legacyPro: createLimit("∞", alwaysInfinite) + } }) - // filesInMonograph: createFeature({ - // id: "filesInMonograph", - // title: "Files in monograph", - // availability: { - // free: createLimit("Images", alwaysTrue), - // essential: createLimit("Images & files", alwaysTrue), - // pro: createLimit("Images & files", alwaysTrue), - // believer: createLimit("Images & files", alwaysTrue), - // legacyPro: createLimit("Images & files", alwaysTrue) - // } - // }), - // noteHistoryRetention: createFeature({ - // id: "noteHistoryRetention", - // title: "Note history retention", - // availability: { - // free: createLimit("1 month", alwaysTrue), - // essential: createLimit("∞", alwaysInfinite), - // pro: createLimit("∞", alwaysInfinite), - // believer: createLimit("∞", alwaysInfinite), - // legacyPro: createLimit("∞", alwaysInfinite) - // } - // }), - // appLock: createFeature({ - // id: "appLockDelay", - // title: "App lock delay", - // availability: { - // free: createLimit("immediate", alwaysTrue), - // essential: createLimit("immediate", alwaysTrue), - // pro: createLimit("any", alwaysTrue), - // believer: createLimit("any", alwaysTrue), - // legacyPro: createLimit("any", alwaysTrue) - // } - // }), - // passwordOnMonograph: createFeature({ - // id: "passwordOnMonograph", - // title: "Password on monograph", - // availability: { - // free: createLimit("Default", alwaysTrue), - // essential: createLimit("Custom", alwaysTrue), - // pro: createLimit("Custom", alwaysTrue), - // believer: createLimit("Custom", alwaysTrue), - // legacyPro: createLimit("Custom", alwaysTrue) - // } - // }), - // voiceMemos: createFeature({ - // id: "voiceMemos", - // title: "Voice memos", - // availability: { - // free: createLimit("5 minute", lte(5)), - // essential: createLimit("1 hour", lte(60)), - // pro: createLimit("∞", alwaysInfinite), - // believer: createLimit(true, alwaysTrue), - // legacyPro: createLimit(true, alwaysTrue) - // } - // }), - // notebookPublishing: createFeature({ - // id: "notebookPublishing", - // title: "Notebook publishing", - // availability: { - // free: createLimit(1, lte(1)), - // essential: createLimit(2, lte(2)), - // pro: createLimit(5, lte(5)), - // believer: createLimit(true, alwaysTrue), - // legacyPro: createLimit(true, alwaysTrue) - // } - // }), - // vaults: createFeature({ - // id: "vaults", - // title: "Vaults", - // availability: { - // free: createLimit(1, lte(1)), - // essential: createLimit(3, lte(3)), - // pro: createLimit("∞", alwaysInfinite), - // believer: createLimit("∞", alwaysInfinite), - // legacyPro: createLimit("∞", alwaysInfinite) - // } - // }) }; export async function isFeatureAvailable( id: TId, value?: number ): Promise> { - const user = await db.user.getUser(); - const type = user?.subscription?.type; - - const isLegacyPro = - type !== undefined && - (type === SubscriptionType.BETA || - type === SubscriptionType.PREMIUM || - type === SubscriptionType.PREMIUM_CANCELED || - type === SubscriptionType.TRIAL); - const plan = user?.subscription?.plan || SubscriptionPlan.FREE; - - const limit = getFeatureLimitFromPlan(id, plan, isLegacyPro); + const limit = await getFeatureLimit(id); const isAllowed = await limit.isAllowed(value); return { @@ -479,21 +391,16 @@ export async function isFeatureAvailable( }; } +export async function getFeatureLimit(id: TId) { + const { isLegacyPro, plan } = await getUserPlan(); + return getFeatureLimitFromPlan(id, plan, isLegacyPro); +} + export async function areFeaturesAvailable( ids: TIds, values: number[] = [] ): Promise>> { - const user = await db.user.getUser(); - const type = user?.subscription?.type; - - const isLegacyPro = - type !== undefined && - (type === SubscriptionType.BETA || - type === SubscriptionType.PREMIUM || - type === SubscriptionType.PREMIUM_CANCELED || - type === SubscriptionType.TRIAL); - const plan = user?.subscription?.plan || SubscriptionPlan.FREE; - + const { isLegacyPro, plan } = await getUserPlan(); const results = {} as Record>; for (let i = 0; i < ids.length; ++i) { const value = values.at(i); @@ -513,6 +420,20 @@ export async function areFeaturesAvailable( return results; } +async function getUserPlan() { + const user = await db.user.getUser(); + const type = user?.subscription?.type; + + const isLegacyPro = + type !== undefined && + (type === SubscriptionType.BETA || + type === SubscriptionType.PREMIUM || + type === SubscriptionType.PREMIUM_CANCELED || + type === SubscriptionType.TRIAL); + const plan = user?.subscription?.plan || SubscriptionPlan.FREE; + return { plan, isLegacyPro }; +} + async function availableOn(id: FeatureId, value?: number) { const feature = features[id]; for (const key in feature.availability) {