dating-schema 0.35.1 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  export { type AgeRange, ageRangeSchema, emptyStringToUndefined, type Gender, genderSchema, type InterestedIn, interestedInSchema, type LookingFor, lookingForSchema, type MaritalStatus, type MatchStatus, type MessageType, maritalStatusSchema, matchStatusSchema, messageTypeSchema, optionalTrimmedString, type Pagination, type Photo, paginationSchema, photoSchema, type SwipeAction, swipeActionSchema, } from './common.js';
10
10
  export { getFieldOptions } from './field-helpers.js';
11
11
  export { type BoostBody, boostBodySchema, type ComplimentBody, complimentBodySchema, type ImageLikeBody, type InteractionListQuery, type InteractionType, imageLikeBodySchema, interactionListQuerySchema, interactionTypeSchema, type RewindBody, rewindBodySchema, type SwipeBody, type SwipeResponse, swipeBodySchema, swipeResponseSchema, } from './interactions.js';
12
- export { type BooleanFeatureKey, type EnumFeatureKey, type ExploreAccessLevel, type FeatureDisplayItem, type FeatureKey, type FilterLevel, getMinPlanForFeature, getPlanFeatureDisplay, getPlanLimit, getPlanLimits, hasRemainingQuota, isFeatureEnabled, isPaidPlan, type NumericFeatureKey, PAID_PLANS, PLAN_FEATURE_DISPLAY, PLAN_IDS, PLAN_LIMITS, type PlanId, type PlanLimits, PREMIUM_EXPLORE_CATEGORIES, PREMIUM_PLANS, type ScoreBreakdownLevel, TOP_PLANS, type WhoLikedYouLevel, } from './plan-limits.js';
12
+ export { type BooleanFeatureKey, type EnumFeatureKey, type ExploreAccessLevel, type FeatureDisplayItem, type FeatureKey, type FeatureValue, type FilterLevel, getMinPlanForFeature, getPlanFeatureDisplay, getPlanLimit, getPlanLimits, hasRemainingQuota, isFeatureEnabled, isPaidPlan, type NumericFeatureKey, PAID_PLANS, PLAN_FEATURE_DISPLAY, PLAN_IDS, PLAN_LIMITS, type PlanId, type PlanLimits, PREMIUM_EXPLORE_CATEGORIES, renderFeatureValue, PREMIUM_PLANS, type ScoreBreakdownLevel, TOP_PLANS, type WhoLikedYouLevel, } from './plan-limits.js';
13
13
  export { type CreateProfileInput, createProfileSchema, type FullProfileData, fullProfileSchema, getCognitiveBandRank, type OnboardingFormData, onboardingFormSchema, type UpdateProfileInput, updateProfileSchema, } from './profile.js';
14
14
  export { allFieldGroups, basics, career, cognitive, type FieldGroup, type FieldOption, familyHome, favourites, financeLegal, healthWellness, heroIdentity, interestCategories, interests, lifestyle, lookingFor, type MultiSelectField, type OptionCategory, type ProfileField, personality, relationshipIntimacy, type SelectField, selfAssessment, storyPrompts, values, } from './profile-fields/index.js';
15
15
  export { type BlockBody, blockBodySchema, type ReportBody, type ReportReason, type ReportStatus, reportBodySchema, reportReasonSchema, reportStatusSchema, } from './safety.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,KAAK,QAAQ,EAEb,cAAc,EACd,sBAAsB,EACtB,KAAK,MAAM,EAEX,YAAY,EACZ,KAAK,YAAY,EACjB,kBAAkB,EAClB,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,KAAK,WAAW,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EACL,KAAK,SAAS,EACd,eAAe,EACf,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,KAAK,iBAAiB,EACtB,UAAU,EACV,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,KAAK,MAAM,EACX,KAAK,UAAU,EACf,0BAA0B,EAC1B,aAAa,EACb,KAAK,mBAAmB,EACxB,SAAS,EACT,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,KAAK,kBAAkB,EACvB,mBAAmB,EACnB,KAAK,eAAe,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,WAAW,EACX,oBAAoB,EACpB,KAAK,WAAW,EAChB,cAAc,EACd,YAAY,EACZ,MAAM,GACP,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,KAAK,SAAS,EACd,eAAe,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,KAAK,kBAAkB,EACvB,wBAAwB,EACxB,KAAK,MAAM,EACX,YAAY,GACb,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,KAAK,QAAQ,EAEb,cAAc,EACd,sBAAsB,EACtB,KAAK,MAAM,EAEX,YAAY,EACZ,KAAK,YAAY,EACjB,kBAAkB,EAClB,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,KAAK,WAAW,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EACL,KAAK,SAAS,EACd,eAAe,EACf,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,KAAK,iBAAiB,EACtB,UAAU,EACV,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,KAAK,MAAM,EACX,KAAK,UAAU,EACf,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,KAAK,mBAAmB,EACxB,SAAS,EACT,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,KAAK,kBAAkB,EACvB,mBAAmB,EACnB,KAAK,eAAe,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,WAAW,EACX,oBAAoB,EACpB,KAAK,WAAW,EAChB,cAAc,EACd,YAAY,EACZ,MAAM,GACP,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,KAAK,SAAS,EACd,eAAe,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,KAAK,kBAAkB,EACvB,wBAAwB,EACxB,KAAK,MAAM,EACX,YAAY,GACb,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ export { getFieldOptions } from './field-helpers.js';
16
16
  // ─── Interaction schemas ─────────────────────────────────────────────────────
17
17
  export { boostBodySchema, complimentBodySchema, imageLikeBodySchema, interactionListQuerySchema, interactionTypeSchema, rewindBodySchema, swipeBodySchema, swipeResponseSchema, } from './interactions.js';
18
18
  // ─── Plan limits ─────────────────────────────────────────────────────────────
19
- export { getMinPlanForFeature, getPlanFeatureDisplay, getPlanLimit, getPlanLimits, hasRemainingQuota, isFeatureEnabled, isPaidPlan, PAID_PLANS, PLAN_FEATURE_DISPLAY, PLAN_IDS, PLAN_LIMITS, PREMIUM_EXPLORE_CATEGORIES, PREMIUM_PLANS, TOP_PLANS, } from './plan-limits.js';
19
+ export { getMinPlanForFeature, getPlanFeatureDisplay, getPlanLimit, getPlanLimits, hasRemainingQuota, isFeatureEnabled, isPaidPlan, PAID_PLANS, PLAN_FEATURE_DISPLAY, PLAN_IDS, PLAN_LIMITS, PREMIUM_EXPLORE_CATEGORIES, renderFeatureValue, PREMIUM_PLANS, TOP_PLANS, } from './plan-limits.js';
20
20
  // ─── Profile schemas ─────────────────────────────────────────────────────────
21
21
  export { createProfileSchema, fullProfileSchema, getCognitiveBandRank, onboardingFormSchema, updateProfileSchema, } from './profile.js';
