Feature States: - 'on': Feature fully accessible - 'off': Feature hidden from menus - 'coming-soon': Visible with "Coming Soon!" badge, not clickable - 'basic': Visible with "Sign Up for Free" badge, triggers Auth0 login - 'pro': Visible with "Upgrade to Pro" badge (for future upgrade flow) Changes: - Update FeatureState type to support 5 states (on/off/coming-soon/basic/pro) - Consolidate GUEST_FEATURE_CONFIG as DEFAULT_FEATURE_CONFIG - Create ComingSoonBadge component for coming-soon features - Create UpgradeBadge component for basic/pro tier requirements - Update VR Experience hamburger menu to maintain open/closed state - Make menu default to open, persist state in localStorage - Make 'basic' features clickable to trigger Auth0 sign-in - Update createDiagramModal to show appropriate badges - Fix camera initial position to match VR rig (prevent flip on load) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
170 lines
4.9 KiB
TypeScript
170 lines
4.9 KiB
TypeScript
/**
|
|
* Feature configuration system for controlling access to pages, features, and limits
|
|
* based on user tier/subscription level.
|
|
*/
|
|
|
|
export type UserTier = 'none' | 'free' | 'basic' | 'pro';
|
|
|
|
export type FeatureState = 'on' | 'coming-soon' | 'basic' | 'pro' | 'off';
|
|
|
|
export interface PageFlags {
|
|
examples: FeatureState;
|
|
documentation: FeatureState;
|
|
pricing: FeatureState;
|
|
vrExperience: FeatureState;
|
|
}
|
|
|
|
export interface FeatureFlags {
|
|
createDiagram: FeatureState;
|
|
createFromTemplate: FeatureState;
|
|
manageDiagrams: FeatureState;
|
|
shareCollaborate: FeatureState;
|
|
privateDesigns: FeatureState;
|
|
encryptedDesigns: FeatureState;
|
|
editData: FeatureState;
|
|
config: FeatureState;
|
|
enterImmersive: FeatureState;
|
|
launchMetaQuest: FeatureState;
|
|
}
|
|
|
|
export interface LimitFlags {
|
|
maxDiagrams: number;
|
|
maxCollaborators: number;
|
|
storageQuotaMB: number;
|
|
}
|
|
|
|
export interface FeatureConfig {
|
|
tier: UserTier;
|
|
pages: PageFlags;
|
|
features: FeatureFlags;
|
|
limits: LimitFlags;
|
|
}
|
|
|
|
/**
|
|
* Default configuration for unauthenticated users (guest mode).
|
|
* Allows limited access with local storage only (no sync/collaboration).
|
|
*/
|
|
export const DEFAULT_FEATURE_CONFIG: FeatureConfig = {
|
|
tier: 'none',
|
|
pages: {
|
|
examples: 'coming-soon',
|
|
documentation: 'coming-soon',
|
|
pricing: 'coming-soon',
|
|
vrExperience: 'on', // Allow VR experience for guests
|
|
},
|
|
features: {
|
|
createDiagram: 'basic', // Guests can create diagrams
|
|
createFromTemplate: 'coming-soon', // Coming soon for guests
|
|
manageDiagrams: 'basic', // Guests can manage their local diagrams
|
|
shareCollaborate: 'coming-soon', // Coming soon for guests
|
|
privateDesigns: 'coming-soon', // Coming soon for guests
|
|
encryptedDesigns: 'pro', // No encryption for guests
|
|
editData: 'coming-soon', // Guests can edit data
|
|
config: 'on', // Guests can access settings
|
|
enterImmersive: 'on', // Guests can enter immersive mode
|
|
launchMetaQuest: 'on', // Guests can launch on Meta Quest
|
|
},
|
|
limits: {
|
|
maxDiagrams: 3, // Guests limited to 3 diagrams
|
|
maxCollaborators: 0, // No collaboration for guests
|
|
storageQuotaMB: 50, // 50MB local storage for guests
|
|
},
|
|
};
|
|
|
|
export const BASIC_FEATURE_CONFIG: FeatureConfig = {
|
|
tier: 'basic',
|
|
pages: {
|
|
examples: 'off',
|
|
documentation: 'off',
|
|
pricing: 'coming-soon',
|
|
vrExperience: 'on',
|
|
},
|
|
features: {
|
|
createDiagram: 'on',
|
|
createFromTemplate: 'off',
|
|
manageDiagrams: 'off',
|
|
shareCollaborate: 'off',
|
|
privateDesigns: 'off',
|
|
encryptedDesigns: 'off',
|
|
editData: 'off',
|
|
config: 'off',
|
|
enterImmersive: 'off',
|
|
launchMetaQuest: 'off',
|
|
},
|
|
limits: {
|
|
maxDiagrams: 0,
|
|
maxCollaborators: 0,
|
|
storageQuotaMB: 0,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Type guard to check if a page name is valid
|
|
*/
|
|
export function isValidPage(page: string): page is keyof PageFlags {
|
|
return page in DEFAULT_FEATURE_CONFIG.pages;
|
|
}
|
|
|
|
/**
|
|
* Type guard to check if a feature name is valid
|
|
*/
|
|
export function isValidFeature(feature: string): feature is keyof FeatureFlags {
|
|
return feature in DEFAULT_FEATURE_CONFIG.features;
|
|
}
|
|
|
|
/**
|
|
* Type guard to check if a limit name is valid
|
|
*/
|
|
export function isValidLimit(limit: string): limit is keyof LimitFlags {
|
|
return limit in DEFAULT_FEATURE_CONFIG.limits;
|
|
}
|
|
|
|
/**
|
|
* Helper to check if a page is enabled (on) in the config
|
|
*/
|
|
export function isPageEnabled(config: FeatureConfig, page: keyof PageFlags): boolean {
|
|
return config.pages[page] === 'on';
|
|
}
|
|
|
|
/**
|
|
* Helper to check if a feature is enabled (on) in the config
|
|
*/
|
|
export function isFeatureEnabled(config: FeatureConfig, feature: keyof FeatureFlags): boolean {
|
|
return config.features[feature] === 'on';
|
|
}
|
|
|
|
/**
|
|
* Helper to check if a page or feature should be visible (not 'off')
|
|
*/
|
|
export function shouldShowPage(config: FeatureConfig, page: keyof PageFlags): boolean {
|
|
return config.pages[page] !== 'off';
|
|
}
|
|
|
|
/**
|
|
* Helper to check if a feature should be visible (not 'off')
|
|
*/
|
|
export function shouldShowFeature(config: FeatureConfig, feature: keyof FeatureFlags): boolean {
|
|
return config.features[feature] !== 'off';
|
|
}
|
|
|
|
/**
|
|
* Helper to get the state of a page
|
|
*/
|
|
export function getPageState(config: FeatureConfig, page: keyof PageFlags): FeatureState {
|
|
return config.pages[page];
|
|
}
|
|
|
|
/**
|
|
* Helper to get the state of a feature
|
|
*/
|
|
export function getFeatureState(config: FeatureConfig, feature: keyof FeatureFlags): FeatureState {
|
|
return config.features[feature];
|
|
}
|
|
|
|
/**
|
|
* Helper to get a limit value from the config
|
|
*/
|
|
export function getFeatureLimit(config: FeatureConfig, limit: keyof LimitFlags): number {
|
|
return config.limits[limit];
|
|
}
|