Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x | /**
* Subscription tier limits — single source of truth.
*
* Both server-side enforcement and client-side UI (DurationSelector, etc.)
* derive from these values. Change a limit here and it propagates everywhere.
*/
/** All available session duration options (minutes). */
export const DURATION_OPTIONS = [5, 10, 15, 20] as const
export type DurationOption = (typeof DURATION_OPTIONS)[number]
export type TierName = 'guest' | 'free' | 'family'
export interface TierLimits {
maxPracticeStudents: number
maxSessionMinutes: DurationOption
maxSessionsPerWeek: number // Infinity = unlimited
maxOfflineParsingPerMonth: number
}
export const TIER_LIMITS: Record<TierName, TierLimits> = {
guest: {
maxPracticeStudents: 1,
maxSessionMinutes: 10,
maxSessionsPerWeek: Infinity, // can't enforce — cookies clearable
maxOfflineParsingPerMonth: 3,
},
free: {
maxPracticeStudents: 1,
maxSessionMinutes: 10,
maxSessionsPerWeek: 5,
maxOfflineParsingPerMonth: 3,
},
family: {
maxPracticeStudents: Infinity,
maxSessionMinutes: 20,
maxSessionsPerWeek: Infinity,
maxOfflineParsingPerMonth: 30,
},
} as const
/** Duration options available for a given tier. */
export function durationOptionsForTier(tier: TierName): DurationOption[] {
const max = TIER_LIMITS[tier].maxSessionMinutes
return DURATION_OPTIONS.filter((d) => d <= max)
}
|