22
22
  // ─── Profile field configs (option arrays with labels for UI rendering) ──────
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gFAAgF;AAChF,OAAO;AAEL,cAAc;AACd,cAAc,EACd,sBAAsB;AAEtB,QAAQ;AACR,YAAY,EAEZ,kBAAkB,EAElB,gBAAgB,EAIhB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EAGrB,gBAAgB,EAChB,WAAW,EAEX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,gFAAgF;AAChF,OAAO,EAEL,eAAe,EAEf,oBAAoB,EAIpB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EAErB,gBAAgB,EAGhB,eAAe,EACf,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,gFAAgF;AAChF,OAAO,EAOL,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EAEV,UAAU,EACV,oBAAoB,EACpB,QAAQ,EACR,WAAW,EAGX,0BAA0B,EAC1B,aAAa,EAEb,SAAS,GAEV,MAAM,kBAAkB,CAAC;AAC1B,gFAAgF;AAChF,OAAO,EAEL,mBAAmB,EAEnB,iBAAiB,EACjB,oBAAoB,EAEpB,oBAAoB,EAEpB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,gFAAgF;AAChF,OAAO,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,SAAS,EAGT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,UAAU,EAIV,WAAW,EACX,oBAAoB,EAEpB,cAAc,EACd,YAAY,EACZ,MAAM,GACP,MAAM,2BAA2B,CAAC;AAEnC,gFAAgF;AAChF,OAAO,EAEL,eAAe,EAIf,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,gFAAgF;AAChF,OAAO,EAEL,4BAA4B,EAE5B,wBAAwB,EAExB,YAAY,GACb,MAAM,mBAAmB,CAAC","sourcesContent":["/**\n * dating-schema — Shared Zod schemas for the Dating feature\n *\n * Single source of truth consumed by both jansathi-frontend and reform-backend.\n * Guarantees identical validation logic and TypeScript types on both sides.\n *\n * @module dating-schema\n */\n\n// ─── Common helpers and enums ────────────────────────────────────────────────\nexport {\n type AgeRange,\n // sub-schemas\n ageRangeSchema,\n emptyStringToUndefined,\n type Gender,\n // enums\n genderSchema,\n type InterestedIn,\n interestedInSchema,\n type LookingFor,\n lookingForSchema,\n type MaritalStatus,\n type MatchStatus,\n type MessageType,\n maritalStatusSchema,\n matchStatusSchema,\n messageTypeSchema,\n optionalTrimmedString,\n type Pagination,\n type Photo,\n paginationSchema,\n photoSchema,\n type SwipeAction,\n swipeActionSchema,\n} from './common.js';\nexport { getFieldOptions } from './field-helpers.js';\n// ─── Interaction schemas ─────────────────────────────────────────────────────\nexport {\n type BoostBody,\n boostBodySchema,\n type ComplimentBody,\n complimentBodySchema,\n type ImageLikeBody,\n type InteractionListQuery,\n type InteractionType,\n imageLikeBodySchema,\n interactionListQuerySchema,\n interactionTypeSchema,\n type RewindBody,\n rewindBodySchema,\n type SwipeBody,\n type SwipeResponse,\n swipeBodySchema,\n swipeResponseSchema,\n} from './interactions.js';\n// ─── Plan limits ─────────────────────────────────────────────────────────────\nexport {\n type BooleanFeatureKey,\n type EnumFeatureKey,\n type ExploreAccessLevel,\n type FeatureDisplayItem,\n type FeatureKey,\n type FilterLevel,\n getMinPlanForFeature,\n getPlanFeatureDisplay,\n getPlanLimit,\n getPlanLimits,\n hasRemainingQuota,\n isFeatureEnabled,\n isPaidPlan,\n type NumericFeatureKey,\n PAID_PLANS,\n PLAN_FEATURE_DISPLAY,\n PLAN_IDS,\n PLAN_LIMITS,\n type PlanId,\n type PlanLimits,\n PREMIUM_EXPLORE_CATEGORIES,\n PREMIUM_PLANS,\n type ScoreBreakdownLevel,\n TOP_PLANS,\n type WhoLikedYouLevel,\n} from './plan-limits.js';\n// ─── Profile schemas ─────────────────────────────────────────────────────────\nexport {\n type CreateProfileInput,\n createProfileSchema,\n type FullProfileData,\n fullProfileSchema,\n getCognitiveBandRank,\n type OnboardingFormData,\n onboardingFormSchema,\n type UpdateProfileInput,\n updateProfileSchema,\n} from './profile.js';\n// ─── Profile field configs (option arrays with labels for UI rendering) ──────\nexport {\n allFieldGroups,\n basics,\n career,\n cognitive,\n type FieldGroup,\n type FieldOption,\n familyHome,\n favourites,\n financeLegal,\n healthWellness,\n heroIdentity,\n interestCategories,\n interests,\n lifestyle,\n lookingFor,\n type MultiSelectField,\n type OptionCategory,\n type ProfileField,\n personality,\n relationshipIntimacy,\n type SelectField,\n selfAssessment,\n storyPrompts,\n values,\n} from './profile-fields/index.js';\n\n// ─── Safety schemas ──────────────────────────────────────────────────────────\nexport {\n type BlockBody,\n blockBodySchema,\n type ReportBody,\n type ReportReason,\n type ReportStatus,\n reportBodySchema,\n reportReasonSchema,\n reportStatusSchema,\n} from './safety.js';\n\n// ─── Subscription schemas ────────────────────────────────────────────────────\nexport {\n type CreateSubscriptionBody,\n createSubscriptionBodySchema,\n type SubscriptionStatus,\n subscriptionStatusSchema,\n type Tenure,\n tenureSchema,\n} from './subscription.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gFAAgF;AAChF,OAAO;AAEL,cAAc;AACd,cAAc,EACd,sBAAsB;AAEtB,QAAQ;AACR,YAAY,EAEZ,kBAAkB,EAElB,gBAAgB,EAIhB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EAGrB,gBAAgB,EAChB,WAAW,EAEX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,gFAAgF;AAChF,OAAO,EAEL,eAAe,EAEf,oBAAoB,EAIpB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EAErB,gBAAgB,EAGhB,eAAe,EACf,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,gFAAgF;AAChF,OAAO,EAQL,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EAEV,UAAU,EACV,oBAAoB,EACpB,QAAQ,EACR,WAAW,EAGX,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EAEb,SAAS,GAEV,MAAM,kBAAkB,CAAC;AAC1B,gFAAgF;AAChF,OAAO,EAEL,mBAAmB,EAEnB,iBAAiB,EACjB,oBAAoB,EAEpB,oBAAoB,EAEpB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,gFAAgF;AAChF,OAAO,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,SAAS,EAGT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,UAAU,EAIV,WAAW,EACX,oBAAoB,EAEpB,cAAc,EACd,YAAY,EACZ,MAAM,GACP,MAAM,2BAA2B,CAAC;AAEnC,gFAAgF;AAChF,OAAO,EAEL,eAAe,EAIf,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,gFAAgF;AAChF,OAAO,EAEL,4BAA4B,EAE5B,wBAAwB,EAExB,YAAY,GACb,MAAM,mBAAmB,CAAC","sourcesContent":["/**\n * dating-schema — Shared Zod schemas for the Dating feature\n *\n * Single source of truth consumed by both jansathi-frontend and reform-backend.\n * Guarantees identical validation logic and TypeScript types on both sides.\n *\n * @module dating-schema\n */\n\n// ─── Common helpers and enums ────────────────────────────────────────────────\nexport {\n type AgeRange,\n // sub-schemas\n ageRangeSchema,\n emptyStringToUndefined,\n type Gender,\n // enums\n genderSchema,\n type InterestedIn,\n interestedInSchema,\n type LookingFor,\n lookingForSchema,\n type MaritalStatus,\n type MatchStatus,\n type MessageType,\n maritalStatusSchema,\n matchStatusSchema,\n messageTypeSchema,\n optionalTrimmedString,\n type Pagination,\n type Photo,\n paginationSchema,\n photoSchema,\n type SwipeAction,\n swipeActionSchema,\n} from './common.js';\nexport { getFieldOptions } from './field-helpers.js';\n// ─── Interaction schemas ─────────────────────────────────────────────────────\nexport {\n type BoostBody,\n boostBodySchema,\n type ComplimentBody,\n complimentBodySchema,\n type ImageLikeBody,\n type InteractionListQuery,\n type InteractionType,\n imageLikeBodySchema,\n interactionListQuerySchema,\n interactionTypeSchema,\n type RewindBody,\n rewindBodySchema,\n type SwipeBody,\n type SwipeResponse,\n swipeBodySchema,\n swipeResponseSchema,\n} from './interactions.js';\n// ─── Plan limits ─────────────────────────────────────────────────────────────\nexport {\n type BooleanFeatureKey,\n type EnumFeatureKey,\n type ExploreAccessLevel,\n type FeatureDisplayItem,\n type FeatureKey,\n type FeatureValue,\n type FilterLevel,\n getMinPlanForFeature,\n getPlanFeatureDisplay,\n getPlanLimit,\n getPlanLimits,\n hasRemainingQuota,\n isFeatureEnabled,\n isPaidPlan,\n type NumericFeatureKey,\n PAID_PLANS,\n PLAN_FEATURE_DISPLAY,\n PLAN_IDS,\n PLAN_LIMITS,\n type PlanId,\n type PlanLimits,\n PREMIUM_EXPLORE_CATEGORIES,\n renderFeatureValue,\n PREMIUM_PLANS,\n type ScoreBreakdownLevel,\n TOP_PLANS,\n type WhoLikedYouLevel,\n} from './plan-limits.js';\n// ─── Profile schemas ─────────────────────────────────────────────────────────\nexport {\n type CreateProfileInput,\n createProfileSchema,\n type FullProfileData,\n fullProfileSchema,\n getCognitiveBandRank,\n type OnboardingFormData,\n onboardingFormSchema,\n type UpdateProfileInput,\n updateProfileSchema,\n} from './profile.js';\n// ─── Profile field configs (option arrays with labels for UI rendering) ──────\nexport {\n allFieldGroups,\n basics,\n career,\n cognitive,\n type FieldGroup,\n type FieldOption,\n familyHome,\n favourites,\n financeLegal,\n healthWellness,\n heroIdentity,\n interestCategories,\n interests,\n lifestyle,\n lookingFor,\n type MultiSelectField,\n type OptionCategory,\n type ProfileField,\n personality,\n relationshipIntimacy,\n type SelectField,\n selfAssessment,\n storyPrompts,\n values,\n} from './profile-fields/index.js';\n\n// ─── Safety schemas ──────────────────────────────────────────────────────────\nexport {\n type BlockBody,\n blockBodySchema,\n type ReportBody,\n type ReportReason,\n type ReportStatus,\n reportBodySchema,\n reportReasonSchema,\n reportStatusSchema,\n} from './safety.js';\n\n// ─── Subscription schemas ────────────────────────────────────────────────────\nexport {\n type CreateSubscriptionBody,\n createSubscriptionBodySchema,\n type SubscriptionStatus,\n subscriptionStatusSchema,\n type Tenure,\n tenureSchema,\n} from './subscription.js';\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Plan feature display data — separated to keep plan-limits.ts clean.
3
+ * Each plan value uses { qty, text, unit? } so the frontend renders:
4
+ * `${qty} ${text} ${unit}` → "10 compliments per day"
5
+ * Boolean true = checkmark (show label), false = locked.
6
+ */
7
+ import type { FeatureDisplayItem } from './plan-limits.js';
8
+ export declare const PLAN_FEATURE_DISPLAY_DATA: FeatureDisplayItem[];
9
+ //# sourceMappingURL=plan-features-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-features-data.d.ts","sourceRoot":"","sources":["../src/plan-features-data.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,kBAAkB,CAAC;AAQzE,eAAO,MAAM,yBAAyB,EAAE,kBAAkB,EA+KzD,CAAC"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Plan feature display data — separated to keep plan-limits.ts clean.
3
+ * Each plan value uses { qty, text, unit? } so the frontend renders:
4
+ * `${qty} ${text} ${unit}` → "10 compliments per day"
5
+ * Boolean true = checkmark (show label), false = locked.
6
+ */
7
+ /** Shorthand helpers */
8
+ const u = (text) => ({ qty: 'Unlimited', text });
9
+ const n = (qty, text, unit) => ({ qty, text, unit });
10
+ const d = (qty, text) => ({ qty, text });
11
+ // prettier-ignore
12
+ export const PLAN_FEATURE_DISPLAY_DATA = [
13
+ // ── Core actions ──
14
+ {
15
+ key: 'dailySwipes',
16
+ label: 'Swipes per day',
17
+ available: true,
18
+ helpText: 'The number of profiles you can like or pass on each day. Swipe right to like, left to pass.',
19
+ plans: {
20
+ spark: n('150', 'swipes', 'per day'),
21
+ gold: u('swipes'),
22
+ diamond: u('swipes'),
23
+ platinum: u('swipes'),
24
+ black: u('swipes'),
25
+ },
26
+ },
27
+ {
28
+ key: 'dailyCompliments',
29
+ label: 'Compliments per day',
30
+ available: true,
31
+ helpText: 'Send a compliment with a personal note to stand out. They see it even before matching with you.',
32
+ plans: {
33
+ spark: n('1', 'compliment', 'per day'),
34
+ gold: n('3', 'compliments', 'per day'),
35
+ diamond: n('5', 'compliments', 'per day'),
36
+ platinum: n('10', 'compliments', 'per day'),
37
+ black: n('15', 'compliments', 'per day'),
38
+ },
39
+ },
40
+ {
41
+ key: 'dailyRewinds',
42
+ label: 'Undo swipes',
43
+ available: true,
44
+ helpText: 'Accidentally passed on someone? Tap undo to bring their profile back instantly.',
45
+ plans: {
46
+ spark: false,
47
+ gold: u('undo'),
48
+ diamond: u('undo'),
49
+ platinum: u('undo'),
50
+ black: u('undo'),
51
+ },
52
+ },
53
+ // ── Visibility & discovery ──
54
+ {
55
+ key: 'showAds',
56
+ label: 'Ad-free experience',
57
+ available: true,
58
+ helpText: 'Browse profiles, chat, and explore without any ads interrupting your experience.',
59
+ plans: { spark: false, gold: true, diamond: true, platinum: true, black: true },
60
+ },
61
+ {
62
+ key: 'whoLikedYou',
63
+ label: 'See who likes you',
64
+ available: true,
65
+ helpText: 'See the list of people who already liked your profile, so you can match with them instantly.',
66
+ plans: {
67
+ spark: false,
68
+ gold: d('Blurred', 'preview of likes'),
69
+ diamond: d('Full', 'list of likes'),
70
+ platinum: d('Real-time', 'like alerts'),
71
+ black: d('Real-time', 'like alerts'),
72
+ },
73
+ },
74
+ {
75
+ key: 'filterLevel',
76
+ label: 'Search filters',
77
+ available: true,
78
+ helpText: 'Filter profiles by age, location, religion, education, profession, and 140+ more preferences.',
79
+ plans: {
80
+ spark: d('Basic', 'filters'),
81
+ gold: d('Advanced', 'filters'),
82
+ diamond: d('All 147', 'filters'),
83
+ platinum: d('All 147', 'filters'),
84
+ black: d('All 147', 'filters'),
85
+ },
86
+ },
87
+ {
88
+ key: 'monthlyBoosts',
89
+ label: 'Profile boosts',
90
+ available: true,
91
+ helpText: 'Boost your profile to the top of the discover feed for 30 minutes and get seen by more people.',
92
+ plans: {
93
+ spark: false,
94
+ gold: n('1', 'boost', 'per month'),
95
+ diamond: n('3', 'boosts', 'per month'),
96
+ platinum: n('5', 'boosts', 'per month'),
97
+ black: n('5', 'boosts', 'per month'),
98
+ },
99
+ },
100
+ // ── Communication ──
101
+ {
102
+ key: 'readReceipts',
103
+ label: 'Read receipts',
104
+ available: true,
105
+ helpText: 'Know exactly when someone has read your message. No more guessing.',
106
+ plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },
107
+ },
108
+ {
109
+ key: 'calls',
110
+ label: 'Voice & video calls',
111
+ available: false,
112
+ helpText: 'Talk face-to-face or voice-only with your matches — directly inside the app, no phone number needed.',
113
+ plans: {
114
+ spark: false,
115
+ gold: false,
116
+ diamond: n('3 voice + 2 video', 'calls', 'per day'),
117
+ platinum: u('calls'),
118
+ black: u('calls'),
119
+ },
120
+ },
121
+ {
122
+ key: 'voiceMessages',
123
+ label: 'Voice messages in chat',
124
+ available: false,
125
+ helpText: 'Record and send short voice clips in chat instead of typing.',
126
+ plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },
127
+ },
128
+ {
129
+ key: 'messageBeforeMatch',
130
+ label: 'Send a message first',
131
+ available: false,
132
+ helpText: 'Introduce yourself with a message before the other person swipes on you.',
133
+ plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
134
+ },
135
+ // ── Insights ──
136
+ {
137
+ key: 'photoInsights',
138
+ label: 'Photo performance insights',
139
+ available: true,
140
+ helpText: 'See which of your photos gets the most likes and attention, and get tips to improve.',
141
+ plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
142
+ },
143
+ // ── Premium perks ──
144
+ {
145
+ key: 'priorityVisibility',
146
+ label: 'Appear higher in search',
147
+ available: true,
148
+ helpText: 'Your profile is shown before others in the discover feed, so more people see you first.',
149
+ plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },
150
+ },
151
+ {
152
+ key: 'autoBoost',
153
+ label: 'Automatic weekly boost',
154
+ available: true,
155
+ helpText: "Your profile is automatically boosted every week — you don't have to do anything.",
156
+ plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
157
+ },
158
+ {
159
+ key: 'incognitoMode',
160
+ label: 'Browse privately',
161
+ available: true,
162
+ helpText: 'Go invisible. Your profile is hidden from everyone except the people you choose to like.',
163
+ plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
164
+ },
165
+ {
166
+ key: 'travelMode',
167
+ label: 'Explore any location',
168
+ available: true,
169
+ helpText: 'Browse profiles in any city, district, or state — without physically being there.',
170
+ plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
171
+ },
172
+ // ── Exclusive ──
173
+ {
174
+ key: 'exclusiveLeague',
175
+ label: 'Exclusive members-only league',
176
+ available: true,
177
+ helpText: 'Your profile is only visible to other Black members. A private, curated matchmaking experience.',
178
+ plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },
179
+ },
180
+ {
181
+ key: 'vipSupport',
182
+ label: 'Dedicated VIP support',
183
+ available: true,
184
+ helpText: 'Get fast, priority help from a dedicated support team whenever you need it.',
185
+ plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },
186
+ },
187
+ ];
188
+ //# sourceMappingURL=plan-features-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-features-data.js","sourceRoot":"","sources":["../src/plan-features-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plan feature display data — separated to keep plan-limits.ts clean.\n * Each plan value uses { qty, text, unit? } so the frontend renders:\n * `${qty} ${text} ${unit}` → \"10 compliments per day\"\n * Boolean true = checkmark (show label), false = locked.\n */\n\nimport type { FeatureDisplayItem, FeatureValue } from './plan-limits.js';\n\n/** Shorthand helpers */\nconst u = (text: string): FeatureValue => ({ qty: 'Unlimited', text });\nconst n = (qty: string, text: string, unit: string): FeatureValue => ({ qty, text, unit });\nconst d = (qty: string, text: string): FeatureValue => ({ qty, text });\n\n// prettier-ignore\nexport const PLAN_FEATURE_DISPLAY_DATA: FeatureDisplayItem[] = [\n // ── Core actions ──\n {\n key: 'dailySwipes',\n label: 'Swipes per day',\n available: true,\n helpText: 'The number of profiles you can like or pass on each day. Swipe right to like, left to pass.',\n plans: {\n spark: n('150', 'swipes', 'per day'),\n gold: u('swipes'),\n diamond: u('swipes'),\n platinum: u('swipes'),\n black: u('swipes'),\n },\n },\n {\n key: 'dailyCompliments',\n label: 'Compliments per day',\n available: true,\n helpText: 'Send a compliment with a personal note to stand out. They see it even before matching with you.',\n plans: {\n spark: n('1', 'compliment', 'per day'),\n gold: n('3', 'compliments', 'per day'),\n diamond: n('5', 'compliments', 'per day'),\n platinum: n('10', 'compliments', 'per day'),\n black: n('15', 'compliments', 'per day'),\n },\n },\n {\n key: 'dailyRewinds',\n label: 'Undo swipes',\n available: true,\n helpText: 'Accidentally passed on someone? Tap undo to bring their profile back instantly.',\n plans: {\n spark: false,\n gold: u('undo'),\n diamond: u('undo'),\n platinum: u('undo'),\n black: u('undo'),\n },\n },\n // ── Visibility & discovery ──\n {\n key: 'showAds',\n label: 'Ad-free experience',\n available: true,\n helpText: 'Browse profiles, chat, and explore without any ads interrupting your experience.',\n plans: { spark: false, gold: true, diamond: true, platinum: true, black: true },\n },\n {\n key: 'whoLikedYou',\n label: 'See who likes you',\n available: true,\n helpText: 'See the list of people who already liked your profile, so you can match with them instantly.',\n plans: {\n spark: false,\n gold: d('Blurred', 'preview of likes'),\n diamond: d('Full', 'list of likes'),\n platinum: d('Real-time', 'like alerts'),\n black: d('Real-time', 'like alerts'),\n },\n },\n {\n key: 'filterLevel',\n label: 'Search filters',\n available: true,\n helpText: 'Filter profiles by age, location, religion, education, profession, and 140+ more preferences.',\n plans: {\n spark: d('Basic', 'filters'),\n gold: d('Advanced', 'filters'),\n diamond: d('All 147', 'filters'),\n platinum: d('All 147', 'filters'),\n black: d('All 147', 'filters'),\n },\n },\n {\n key: 'monthlyBoosts',\n label: 'Profile boosts',\n available: true,\n helpText: 'Boost your profile to the top of the discover feed for 30 minutes and get seen by more people.',\n plans: {\n spark: false,\n gold: n('1', 'boost', 'per month'),\n diamond: n('3', 'boosts', 'per month'),\n platinum: n('5', 'boosts', 'per month'),\n black: n('5', 'boosts', 'per month'),\n },\n },\n // ── Communication ──\n {\n key: 'readReceipts',\n label: 'Read receipts',\n available: true,\n helpText: 'Know exactly when someone has read your message. No more guessing.',\n plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },\n },\n {\n key: 'calls',\n label: 'Voice & video calls',\n available: false,\n helpText: 'Talk face-to-face or voice-only with your matches — directly inside the app, no phone number needed.',\n plans: {\n spark: false,\n gold: false,\n diamond: n('3 voice + 2 video', 'calls', 'per day'),\n platinum: u('calls'),\n black: u('calls'),\n },\n },\n {\n key: 'voiceMessages',\n label: 'Voice messages in chat',\n available: false,\n helpText: 'Record and send short voice clips in chat instead of typing.',\n plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },\n },\n {\n key: 'messageBeforeMatch',\n label: 'Send a message first',\n available: false,\n helpText: 'Introduce yourself with a message before the other person swipes on you.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n // ── Insights ──\n {\n key: 'photoInsights',\n label: 'Photo performance insights',\n available: true,\n helpText: 'See which of your photos gets the most likes and attention, and get tips to improve.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n // ── Premium perks ──\n {\n key: 'priorityVisibility',\n label: 'Appear higher in search',\n available: true,\n helpText: 'Your profile is shown before others in the discover feed, so more people see you first.',\n plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },\n },\n {\n key: 'autoBoost',\n label: 'Automatic weekly boost',\n available: true,\n helpText: \"Your profile is automatically boosted every week — you don't have to do anything.\",\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n {\n key: 'incognitoMode',\n label: 'Browse privately',\n available: true,\n helpText: 'Go invisible. Your profile is hidden from everyone except the people you choose to like.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n {\n key: 'travelMode',\n label: 'Explore any location',\n available: true,\n helpText: 'Browse profiles in any city, district, or state — without physically being there.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n // ── Exclusive ──\n {\n key: 'exclusiveLeague',\n label: 'Exclusive members-only league',\n available: true,\n helpText: 'Your profile is only visible to other Black members. A private, curated matchmaking experience.',\n plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },\n },\n {\n key: 'vipSupport',\n label: 'Dedicated VIP support',\n available: true,\n helpText: 'Get fast, priority help from a dedicated support team whenever you need it.',\n plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },\n },\n];\n"]}
@@ -78,25 +78,38 @@ export declare function isPaidPlan(planId: PlanId): boolean;
78
78
  /** Premium explore categories that require Gold+ to access. */
