fix production issues
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 51s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped

This commit is contained in:
root
2026-08-12 16:48:41 -04:00
parent 53de25120a
commit 8fc88ffc14
117 changed files with 4717 additions and 1443 deletions
+30 -58
View File
@@ -19,69 +19,41 @@ export interface ApiError {
[key: string]: unknown
}
// Plan prices in smallest currency unit (MAD, in centimes)
export const PLAN_PRICES: Record<string, Record<string, Record<string, number>>> = {
STARTER: {
MONTHLY: { MAD: 14900 },
ANNUAL: { MAD: 143040 },
},
GROWTH: {
MONTHLY: { MAD: 29900 },
ANNUAL: { MAD: 287040 },
},
PRO: {
MONTHLY: { MAD: 39900 },
ANNUAL: { MAD: 383040 },
},
ENTERPRISE: {
MONTHLY: { MAD: 59900 },
ANNUAL: { MAD: 575040 },
},
}
export {
PLAN_PRICES,
PLAN_ENTITLEMENTS,
SUBSCRIPTION_CAPABILITIES,
ANNUAL_DISCOUNT_PERCENT,
PUBLIC_PRICING_PLAN_MAP,
getVehicleLimit,
planHasCapability,
getPublicMonthlyMajorUnits,
SUBSCRIPTION_PLAN_IDS,
BILLING_PERIODS,
} from './planCatalog'
export type {
SubscriptionPlanId,
SubscriptionCapability,
CatalogBillingPeriod,
PublicPricingPlanId,
PlanEntitlements,
} from './planCatalog'
import { PLAN_ENTITLEMENTS, type SubscriptionCapability } from './planCatalog'
/** Display labels derived from the entitlement catalog (not used for enforcement). */
export const PLAN_FEATURES: Record<string, string[]> = {
STARTER: [
'Up to 25 vehicles',
'1 user account',
'Basic analytics',
'Carplace listing',
'Notification management',
],
GROWTH: [
'Up to 75 vehicles',
'5 user accounts',
'Full analytics',
'Priority Carplace placement',
'Custom branding',
],
PRO: [
'Up to 150 vehicles',
'Unlimited user accounts',
'Advanced reports',
'API access',
'Dedicated support',
],
ENTERPRISE: [
'150+ vehicles',
'Unlimited user accounts',
'Advanced reports',
'API access',
'Dedicated support',
],
STARTER: PLAN_ENTITLEMENTS.STARTER.featureLabels,
GROWTH: PLAN_ENTITLEMENTS.GROWTH.featureLabels,
PRO: PLAN_ENTITLEMENTS.PRO.featureLabels,
ENTERPRISE: PLAN_ENTITLEMENTS.ENTERPRISE.featureLabels,
}
export const SUBSCRIPTION_CAPABILITIES = {
NOTIFICATION_MANAGEMENT: 'NOTIFICATION_MANAGEMENT',
} as const
export type SubscriptionCapability =
(typeof SUBSCRIPTION_CAPABILITIES)[keyof typeof SUBSCRIPTION_CAPABILITIES]
export const PLAN_CAPABILITIES: Record<string, SubscriptionCapability[]> = {
STARTER: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
GROWTH: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
PRO: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
ENTERPRISE: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
STARTER: PLAN_ENTITLEMENTS.STARTER.capabilities,
GROWTH: PLAN_ENTITLEMENTS.GROWTH.capabilities,
PRO: PLAN_ENTITLEMENTS.PRO.capabilities,
ENTERPRISE: PLAN_ENTITLEMENTS.ENTERPRISE.capabilities,
}
export type Locale = 'en' | 'fr' | 'ar'
+1
View File
@@ -2,3 +2,4 @@ export * from './damage'
export * from './fuel'
export * from './api'
export * from './carplace-homepage'
// planCatalog is re-exported via api.ts (single public surface)
+42
View File
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest'
import {
ANNUAL_DISCOUNT_PERCENT,
PLAN_ENTITLEMENTS,
PLAN_PRICES,
PUBLIC_PRICING_PLAN_MAP,
SUBSCRIPTION_PLAN_IDS,
getPublicMonthlyMajorUnits,
getVehicleLimit,
} from './planCatalog'
import { PLAN_FEATURES as apiPlanFeatures, PLAN_PRICES as apiPlanPrices } from './api'
describe('planCatalog source of truth', () => {
it('defines all subscription plans with prices and entitlements', () => {
for (const plan of SUBSCRIPTION_PLAN_IDS) {
expect(PLAN_PRICES[plan].MONTHLY.MAD).toBeGreaterThan(0)
expect(PLAN_PRICES[plan].ANNUAL.MAD).toBeGreaterThan(0)
expect(PLAN_ENTITLEMENTS[plan].featureLabels.length).toBeGreaterThan(0)
}
})
it('keeps api.ts re-exports aligned with the catalog', () => {
expect(apiPlanPrices).toEqual(PLAN_PRICES)
expect(apiPlanFeatures.STARTER).toEqual(PLAN_ENTITLEMENTS.STARTER.featureLabels)
})
it('maps public marketing plans to subscription plans without price drift', () => {
expect(PUBLIC_PRICING_PLAN_MAP.launch).toBe('STARTER')
expect(PUBLIC_PRICING_PLAN_MAP.growth).toBe('GROWTH')
expect(getPublicMonthlyMajorUnits('launch')).toBe(PLAN_PRICES.STARTER.MONTHLY.MAD / 100)
expect(getPublicMonthlyMajorUnits('growth')).toBe(PLAN_PRICES.GROWTH.MONTHLY.MAD / 100)
expect(getPublicMonthlyMajorUnits('enterprise')).toBeNull()
expect(ANNUAL_DISCOUNT_PERCENT).toBe(20)
})
it('exposes typed vehicle limits for enforcement', () => {
expect(getVehicleLimit('STARTER')).toBe(25)
expect(getVehicleLimit('GROWTH')).toBe(75)
expect(getVehicleLimit('PRO')).toBe(150)
expect(getVehicleLimit('ENTERPRISE')).toBeNull()
})
})
+132
View File
@@ -0,0 +1,132 @@
/**
* Single commercial plan / entitlement catalog.
* Marketing, checkout, API enforcement, and invoices must consume this module
* (or DB PricingConfig overrides that stay aligned with these plan IDs).
*/
export const SUBSCRIPTION_PLAN_IDS = ['STARTER', 'GROWTH', 'PRO', 'ENTERPRISE'] as const
export type SubscriptionPlanId = (typeof SUBSCRIPTION_PLAN_IDS)[number]
export const BILLING_PERIODS = ['MONTHLY', 'ANNUAL'] as const
export type CatalogBillingPeriod = (typeof BILLING_PERIODS)[number]
export const SUBSCRIPTION_CAPABILITIES = {
NOTIFICATION_MANAGEMENT: 'NOTIFICATION_MANAGEMENT',
} as const
export type SubscriptionCapability =
(typeof SUBSCRIPTION_CAPABILITIES)[keyof typeof SUBSCRIPTION_CAPABILITIES]
export type PlanEntitlements = {
/** null = unlimited */
maxVehicles: number | null
maxUsers: number | null
capabilities: SubscriptionCapability[]
/** Marketing display labels (not used for enforcement) */
featureLabels: string[]
}
/** Prices in smallest currency unit (MAD centimes). */
export type PlanPriceTable = Record<
SubscriptionPlanId,
Record<CatalogBillingPeriod, Record<'MAD', number>>
>
export const PLAN_ENTITLEMENTS: Record<SubscriptionPlanId, PlanEntitlements> = {
STARTER: {
maxVehicles: 25,
maxUsers: 1,
capabilities: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
featureLabels: [
'Up to 25 vehicles',
'1 user account',
'Basic analytics',
'Carplace listing',
'Notification management',
],
},
GROWTH: {
maxVehicles: 75,
maxUsers: 5,
capabilities: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
featureLabels: [
'Up to 75 vehicles',
'5 user accounts',
'Full analytics',
'Priority Carplace placement',
'Custom branding',
],
},
PRO: {
maxVehicles: 150,
maxUsers: null,
capabilities: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
featureLabels: [
'Up to 150 vehicles',
'Unlimited user accounts',
'Advanced reports',
'API access',
'Dedicated support',
],
},
ENTERPRISE: {
maxVehicles: null,
maxUsers: null,
capabilities: [SUBSCRIPTION_CAPABILITIES.NOTIFICATION_MANAGEMENT],
featureLabels: [
'150+ vehicles',
'Unlimited user accounts',
'Advanced reports',
'API access',
'Dedicated support',
],
},
}
export const PLAN_PRICES: PlanPriceTable = {
STARTER: {
MONTHLY: { MAD: 14900 },
ANNUAL: { MAD: 143040 },
},
GROWTH: {
MONTHLY: { MAD: 29900 },
ANNUAL: { MAD: 287040 },
},
PRO: {
MONTHLY: { MAD: 39900 },
ANNUAL: { MAD: 383040 },
},
ENTERPRISE: {
MONTHLY: { MAD: 59900 },
ANNUAL: { MAD: 575040 },
},
}
/** Public homepage marketing IDs mapped to subscription plan IDs. */
export const PUBLIC_PRICING_PLAN_MAP = {
launch: 'STARTER',
growth: 'GROWTH',
/** Public "enterprise" is custom quote; checkout still uses ENTERPRISE entitlements when sold. */
enterprise: 'ENTERPRISE',
} as const
export type PublicPricingPlanId = keyof typeof PUBLIC_PRICING_PLAN_MAP
export const ANNUAL_DISCOUNT_PERCENT = 20
export function getVehicleLimit(plan: string): number | null {
const entitlements = PLAN_ENTITLEMENTS[plan as SubscriptionPlanId]
return entitlements ? entitlements.maxVehicles : PLAN_ENTITLEMENTS.STARTER.maxVehicles
}
export function planHasCapability(plan: string, capability: SubscriptionCapability): boolean {
const entitlements = PLAN_ENTITLEMENTS[plan as SubscriptionPlanId]
return Boolean(entitlements?.capabilities.includes(capability))
}
/** Major-unit monthly MAD prices for public marketing (null = custom). */
export function getPublicMonthlyMajorUnits(planId: PublicPricingPlanId): number | null {
if (planId === 'enterprise') return null
const subscriptionPlan = PUBLIC_PRICING_PLAN_MAP[planId]
return PLAN_PRICES[subscriptionPlan].MONTHLY.MAD / 100
}