79
79
  export declare const PREMIUM_EXPLORE_CATEGORIES: readonly ["high-iq", "elite-institute", "wealth-dating", "casual", "nri-dating", "senior-dating", "lgbtq", "second-innings"];
80
80
  /** How a feature appears in UI — per-plan value can be a string (quantity), true (check), or false (locked). */
81
+ /** Structured value for numeric/descriptive plan features */
82
+ export interface FeatureValue {
83
+ /** Quantity — e.g. "10", "Unlimited", "3", "All 147", "Blurred preview" */
84
+ qty: string;
85
+ /** Feature noun — e.g. "compliments", "swipes", "filters", "of likes" */
86
+ text: string;
87
+ /** Time/frequency unit — e.g. "per day", "per month". Omit if not applicable. */
88
+ unit?: string;
89
+ }
81
90
  export interface FeatureDisplayItem {
82
91
  /** Unique key matching a FeatureKey or custom display key */
83
92
  key: string;
84
- /** Short, clear label shown on the card */
93
+ /** Short, clear label shown when feature is locked */
85
94
  label: string;
86
95
  /** Tooltip help text explaining what this feature does */
87
96
  helpText: string;
88
- /** Per-plan display value: string = show text, true = checkmark, false = locked/unavailable */
89
- plans: Record<PlanId, string | boolean>;
97
+ /** Whether this feature is live. If false, hidden from UI entirely (not yet rolled out). */
98
+ available: boolean;
99
+ /** Per-plan display value: FeatureValue = structured text, true = checkmark, false = locked */
100
+ plans: Record<PlanId, FeatureValue | boolean>;
90
101
  }
102
+ /** Render a plan feature value to a single display string */
103
+ export declare function renderFeatureValue(value: FeatureValue | boolean, label: string): string;
91
104
  /**
92
105
  * Master feature list for plan comparison UI.
93
- * Derived from PLAN_LIMITS but with human-readable labels and display values.
94
- * This is the single source of truth for what features each plan shows.
106
+ * Now in plan-features-data.ts for cleaner separation.
95
107
  */
96
- export declare const PLAN_FEATURE_DISPLAY: FeatureDisplayItem[];
108
+ export { PLAN_FEATURE_DISPLAY_DATA as PLAN_FEATURE_DISPLAY } from './plan-features-data.js';
97
109
  /**
98
110
  * Get features for a plan, split into included (with values) and locked.
99
111
  * Included features come first, locked features last.
112
+ * Only includes features where `available: true` (hides unreleased features).
100
113
  */
101
114
  export declare function getPlanFeatureDisplay(planId: PlanId): {
102
115
  included: {
@@ -1 +1 @@
1
- {"version":3,"file":"plan-limits.d.ts","sourceRoot":"","sources":["../src/plan-limits.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan-limits.d.ts","sourceRoot":"","sources":["../src/plan-limits.ts"],"names":[],"mappings":"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"}
@@ -12,15 +12,16 @@
12
12
  *
13
13
  * @module dating-schema/plan-limits
14
14
  */
15
+ import { PLAN_FEATURE_DISPLAY_DATA as PLAN_FEATURE_DISPLAY_ITEMS } from './plan-features-data.js';
15
16
  // ─── Plan Limits Config ──────────────────────────────────────────────────────
16
17
  export const PLAN_LIMITS = {
17
- // ── Spark (Free) — Enough to hook, heavily limited ──
18
+ // ── Spark (Free) — Generous enough to hook, limited enough to upgrade ──
18
19
  spark: {
19
- dailySwipes: 20,
20
+ dailySwipes: 150,
20
21
  dailyCompliments: 1,
21
22
  dailyRewinds: 0,
22
- dailyProfileVisits: 10,
23
- monthlyNewChatThreads: 5,
23
+ dailyProfileVisits: 20,
24
+ monthlyNewChatThreads: Infinity, // no chat cap — matches can always chat
24
25
  monthlyBoosts: 0,
25
26
  weeklyAutoBoost: 0,
26
27
  dailyVoiceCalls: 0,
@@ -48,19 +49,19 @@ export const PLAN_LIMITS = {
48
49
  photoInsights: false,
49
50
  exclusiveLeague: false,
50
51
  },
51
- // ── Gold — Good value, removes major pain points ──
52
+ // ── Gold — Removes all friction, more generous than Tinder Plus ──
52
53
  gold: {
53
- dailySwipes: 80,
54
- dailyCompliments: 5,
55
- dailyRewinds: 3,
56
- dailyProfileVisits: 30,
57
- monthlyNewChatThreads: 15,
54
+ dailySwipes: Infinity, // no swipe cap for paid users
55
+ dailyCompliments: 3,
56
+ dailyRewinds: Infinity, // no rewind cap for paid users
57
+ dailyProfileVisits: Infinity,
58
+ monthlyNewChatThreads: Infinity,
58
59
  monthlyBoosts: 1,
59
60
  weeklyAutoBoost: 0,
60
- dailyVoiceCalls: 2,
61
- dailyVideoCalls: 1,
62
- voiceCallDurationMin: 10,
63
- videoCallDurationMin: 10,
61
+ dailyVoiceCalls: 0, // voice/video starts at Diamond
62
+ dailyVideoCalls: 0,
63
+ voiceCallDurationMin: 0,
64
+ videoCallDurationMin: 0,
64
65
  monthlyIntentChanges: 4,
65
66
  scoreVisibleProfiles: Infinity,
66
67
  filterLevel: 'advanced',
@@ -69,32 +70,32 @@ export const PLAN_LIMITS = {
69
70
  scoreBreakdown: 'score',
70
71
  readReceipts: false,
71
72
  incognitoMode: false,
72
- voiceMessages: true,
73
- voiceCalls: true,
74
- videoCalls: true,
73
+ voiceMessages: false,
74
+ voiceCalls: false,
75
+ videoCalls: false,
75
76
  showAds: false,
76
77
  priorityVisibility: false,
77
78
  autoBoost: false,
78
- seeProfileViewers: true,
79
+ seeProfileViewers: false,
79
80
  messageBeforeMatch: false,
80
81
  travelMode: false,
81
82
  profileContentUnlock: true,
82
83
  photoInsights: false,
83
84
  exclusiveLeague: false,
84
85
  },
85
- // ── Diamond (DECOY) — Generous but capped, pushes to Platinum ──
86
+ // ── Diamond (DECOY) — Almost Platinum but capped, pushes upgrade ──
86
87
  diamond: {
87
- dailySwipes: 200,
88
- dailyCompliments: 15,
89
- dailyRewinds: 10,
90
- dailyProfileVisits: 100,
91
- monthlyNewChatThreads: 50,
88
+ dailySwipes: Infinity,
89
+ dailyCompliments: 5,
90
+ dailyRewinds: Infinity,
91
+ dailyProfileVisits: Infinity,
92
+ monthlyNewChatThreads: Infinity,
92
93
  monthlyBoosts: 3,
93
94
  weeklyAutoBoost: 0,
94
- dailyVoiceCalls: 5,
95
- dailyVideoCalls: 3,
96
- voiceCallDurationMin: 20,
97
- videoCallDurationMin: 15,
95
+ dailyVoiceCalls: 3,
96
+ dailyVideoCalls: 2,
97
+ voiceCallDurationMin: 15,
98
+ videoCallDurationMin: 10,
98
99
  monthlyIntentChanges: 8,
99
100
  scoreVisibleProfiles: Infinity,
100
101
  filterLevel: 'full',
@@ -113,17 +114,17 @@ export const PLAN_LIMITS = {
113
114
  messageBeforeMatch: false,
114
115
  travelMode: false,
115
116
  profileContentUnlock: true,
116
- photoInsights: true,
117
+ photoInsights: false,
117
118
  exclusiveLeague: false,
118
119
  },
119
- // ── Platinum — Unlimited everything, the aspirational tier ──
120
+ // ── Platinum — The aspirational tier, everything unlocked ──
120
121
  platinum: {
121
122
  dailySwipes: Infinity,
122
- dailyCompliments: Infinity,
123
+ dailyCompliments: 10,
123
124
  dailyRewinds: Infinity,
124
125
  dailyProfileVisits: Infinity,
125
126
  monthlyNewChatThreads: Infinity,
126
- monthlyBoosts: 7,
127
+ monthlyBoosts: 5,
127
128
  weeklyAutoBoost: 1,
128
129
  dailyVoiceCalls: Infinity,
129
130
  dailyVideoCalls: Infinity,
@@ -151,14 +152,13 @@ export const PLAN_LIMITS = {
151
152
  exclusiveLeague: false,
152
153
  },
153
154
  // ── Black — Exclusive league for celebrities & HNIs ──
154
- // All Platinum features + exclusive league (only Black users see each other)
155
155
  black: {
156
156
  dailySwipes: Infinity,
157
- dailyCompliments: Infinity,
157
+ dailyCompliments: 15,
158
158
  dailyRewinds: Infinity,
159
159
  dailyProfileVisits: Infinity,
160
160
  monthlyNewChatThreads: Infinity,
161
- monthlyBoosts: 7,
161
+ monthlyBoosts: 5,
162
162
  weeklyAutoBoost: 1,
163
163
  dailyVoiceCalls: Infinity,
164
164
  dailyVideoCalls: Infinity,
@@ -210,116 +210,34 @@ export const PREMIUM_EXPLORE_CATEGORIES = [
210
210
  'lgbtq',
211
211
  'second-innings',
212
212
  ];
213
+ /** Render a plan feature value to a single display string */
214
+ export function renderFeatureValue(value, label) {
215
+ if (value === true)
216
+ return label;
217
+ if (value === false)
218
+ return label;
219
+ const parts = [value.qty, value.text];
220
+ if (value.unit)
221
+ parts.push(value.unit);
222
+ return parts.join(' ');
223
+ }
213
224
  /**
214
225
  * Master feature list for plan comparison UI.
215
- * Derived from PLAN_LIMITS but with human-readable labels and display values.
216
- * This is the single source of truth for what features each plan shows.
226
+ * Now in plan-features-data.ts for cleaner separation.
217
227
  */
218
- export const PLAN_FEATURE_DISPLAY = [
219
- {
220
- key: 'dailySwipes', label: 'Swipes per day',
221
- helpText: 'How many profiles you can like or pass on each day.',
222
- plans: { spark: '20', gold: '80', diamond: '200', platinum: 'Unlimited', black: 'Unlimited' },
223
- },
224
- {
225
- key: 'dailyCompliments', label: 'Compliments per day',
226
- helpText: 'Send a compliment with a personal note — they always see it, even without matching.',
227
- plans: { spark: '1', gold: '5', diamond: '15', platinum: 'Unlimited', black: 'Unlimited' },
228
- },
229
- {
230
- key: 'dailyRewinds', label: 'Undo swipes',
231
- helpText: 'Accidentally passed on someone? Rewind to bring them back.',
232
- plans: { spark: false, gold: '3/day', diamond: '10/day', platinum: 'Unlimited', black: 'Unlimited' },
233
- },
234
- {
235
- key: 'calls', label: 'Voice & video calls',
236
- helpText: 'Talk face-to-face or voice-only with your matches — right inside the app.',
237
- plans: { spark: false, gold: 'Limited', diamond: 'Limited', platinum: 'Unlimited', black: 'Unlimited' },
238
- },
239
- {
240
- key: 'whoLikedYou', label: 'See who likes you',
241
- helpText: 'Find out who already liked your profile so you can match instantly.',
242
- plans: { spark: false, gold: 'Blurred', diamond: 'Full list', platinum: 'Real-time', black: 'Real-time' },
243
- },
244
- {
245
- key: 'filterLevel', label: 'Search filters',
246
- helpText: 'Narrow down profiles by age, location, religion, profession, and 140+ more options.',
247
- plans: { spark: 'Basic', gold: 'Advanced', diamond: 'All 147', platinum: 'All 147', black: 'All 147' },
248
- },
249
- {
250
- key: 'monthlyBoosts', label: 'Profile boosts',
251
- helpText: 'Push your profile to the top of the discover feed for 30 minutes — get seen by more people.',
252
- plans: { spark: false, gold: '1/month', diamond: '3/month', platinum: '7/month', black: '7/month' },
253
- },
254
- {
255
- key: 'monthlyNewChatThreads', label: 'New conversations',
256
- helpText: 'Start new chat conversations with your matches each month. Existing chats always stay open.',
257
- plans: { spark: '5/month', gold: '15/month', diamond: '50/month', platinum: 'Unlimited', black: 'Unlimited' },
258
- },
259
- {
260
- key: 'readReceipts', label: 'Read receipts',
261
- helpText: 'See when your messages have been read by the other person.',
262
- plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },
263
- },
264
- {
265
- key: 'seeProfileViewers', label: 'See who viewed you',
266
- helpText: 'Find out who visited your profile — even if they didn\'t swipe.',
267
- plans: { spark: false, gold: true, diamond: true, platinum: true, black: true },
268
- },
269
- {
270
- key: 'photoInsights', label: 'Photo insights',
271
- helpText: 'See which of your photos gets the most attention and likes.',
272
- plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },
273
- },
274
- {
275
- key: 'priorityVisibility', label: 'Show up first',
276
- helpText: 'Your profile appears higher in other people\'s discover feed.',
277
- plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },
278
- },
279
- {
280
- key: 'autoBoost', label: 'Weekly auto-boost',
281
- helpText: 'Your profile is automatically boosted every week — no need to remember.',
282
- plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
283
- },
284
- {
285
- key: 'incognitoMode', label: 'Browse privately',
286
- helpText: 'Hide your profile from everyone. Only people you like can see you.',
287
- plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
288
- },
289
- {
290
- key: 'travelMode', label: 'Travel mode',
291
- helpText: 'Browse profiles in any city or district — without being there physically.',
292
- plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
293
- },
294
- {
295
- key: 'messageBeforeMatch', label: 'Message first',
296
- helpText: 'Send a message to someone before they swipe on you.',
297
- plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },
298
- },
299
- {
300
- key: 'exclusiveLeague', label: 'Exclusive league',
301
- helpText: 'Your profile is only shown to other Black members. A private, curated experience.',
302
- plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },
303
- },
304
- {
305
- key: 'vipSupport', label: 'VIP support',
306
- helpText: 'Get priority help from a dedicated support team.',
307
- plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },
308
- },
309
- {
310
- key: 'showAds', label: 'No ads',
311
- helpText: 'Enjoy a clean, ad-free experience throughout the app.',
312
- plans: { spark: false, gold: true, diamond: true, platinum: true, black: true },
313
- },
314
- ];
228
+ export { PLAN_FEATURE_DISPLAY_DATA as PLAN_FEATURE_DISPLAY } from './plan-features-data.js';
315
229
  /**
316
230
  * Get features for a plan, split into included (with values) and locked.
317
231
  * Included features come first, locked features last.
232
+ * Only includes features where `available: true` (hides unreleased features).
318
233
  */
319
234
  export function getPlanFeatureDisplay(planId) {
320
235
  const included = [];
321
236
  const locked = [];
322
- for (const f of PLAN_FEATURE_DISPLAY) {
237
+ for (const f of PLAN_FEATURE_DISPLAY_ITEMS) {
238
+ // Skip features not yet rolled out
239
+ if (!f.available)
240
+ continue;
323
241
  const val = f.plans[planId];
324
242
  if (val === false) {
325
243
  locked.push({ key: f.key, label: f.label, helpText: f.helpText });
@@ -328,7 +246,7 @@ export function getPlanFeatureDisplay(planId) {
328
246
  included.push({ key: f.key, label: f.label, helpText: f.helpText, value: true });
329
247
  }
330
248
  else {
331
- included.push({ key: f.key, label: f.label, helpText: f.helpText, value: val });
249
+ included.push({ key: f.key, label: f.label, helpText: f.helpText, value: renderFeatureValue(val, f.label) });
332
250
  }
333
251
  }
334
252
  return { included, locked };
@@ -1 +1 @@
1
- {"version":3,"file":"plan-limits.js","sourceRoot":"","sources":["../src/plan-limits.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * dating-schema — Plan limits configuration\n *\n * Single source of truth for feature gating by pricing plan.\n * Both backend middleware and frontend UI read from this config.\n *\n * To add/remove a gated feature:\n * 1. Add/remove the key in PlanLimits interface\n * 2. Set values for each plan in PLAN_LIMITS\n * 3. Backend: add enforceDatingPlanLimit('featureKey') middleware to the route\n * That's it — no scattered if-checks needed.\n *\n * @module dating-schema/plan-limits\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** The 5 pricing plans. Black is the exclusive celebrity/HNI tier. */\nexport type PlanId = 'spark' | 'gold' | 'diamond' | 'platinum' | 'black';\n\n/** Filter capability levels by plan. */\nexport type FilterLevel = 'basic' | 'advanced' | 'full';\n\n/** \"Who liked you\" visibility levels by plan. */\nexport type WhoLikedYouLevel = 'none' | 'blurred' | 'full' | 'realtime';\n\n/** Explore category access levels by plan. */\nexport type ExploreAccessLevel = 'basic' | 'premium';\n\n/** Compatibility score breakdown visibility by plan. */\nexport type ScoreBreakdownLevel = 'none' | 'score' | 'breakdown';\n\n/** All numeric limit keys (daily/periodic counters enforced server-side). */\nexport type NumericFeatureKey =\n | 'dailySwipes'\n | 'dailyCompliments'\n | 'dailyRewinds'\n | 'dailyProfileVisits'\n | 'monthlyNewChatThreads'\n | 'monthlyBoosts'\n | 'weeklyAutoBoost'\n | 'dailyVoiceCalls'\n | 'dailyVideoCalls'\n | 'voiceCallDurationMin'\n | 'videoCallDurationMin'\n | 'monthlyIntentChanges'\n | 'scoreVisibleProfiles';\n\n/** All boolean feature keys (on/off per plan). */\nexport type BooleanFeatureKey =\n | 'readReceipts'\n | 'incognitoMode'\n | 'voiceMessages'\n | 'voiceCalls'\n | 'videoCalls'\n | 'showAds'\n | 'priorityVisibility'\n | 'autoBoost'\n | 'seeProfileViewers'\n | 'messageBeforeMatch'\n | 'travelMode'\n | 'profileContentUnlock'\n | 'photoInsights'\n | 'exclusiveLeague';\n\n/** All string-enum feature keys. */\nexport type EnumFeatureKey = 'filterLevel' | 'whoLikedYou' | 'exploreAccess' | 'scoreBreakdown';\n\n/** Union of all feature keys that can be gated. */\nexport type FeatureKey = NumericFeatureKey | BooleanFeatureKey | EnumFeatureKey;\n\n/** Complete plan limits definition. */\nexport interface PlanLimits {\n // ── Numeric limits (daily/periodic) ──\n dailySwipes: number;\n dailyCompliments: number;\n dailyRewinds: number;\n dailyProfileVisits: number;\n monthlyNewChatThreads: number;\n monthlyBoosts: number;\n weeklyAutoBoost: number;\n dailyVoiceCalls: number;\n dailyVideoCalls: number;\n voiceCallDurationMin: number;\n videoCallDurationMin: number;\n monthlyIntentChanges: number;\n scoreVisibleProfiles: number;\n\n // ── Enum limits ──\n filterLevel: FilterLevel;\n whoLikedYou: WhoLikedYouLevel;\n exploreAccess: ExploreAccessLevel;\n scoreBreakdown: ScoreBreakdownLevel;\n\n // ── Boolean flags ──\n readReceipts: boolean;\n incognitoMode: boolean;\n voiceMessages: boolean;\n voiceCalls: boolean;\n videoCalls: boolean;\n showAds: boolean;\n priorityVisibility: boolean;\n autoBoost: boolean;\n seeProfileViewers: boolean;\n messageBeforeMatch: boolean;\n travelMode: boolean;\n profileContentUnlock: boolean;\n photoInsights: boolean;\n exclusiveLeague: boolean;\n}\n\n// ─── Plan Limits Config ──────────────────────────────────────────────────────\n\nexport const PLAN_LIMITS: Record<PlanId, PlanLimits> = {\n // ── Spark (Free) — Enough to hook, heavily limited ──\n spark: {\n dailySwipes: 20,\n dailyCompliments: 1,\n dailyRewinds: 0,\n dailyProfileVisits: 10,\n monthlyNewChatThreads: 5,\n monthlyBoosts: 0,\n weeklyAutoBoost: 0,\n dailyVoiceCalls: 0,\n dailyVideoCalls: 0,\n voiceCallDurationMin: 0,\n videoCallDurationMin: 0,\n monthlyIntentChanges: 2,\n scoreVisibleProfiles: 3,\n filterLevel: 'basic',\n whoLikedYou: 'none',\n exploreAccess: 'basic',\n scoreBreakdown: 'none',\n readReceipts: false,\n incognitoMode: false,\n voiceMessages: false,\n voiceCalls: false,\n videoCalls: false,\n showAds: true,\n priorityVisibility: false,\n autoBoost: false,\n seeProfileViewers: false,\n messageBeforeMatch: false,\n travelMode: false,\n profileContentUnlock: false,\n photoInsights: false,\n exclusiveLeague: false,\n },\n\n // ── Gold — Good value, removes major pain points ──\n gold: {\n dailySwipes: 80,\n dailyCompliments: 5,\n dailyRewinds: 3,\n dailyProfileVisits: 30,\n monthlyNewChatThreads: 15,\n monthlyBoosts: 1,\n weeklyAutoBoost: 0,\n dailyVoiceCalls: 2,\n dailyVideoCalls: 1,\n voiceCallDurationMin: 10,\n videoCallDurationMin: 10,\n monthlyIntentChanges: 4,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'advanced',\n whoLikedYou: 'blurred',\n exploreAccess: 'premium',\n scoreBreakdown: 'score',\n readReceipts: false,\n incognitoMode: false,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: false,\n autoBoost: false,\n seeProfileViewers: true,\n messageBeforeMatch: false,\n travelMode: false,\n profileContentUnlock: true,\n photoInsights: false,\n exclusiveLeague: false,\n },\n\n // ── Diamond (DECOY) — Generous but capped, pushes to Platinum ──\n diamond: {\n dailySwipes: 200,\n dailyCompliments: 15,\n dailyRewinds: 10,\n dailyProfileVisits: 100,\n monthlyNewChatThreads: 50,\n monthlyBoosts: 3,\n weeklyAutoBoost: 0,\n dailyVoiceCalls: 5,\n dailyVideoCalls: 3,\n voiceCallDurationMin: 20,\n videoCallDurationMin: 15,\n monthlyIntentChanges: 8,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'full',\n whoLikedYou: 'full',\n exploreAccess: 'premium',\n scoreBreakdown: 'score',\n readReceipts: true,\n incognitoMode: false,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: true,\n autoBoost: false,\n seeProfileViewers: true,\n messageBeforeMatch: false,\n travelMode: false,\n profileContentUnlock: true,\n photoInsights: true,\n exclusiveLeague: false,\n },\n\n // ── Platinum — Unlimited everything, the aspirational tier ──\n platinum: {\n dailySwipes: Infinity,\n dailyCompliments: Infinity,\n dailyRewinds: Infinity,\n dailyProfileVisits: Infinity,\n monthlyNewChatThreads: Infinity,\n monthlyBoosts: 7,\n weeklyAutoBoost: 1,\n dailyVoiceCalls: Infinity,\n dailyVideoCalls: Infinity,\n voiceCallDurationMin: Infinity,\n videoCallDurationMin: Infinity,\n monthlyIntentChanges: Infinity,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'full',\n whoLikedYou: 'realtime',\n exploreAccess: 'premium',\n scoreBreakdown: 'breakdown',\n readReceipts: true,\n incognitoMode: true,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: true,\n autoBoost: true,\n seeProfileViewers: true,\n messageBeforeMatch: true,\n travelMode: true,\n profileContentUnlock: true,\n photoInsights: true,\n exclusiveLeague: false,\n },\n\n // ── Black — Exclusive league for celebrities & HNIs ──\n // All Platinum features + exclusive league (only Black users see each other)\n black: {\n dailySwipes: Infinity,\n dailyCompliments: Infinity,\n dailyRewinds: Infinity,\n dailyProfileVisits: Infinity,\n monthlyNewChatThreads: Infinity,\n monthlyBoosts: 7,\n weeklyAutoBoost: 1,\n dailyVoiceCalls: Infinity,\n dailyVideoCalls: Infinity,\n voiceCallDurationMin: Infinity,\n videoCallDurationMin: Infinity,\n monthlyIntentChanges: Infinity,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'full',\n whoLikedYou: 'realtime',\n exploreAccess: 'premium',\n scoreBreakdown: 'breakdown',\n readReceipts: true,\n incognitoMode: true,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: true,\n autoBoost: true,\n seeProfileViewers: true,\n messageBeforeMatch: true,\n travelMode: true,\n profileContentUnlock: true,\n photoInsights: true,\n exclusiveLeague: true,\n },\n} as const;\n\n// ─── Plan Tier Helpers ──────────────────────────────────────────────────────\n\n/** All valid plan IDs in tier order. */\nexport const PLAN_IDS: PlanId[] = ['spark', 'gold', 'diamond', 'platinum', 'black'];\n\n/** Paid plans (Gold and above). */\nexport const PAID_PLANS: PlanId[] = ['gold', 'diamond', 'platinum', 'black'];\n\n/** Premium plans (Diamond and above). */\nexport const PREMIUM_PLANS: PlanId[] = ['diamond', 'platinum', 'black'];\n\n/** Top-tier plans (Platinum and Black). */\nexport const TOP_PLANS: PlanId[] = ['platinum', 'black'];\n\n/** Check if a plan is paid (not Spark). */\nexport function isPaidPlan(planId: PlanId): boolean {\n return planId !== 'spark';\n}\n\n/** Premium explore categories that require Gold+ to access. */\nexport const PREMIUM_EXPLORE_CATEGORIES = [\n 'high-iq',\n 'elite-institute',\n 'wealth-dating',\n 'casual',\n 'nri-dating',\n 'senior-dating',\n 'lgbtq',\n 'second-innings',\n] as const;\n\n// ─── Feature Display Config (UI source of truth) ────────────────────────────\n\n/** How a feature appears in UI — per-plan value can be a string (quantity), true (check), or false (locked). */\nexport interface FeatureDisplayItem {\n /** Unique key matching a FeatureKey or custom display key */\n key: string;\n /** Short, clear label shown on the card */\n label: string;\n /** Tooltip help text explaining what this feature does */\n helpText: string;\n /** Per-plan display value: string = show text, true = checkmark, false = locked/unavailable */\n plans: Record<PlanId, string | boolean>;\n}\n\n/**\n * Master feature list for plan comparison UI.\n * Derived from PLAN_LIMITS but with human-readable labels and display values.\n * This is the single source of truth for what features each plan shows.\n */\nexport const PLAN_FEATURE_DISPLAY: FeatureDisplayItem[] = [\n {\n key: 'dailySwipes', label: 'Swipes per day',\n helpText: 'How many profiles you can like or pass on each day.',\n plans: { spark: '20', gold: '80', diamond: '200', platinum: 'Unlimited', black: 'Unlimited' },\n },\n {\n key: 'dailyCompliments', label: 'Compliments per day',\n helpText: 'Send a compliment with a personal note — they always see it, even without matching.',\n plans: { spark: '1', gold: '5', diamond: '15', platinum: 'Unlimited', black: 'Unlimited' },\n },\n {\n key: 'dailyRewinds', label: 'Undo swipes',\n helpText: 'Accidentally passed on someone? Rewind to bring them back.',\n plans: { spark: false, gold: '3/day', diamond: '10/day', platinum: 'Unlimited', black: 'Unlimited' },\n },\n {\n key: 'calls', label: 'Voice & video calls',\n helpText: 'Talk face-to-face or voice-only with your matches — right inside the app.',\n plans: { spark: false, gold: 'Limited', diamond: 'Limited', platinum: 'Unlimited', black: 'Unlimited' },\n },\n {\n key: 'whoLikedYou', label: 'See who likes you',\n helpText: 'Find out who already liked your profile so you can match instantly.',\n plans: { spark: false, gold: 'Blurred', diamond: 'Full list', platinum: 'Real-time', black: 'Real-time' },\n },\n {\n key: 'filterLevel', label: 'Search filters',\n helpText: 'Narrow down profiles by age, location, religion, profession, and 140+ more options.',\n plans: { spark: 'Basic', gold: 'Advanced', diamond: 'All 147', platinum: 'All 147', black: 'All 147' },\n },\n {\n key: 'monthlyBoosts', label: 'Profile boosts',\n helpText: 'Push your profile to the top of the discover feed for 30 minutes — get seen by more people.',\n plans: { spark: false, gold: '1/month', diamond: '3/month', platinum: '7/month', black: '7/month' },\n },\n {\n key: 'monthlyNewChatThreads', label: 'New conversations',\n helpText: 'Start new chat conversations with your matches each month. Existing chats always stay open.',\n plans: { spark: '5/month', gold: '15/month', diamond: '50/month', platinum: 'Unlimited', black: 'Unlimited' },\n },\n {\n key: 'readReceipts', label: 'Read receipts',\n helpText: 'See when your messages have been read by the other person.',\n plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },\n },\n {\n key: 'seeProfileViewers', label: 'See who viewed you',\n helpText: 'Find out who visited your profile — even if they didn\\'t swipe.',\n plans: { spark: false, gold: true, diamond: true, platinum: true, black: true },\n },\n {\n key: 'photoInsights', label: 'Photo insights',\n helpText: 'See which of your photos gets the most attention and likes.',\n plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },\n },\n {\n key: 'priorityVisibility', label: 'Show up first',\n helpText: 'Your profile appears higher in other people\\'s discover feed.',\n plans: { spark: false, gold: false, diamond: true, platinum: true, black: true },\n },\n {\n key: 'autoBoost', label: 'Weekly auto-boost',\n helpText: 'Your profile is automatically boosted every week — no need to remember.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n {\n key: 'incognitoMode', label: 'Browse privately',\n helpText: 'Hide your profile from everyone. Only people you like can see you.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n {\n key: 'travelMode', label: 'Travel mode',\n helpText: 'Browse profiles in any city or district — without being there physically.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n {\n key: 'messageBeforeMatch', label: 'Message first',\n helpText: 'Send a message to someone before they swipe on you.',\n plans: { spark: false, gold: false, diamond: false, platinum: true, black: true },\n },\n {\n key: 'exclusiveLeague', label: 'Exclusive league',\n helpText: 'Your profile is only shown to other Black members. A private, curated experience.',\n plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },\n },\n {\n key: 'vipSupport', label: 'VIP support',\n helpText: 'Get priority help from a dedicated support team.',\n plans: { spark: false, gold: false, diamond: false, platinum: false, black: true },\n },\n {\n key: 'showAds', label: 'No ads',\n helpText: 'Enjoy a clean, ad-free experience throughout the app.',\n plans: { spark: false, gold: true, diamond: true, platinum: true, black: true },\n },\n];\n\n/**\n * Get features for a plan, split into included (with values) and locked.\n * Included features come first, locked features last.\n */\nexport function getPlanFeatureDisplay(planId: PlanId): {\n included: { key: string; label: string; helpText: string; value: string | true }[];\n locked: { key: string; label: string; helpText: string }[];\n} {\n const included: { key: string; label: string; helpText: string; value: string | true }[] = [];\n const locked: { key: string; label: string; helpText: string }[] = [];\n\n for (const f of PLAN_FEATURE_DISPLAY) {\n const val = f.plans[planId];\n if (val === false) {\n locked.push({ key: f.key, label: f.label, helpText: f.helpText });\n } else if (val === true) {\n included.push({ key: f.key, label: f.label, helpText: f.helpText, value: true });\n } else {\n included.push({ key: f.key, label: f.label, helpText: f.helpText, value: val });\n }\n }\n\n return { included, locked };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Get the limits object for a plan. */\nexport function getPlanLimits(planId: PlanId): PlanLimits {\n return PLAN_LIMITS[planId];\n}\n\n/** Get a specific limit value for a plan + feature. */\nexport function getPlanLimit<K extends FeatureKey>(planId: PlanId, feature: K): PlanLimits[K] {\n return PLAN_LIMITS[planId][feature];\n}\n\n/**\n * Check if a boolean feature is enabled for a plan.\n * Returns `false` for non-boolean features.\n */\nexport function isFeatureEnabled(planId: PlanId, feature: BooleanFeatureKey): boolean {\n return PLAN_LIMITS[planId][feature];\n}\n\n/**\n * Check if a numeric feature has remaining capacity.\n * Returns `true` if the limit is Infinity or greater than current usage.\n */\nexport function hasRemainingQuota(\n planId: PlanId,\n feature: NumericFeatureKey,\n currentUsage: number,\n): boolean {\n const limit = PLAN_LIMITS[planId][feature];\n return limit === Infinity || currentUsage < limit;\n}\n\n/**\n * Get the minimum plan required for a boolean feature.\n * Returns undefined if no plan enables it.\n */\nexport function getMinPlanForFeature(feature: BooleanFeatureKey): PlanId | undefined {\n return PLAN_IDS.find((id) => PLAN_LIMITS[id][feature] === true);\n}\n"]}
1
+ {"version":3,"file":"plan-limits.js","sourceRoot":"","sources":["../src/plan-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,yBAAyB,IAAI,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAkGlG,gFAAgF;AAEhF,MAAM,CAAC,MAAM,WAAW,GAA+B;IACrD,0EAA0E;IAC1E,KAAK,EAAE;QACL,WAAW,EAAE,GAAG;QAChB,gBAAgB,EAAE,CAAC;QACnB,YAAY,EAAE,CAAC;QACf,kBAAkB,EAAE,EAAE;QACtB,qBAAqB,EAAE,QAAQ,EAAE,wCAAwC;QACzE,aAAa,EAAE,CAAC;QAChB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,CAAC;QAClB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,CAAC;QACvB,WAAW,EAAE,OAAO;QACpB,WAAW,EAAE,MAAM;QACnB,aAAa,EAAE,OAAO;QACtB,cAAc,EAAE,MAAM;QACtB,YAAY,EAAE,KAAK;QACnB,aAAa,EAAE,KAAK;QACpB,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,KAAK;QACjB,UAAU,EAAE,KAAK;QACjB,OAAO,EAAE,IAAI;QACb,kBAAkB,EAAE,KAAK;QACzB,SAAS,EAAE,KAAK;QAChB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,UAAU,EAAE,KAAK;QACjB,oBAAoB,EAAE,KAAK;QAC3B,aAAa,EAAE,KAAK;QACpB,eAAe,EAAE,KAAK;KACvB;IAED,oEAAoE;IACpE,IAAI,EAAE;QACJ,WAAW,EAAE,QAAQ,EAAE,8BAA8B;QACrD,gBAAgB,EAAE,CAAC;QACnB,YAAY,EAAE,QAAQ,EAAE,+BAA+B;QACvD,kBAAkB,EAAE,QAAQ;QAC5B,qBAAqB,EAAE,QAAQ;QAC/B,aAAa,EAAE,CAAC;QAChB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,CAAC,EAAE,gCAAgC;QACpD,eAAe,EAAE,CAAC;QAClB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,QAAQ;QAC9B,WAAW,EAAE,UAAU;QACvB,WAAW,EAAE,SAAS;QACtB,aAAa,EAAE,SAAS;QACxB,cAAc,EAAE,OAAO;QACvB,YAAY,EAAE,KAAK;QACnB,aAAa,EAAE,KAAK;QACpB,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,KAAK;QACjB,UAAU,EAAE,KAAK;QACjB,OAAO,EAAE,KAAK;QACd,kBAAkB,EAAE,KAAK;QACzB,SAAS,EAAE,KAAK;QAChB,iBAAiB,EAAE,KAAK;QACxB,kBAAkB,EAAE,KAAK;QACzB,UAAU,EAAE,KAAK;QACjB,oBAAoB,EAAE,IAAI;QAC1B,aAAa,EAAE,KAAK;QACpB,eAAe,EAAE,KAAK;KACvB;IAED,qEAAqE;IACrE,OAAO,EAAE;QACP,WAAW,EAAE,QAAQ;QACrB,gBAAgB,EAAE,CAAC;QACnB,YAAY,EAAE,QAAQ;QACtB,kBAAkB,EAAE,QAAQ;QAC5B,qBAAqB,EAAE,QAAQ;QAC/B,aAAa,EAAE,CAAC;QAChB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,CAAC;QAClB,oBAAoB,EAAE,EAAE;QACxB,oBAAoB,EAAE,EAAE;QACxB,oBAAoB,EAAE,CAAC;QACvB,oBAAoB,EAAE,QAAQ;QAC9B,WAAW,EAAE,MAAM;QACnB,WAAW,EAAE,MAAM;QACnB,aAAa,EAAE,SAAS;QACxB,cAAc,EAAE,OAAO;QACvB,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,KAAK;QACpB,aAAa,EAAE,IAAI;QACnB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,KAAK;QACd,kBAAkB,EAAE,IAAI;QACxB,SAAS,EAAE,KAAK;QAChB,iBAAiB,EAAE,IAAI;QACvB,kBAAkB,EAAE,KAAK;QACzB,UAAU,EAAE,KAAK;QACjB,oBAAoB,EAAE,IAAI;QAC1B,aAAa,EAAE,KAAK;QACpB,eAAe,EAAE,KAAK;KACvB;IAED,8DAA8D;IAC9D,QAAQ,EAAE;QACR,WAAW,EAAE,QAAQ;QACrB,gBAAgB,EAAE,EAAE;QACpB,YAAY,EAAE,QAAQ;QACtB,kBAAkB,EAAE,QAAQ;QAC5B,qBAAqB,EAAE,QAAQ;QAC/B,aAAa,EAAE,CAAC;QAChB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,QAAQ;QACzB,eAAe,EAAE,QAAQ;QACzB,oBAAoB,EAAE,QAAQ;QAC9B,oBAAoB,EAAE,QAAQ;QAC9B,oBAAoB,EAAE,QAAQ;QAC9B,oBAAoB,EAAE,QAAQ;QAC9B,WAAW,EAAE,MAAM;QACnB,WAAW,EAAE,UAAU;QACvB,aAAa,EAAE,SAAS;QACxB,cAAc,EAAE,WAAW;QAC3B,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,IAAI;QACnB,aAAa,EAAE,IAAI;QACnB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,KAAK;QACd,kBAAkB,EAAE,IAAI;QACxB,SAAS,EAAE,IAAI;QACf,iBAAiB,EAAE,IAAI;QACvB,kBAAkB,EAAE,IAAI;QACxB,UAAU,EAAE,IAAI;QAChB,oBAAoB,EAAE,IAAI;QAC1B,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,KAAK;KACvB;IAED,wDAAwD;IACxD,KAAK,EAAE;QACL,WAAW,EAAE,QAAQ;QACrB,gBAAgB,EAAE,EAAE;QACpB,YAAY,EAAE,QAAQ;QACtB,kBAAkB,EAAE,QAAQ;QAC5B,qBAAqB,EAAE,QAAQ;QAC/B,aAAa,EAAE,CAAC;QAChB,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,QAAQ;QACzB,eAAe,EAAE,QAAQ;QACzB,oBAAoB,EAAE,QAAQ;QAC9B,oBAAoB,EAAE,QAAQ;QAC9B,oBAAoB,EAAE,QAAQ;QAC9B,oBAAoB,EAAE,QAAQ;QAC9B,WAAW,EAAE,MAAM;QACnB,WAAW,EAAE,UAAU;QACvB,aAAa,EAAE,SAAS;QACxB,cAAc,EAAE,WAAW;QAC3B,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,IAAI;QACnB,aAAa,EAAE,IAAI;QACnB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,KAAK;QACd,kBAAkB,EAAE,IAAI;QACxB,SAAS,EAAE,IAAI;QACf,iBAAiB,EAAE,IAAI;QACvB,kBAAkB,EAAE,IAAI;QACxB,UAAU,EAAE,IAAI;QAChB,oBAAoB,EAAE,IAAI;QAC1B,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,IAAI;KACtB;CACO,CAAC;AAEX,+EAA+E;AAE/E,wCAAwC;AACxC,MAAM,CAAC,MAAM,QAAQ,GAAa,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAEpF,mCAAmC;AACnC,MAAM,CAAC,MAAM,UAAU,GAAa,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAE7E,yCAAyC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAa,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAExE,2CAA2C;AAC3C,MAAM,CAAC,MAAM,SAAS,GAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAEzD,2CAA2C;AAC3C,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,MAAM,KAAK,OAAO,CAAC;AAC5B,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,SAAS;IACT,iBAAiB;IACjB,eAAe;IACf,QAAQ;IACR,YAAY;IACZ,eAAe;IACf,OAAO;IACP,gBAAgB;CACR,CAAC;AA4BX,6DAA6D;AAC7D,MAAM,UAAU,kBAAkB,CAAC,KAA6B,EAAE,KAAa;IAC7E,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,OAAO,EAAE,yBAAyB,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE5F;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAIlD,MAAM,QAAQ,GAA6E,EAAE,CAAC;IAC9F,MAAM,MAAM,GAAuD,EAAE,CAAC;IAEtE,KAAK,MAAM,CAAC,IAAI,0BAA0B,EAAE,CAAC;QAC3C,mCAAmC;QACnC,IAAI,CAAC,CAAC,CAAC,SAAS;YAAE,SAAS;QAE3B,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpE,CAAC;aAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnF,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,kBAAkB,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/G,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC;AAED,gFAAgF;AAEhF,wCAAwC;AACxC,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAuB,MAAc,EAAE,OAAU;IAC3E,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAA0B;IACzE,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,OAA0B,EAC1B,YAAoB;IAEpB,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3C,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,GAAG,KAAK,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA0B;IAC7D,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;AAClE,CAAC","sourcesContent":["/**\n * dating-schema — Plan limits configuration\n *\n * Single source of truth for feature gating by pricing plan.\n * Both backend middleware and frontend UI read from this config.\n *\n * To add/remove a gated feature:\n * 1. Add/remove the key in PlanLimits interface\n * 2. Set values for each plan in PLAN_LIMITS\n * 3. Backend: add enforceDatingPlanLimit('featureKey') middleware to the route\n * That's it — no scattered if-checks needed.\n *\n * @module dating-schema/plan-limits\n */\n\nimport { PLAN_FEATURE_DISPLAY_DATA as PLAN_FEATURE_DISPLAY_ITEMS } from './plan-features-data.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** The 5 pricing plans. Black is the exclusive celebrity/HNI tier. */\nexport type PlanId = 'spark' | 'gold' | 'diamond' | 'platinum' | 'black';\n\n/** Filter capability levels by plan. */\nexport type FilterLevel = 'basic' | 'advanced' | 'full';\n\n/** \"Who liked you\" visibility levels by plan. */\nexport type WhoLikedYouLevel = 'none' | 'blurred' | 'full' | 'realtime';\n\n/** Explore category access levels by plan. */\nexport type ExploreAccessLevel = 'basic' | 'premium';\n\n/** Compatibility score breakdown visibility by plan. */\nexport type ScoreBreakdownLevel = 'none' | 'score' | 'breakdown';\n\n/** All numeric limit keys (daily/periodic counters enforced server-side). */\nexport type NumericFeatureKey =\n | 'dailySwipes'\n | 'dailyCompliments'\n | 'dailyRewinds'\n | 'dailyProfileVisits'\n | 'monthlyNewChatThreads'\n | 'monthlyBoosts'\n | 'weeklyAutoBoost'\n | 'dailyVoiceCalls'\n | 'dailyVideoCalls'\n | 'voiceCallDurationMin'\n | 'videoCallDurationMin'\n | 'monthlyIntentChanges'\n | 'scoreVisibleProfiles';\n\n/** All boolean feature keys (on/off per plan). */\nexport type BooleanFeatureKey =\n | 'readReceipts'\n | 'incognitoMode'\n | 'voiceMessages'\n | 'voiceCalls'\n | 'videoCalls'\n | 'showAds'\n | 'priorityVisibility'\n | 'autoBoost'\n | 'seeProfileViewers'\n | 'messageBeforeMatch'\n | 'travelMode'\n | 'profileContentUnlock'\n | 'photoInsights'\n | 'exclusiveLeague';\n\n/** All string-enum feature keys. */\nexport type EnumFeatureKey = 'filterLevel' | 'whoLikedYou' | 'exploreAccess' | 'scoreBreakdown';\n\n/** Union of all feature keys that can be gated. */\nexport type FeatureKey = NumericFeatureKey | BooleanFeatureKey | EnumFeatureKey;\n\n/** Complete plan limits definition. */\nexport interface PlanLimits {\n // ── Numeric limits (daily/periodic) ──\n dailySwipes: number;\n dailyCompliments: number;\n dailyRewinds: number;\n dailyProfileVisits: number;\n monthlyNewChatThreads: number;\n monthlyBoosts: number;\n weeklyAutoBoost: number;\n dailyVoiceCalls: number;\n dailyVideoCalls: number;\n voiceCallDurationMin: number;\n videoCallDurationMin: number;\n monthlyIntentChanges: number;\n scoreVisibleProfiles: number;\n\n // ── Enum limits ──\n filterLevel: FilterLevel;\n whoLikedYou: WhoLikedYouLevel;\n exploreAccess: ExploreAccessLevel;\n scoreBreakdown: ScoreBreakdownLevel;\n\n // ── Boolean flags ──\n readReceipts: boolean;\n incognitoMode: boolean;\n voiceMessages: boolean;\n voiceCalls: boolean;\n videoCalls: boolean;\n showAds: boolean;\n priorityVisibility: boolean;\n autoBoost: boolean;\n seeProfileViewers: boolean;\n messageBeforeMatch: boolean;\n travelMode: boolean;\n profileContentUnlock: boolean;\n photoInsights: boolean;\n exclusiveLeague: boolean;\n}\n\n// ─── Plan Limits Config ──────────────────────────────────────────────────────\n\nexport const PLAN_LIMITS: Record<PlanId, PlanLimits> = {\n // ── Spark (Free) — Generous enough to hook, limited enough to upgrade ──\n spark: {\n dailySwipes: 150,\n dailyCompliments: 1,\n dailyRewinds: 0,\n dailyProfileVisits: 20,\n monthlyNewChatThreads: Infinity, // no chat cap — matches can always chat\n monthlyBoosts: 0,\n weeklyAutoBoost: 0,\n dailyVoiceCalls: 0,\n dailyVideoCalls: 0,\n voiceCallDurationMin: 0,\n videoCallDurationMin: 0,\n monthlyIntentChanges: 2,\n scoreVisibleProfiles: 3,\n filterLevel: 'basic',\n whoLikedYou: 'none',\n exploreAccess: 'basic',\n scoreBreakdown: 'none',\n readReceipts: false,\n incognitoMode: false,\n voiceMessages: false,\n voiceCalls: false,\n videoCalls: false,\n showAds: true,\n priorityVisibility: false,\n autoBoost: false,\n seeProfileViewers: false,\n messageBeforeMatch: false,\n travelMode: false,\n profileContentUnlock: false,\n photoInsights: false,\n exclusiveLeague: false,\n },\n\n // ── Gold — Removes all friction, more generous than Tinder Plus ──\n gold: {\n dailySwipes: Infinity, // no swipe cap for paid users\n dailyCompliments: 3,\n dailyRewinds: Infinity, // no rewind cap for paid users\n dailyProfileVisits: Infinity,\n monthlyNewChatThreads: Infinity,\n monthlyBoosts: 1,\n weeklyAutoBoost: 0,\n dailyVoiceCalls: 0, // voice/video starts at Diamond\n dailyVideoCalls: 0,\n voiceCallDurationMin: 0,\n videoCallDurationMin: 0,\n monthlyIntentChanges: 4,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'advanced',\n whoLikedYou: 'blurred',\n exploreAccess: 'premium',\n scoreBreakdown: 'score',\n readReceipts: false,\n incognitoMode: false,\n voiceMessages: false,\n voiceCalls: false,\n videoCalls: false,\n showAds: false,\n priorityVisibility: false,\n autoBoost: false,\n seeProfileViewers: false,\n messageBeforeMatch: false,\n travelMode: false,\n profileContentUnlock: true,\n photoInsights: false,\n exclusiveLeague: false,\n },\n\n // ── Diamond (DECOY) — Almost Platinum but capped, pushes upgrade ──\n diamond: {\n dailySwipes: Infinity,\n dailyCompliments: 5,\n dailyRewinds: Infinity,\n dailyProfileVisits: Infinity,\n monthlyNewChatThreads: Infinity,\n monthlyBoosts: 3,\n weeklyAutoBoost: 0,\n dailyVoiceCalls: 3,\n dailyVideoCalls: 2,\n voiceCallDurationMin: 15,\n videoCallDurationMin: 10,\n monthlyIntentChanges: 8,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'full',\n whoLikedYou: 'full',\n exploreAccess: 'premium',\n scoreBreakdown: 'score',\n readReceipts: true,\n incognitoMode: false,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: true,\n autoBoost: false,\n seeProfileViewers: true,\n messageBeforeMatch: false,\n travelMode: false,\n profileContentUnlock: true,\n photoInsights: false,\n exclusiveLeague: false,\n },\n\n // ── Platinum — The aspirational tier, everything unlocked ──\n platinum: {\n dailySwipes: Infinity,\n dailyCompliments: 10,\n dailyRewinds: Infinity,\n dailyProfileVisits: Infinity,\n monthlyNewChatThreads: Infinity,\n monthlyBoosts: 5,\n weeklyAutoBoost: 1,\n dailyVoiceCalls: Infinity,\n dailyVideoCalls: Infinity,\n voiceCallDurationMin: Infinity,\n videoCallDurationMin: Infinity,\n monthlyIntentChanges: Infinity,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'full',\n whoLikedYou: 'realtime',\n exploreAccess: 'premium',\n scoreBreakdown: 'breakdown',\n readReceipts: true,\n incognitoMode: true,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: true,\n autoBoost: true,\n seeProfileViewers: true,\n messageBeforeMatch: true,\n travelMode: true,\n profileContentUnlock: true,\n photoInsights: true,\n exclusiveLeague: false,\n },\n\n // ── Black — Exclusive league for celebrities & HNIs ──\n black: {\n dailySwipes: Infinity,\n dailyCompliments: 15,\n dailyRewinds: Infinity,\n dailyProfileVisits: Infinity,\n monthlyNewChatThreads: Infinity,\n monthlyBoosts: 5,\n weeklyAutoBoost: 1,\n dailyVoiceCalls: Infinity,\n dailyVideoCalls: Infinity,\n voiceCallDurationMin: Infinity,\n videoCallDurationMin: Infinity,\n monthlyIntentChanges: Infinity,\n scoreVisibleProfiles: Infinity,\n filterLevel: 'full',\n whoLikedYou: 'realtime',\n exploreAccess: 'premium',\n scoreBreakdown: 'breakdown',\n readReceipts: true,\n incognitoMode: true,\n voiceMessages: true,\n voiceCalls: true,\n videoCalls: true,\n showAds: false,\n priorityVisibility: true,\n autoBoost: true,\n seeProfileViewers: true,\n messageBeforeMatch: true,\n travelMode: true,\n profileContentUnlock: true,\n photoInsights: true,\n exclusiveLeague: true,\n },\n} as const;\n\n// ─── Plan Tier Helpers ──────────────────────────────────────────────────────\n\n/** All valid plan IDs in tier order. */\nexport const PLAN_IDS: PlanId[] = ['spark', 'gold', 'diamond', 'platinum', 'black'];\n\n/** Paid plans (Gold and above). */\nexport const PAID_PLANS: PlanId[] = ['gold', 'diamond', 'platinum', 'black'];\n\n/** Premium plans (Diamond and above). */\nexport const PREMIUM_PLANS: PlanId[] = ['diamond', 'platinum', 'black'];\n\n/** Top-tier plans (Platinum and Black). */\nexport const TOP_PLANS: PlanId[] = ['platinum', 'black'];\n\n/** Check if a plan is paid (not Spark). */\nexport function isPaidPlan(planId: PlanId): boolean {\n return planId !== 'spark';\n}\n\n/** Premium explore categories that require Gold+ to access. */\nexport const PREMIUM_EXPLORE_CATEGORIES = [\n 'high-iq',\n 'elite-institute',\n 'wealth-dating',\n 'casual',\n 'nri-dating',\n 'senior-dating',\n 'lgbtq',\n 'second-innings',\n] as const;\n\n// ─── Feature Display Config (UI source of truth) ────────────────────────────\n\n/** How a feature appears in UI — per-plan value can be a string (quantity), true (check), or false (locked). */\n/** Structured value for numeric/descriptive plan features */\nexport interface FeatureValue {\n /** Quantity — e.g. \"10\", \"Unlimited\", \"3\", \"All 147\", \"Blurred preview\" */\n qty: string;\n /** Feature noun — e.g. \"compliments\", \"swipes\", \"filters\", \"of likes\" */\n text: string;\n /** Time/frequency unit — e.g. \"per day\", \"per month\". Omit if not applicable. */\n unit?: string;\n}\n\nexport interface FeatureDisplayItem {\n /** Unique key matching a FeatureKey or custom display key */\n key: string;\n /** Short, clear label shown when feature is locked */\n label: string;\n /** Tooltip help text explaining what this feature does */\n helpText: string;\n /** Whether this feature is live. If false, hidden from UI entirely (not yet rolled out). */\n available: boolean;\n /** Per-plan display value: FeatureValue = structured text, true = checkmark, false = locked */\n plans: Record<PlanId, FeatureValue | boolean>;\n}\n\n/** Render a plan feature value to a single display string */\nexport function renderFeatureValue(value: FeatureValue | boolean, label: string): string {\n if (value === true) return label;\n if (value === false) return label;\n const parts = [value.qty, value.text];\n if (value.unit) parts.push(value.unit);\n return parts.join(' ');\n}\n\n/**\n * Master feature list for plan comparison UI.\n * Now in plan-features-data.ts for cleaner separation.\n */\nexport { PLAN_FEATURE_DISPLAY_DATA as PLAN_FEATURE_DISPLAY } from './plan-features-data.js';\n\n/**\n * Get features for a plan, split into included (with values) and locked.\n * Included features come first, locked features last.\n * Only includes features where `available: true` (hides unreleased features).\n */\nexport function getPlanFeatureDisplay(planId: PlanId): {\n included: { key: string; label: string; helpText: string; value: string | true }[];\n locked: { key: string; label: string; helpText: string }[];\n} {\n const included: { key: string; label: string; helpText: string; value: string | true }[] = [];\n const locked: { key: string; label: string; helpText: string }[] = [];\n\n for (const f of PLAN_FEATURE_DISPLAY_ITEMS) {\n // Skip features not yet rolled out\n if (!f.available) continue;\n\n const val = f.plans[planId];\n if (val === false) {\n locked.push({ key: f.key, label: f.label, helpText: f.helpText });\n } else if (val === true) {\n included.push({ key: f.key, label: f.label, helpText: f.helpText, value: true });\n } else {\n included.push({ key: f.key, label: f.label, helpText: f.helpText, value: renderFeatureValue(val, f.label) });\n }\n }\n\n return { included, locked };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Get the limits object for a plan. */\nexport function getPlanLimits(planId: PlanId): PlanLimits {\n return PLAN_LIMITS[planId];\n}\n\n/** Get a specific limit value for a plan + feature. */\nexport function getPlanLimit<K extends FeatureKey>(planId: PlanId, feature: K): PlanLimits[K] {\n return PLAN_LIMITS[planId][feature];\n}\n\n/**\n * Check if a boolean feature is enabled for a plan.\n * Returns `false` for non-boolean features.\n */\nexport function isFeatureEnabled(planId: PlanId, feature: BooleanFeatureKey): boolean {\n return PLAN_LIMITS[planId][feature];\n}\n\n/**\n * Check if a numeric feature has remaining capacity.\n * Returns `true` if the limit is Infinity or greater than current usage.\n */\nexport function hasRemainingQuota(\n planId: PlanId,\n feature: NumericFeatureKey,\n currentUsage: number,\n): boolean {\n const limit = PLAN_LIMITS[planId][feature];\n return limit === Infinity || currentUsage < limit;\n}\n\n/**\n * Get the minimum plan required for a boolean feature.\n * Returns undefined if no plan enables it.\n */\nexport function getMinPlanForFeature(feature: BooleanFeatureKey): PlanId | undefined {\n return PLAN_IDS.find((id) => PLAN_LIMITS[id][feature] === true);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dating-schema",
3
- "version": "0.35.1",
3
+ "version": "0.36.1",
4
4
  "description": "Shared Zod schemas for the Dating feature — single source of truth for API contracts, validation, and TypeScript types (frontend + backend).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",