/** * NavigationController - Centralized navigation logic for WellNuo app * * This service provides all routing decisions based on user state, * beneficiary data, and equipment status. * * USAGE: * import { NavigationController } from '@/services/NavigationController'; * const { path, params } = NavigationController.getRouteAfterLogin(profile, beneficiaries); * router.replace(path); */ import type { Beneficiary, EquipmentStatus } from '@/types'; // ==================== ROUTE CONSTANTS ==================== export const ROUTES = { // Auth Flow AUTH: { LOGIN: '/(auth)/login', VERIFY_OTP: '/(auth)/verify-otp', ENTER_NAME: '/(auth)/enter-name', ADD_LOVED_ONE: '/(auth)/add-loved-one', PURCHASE: '/(auth)/purchase', ACTIVATE: '/(auth)/activate', WELCOME_BACK: '/(auth)/welcome-back', COMPLETE_PROFILE: '/(auth)/complete-profile', }, // Main Tabs TABS: { DASHBOARD: '/(tabs)/dashboard', BENEFICIARIES: '/(tabs)', // index.tsx shows beneficiary list CHAT: '/(tabs)/chat', VOICE: '/(tabs)/voice', PROFILE: '/(tabs)/profile', }, // Beneficiary Management BENEFICIARY: { ADD: '/(tabs)/beneficiaries/add', DETAIL: (id: number) => `/(tabs)/beneficiaries/${id}` as const, SUBSCRIPTION: (id: number) => `/(tabs)/beneficiaries/${id}/subscription` as const, EQUIPMENT: (id: number) => `/(tabs)/beneficiaries/${id}/equipment` as const, SHARE: (id: number) => `/(tabs)/beneficiaries/${id}/share` as const, }, // Profile PROFILE: { INDEX: '/(tabs)/profile', EDIT: '/(tabs)/profile/edit', NOTIFICATIONS: '/(tabs)/profile/notifications', LANGUAGE: '/(tabs)/profile/language', PRIVACY: '/(tabs)/profile/privacy', TERMS: '/(tabs)/profile/terms', SUPPORT: '/(tabs)/profile/support', ABOUT: '/(tabs)/profile/about', HELP: '/(tabs)/profile/help', }, } as const; // ==================== TYPE DEFINITIONS ==================== export interface NavigationResult { path: string; params?: Record; } export interface UserProfile { id: number; email: string; firstName: string | null; lastName: string | null; phone: string | null; } // ==================== NAVIGATION CONTROLLER ==================== export const NavigationController = { /** * Determine where to navigate after successful OTP verification * * Flow: * 1. New user without name → enter-name * 2. User without beneficiaries → add-loved-one * 3. User has beneficiary without devices → check hasDevices * - hasDevices=false → purchase (need to buy equipment) * - hasDevices=true but equipment not activated → activate * 4. User has active beneficiary → dashboard */ getRouteAfterLogin( profile: UserProfile | null, beneficiaries: Beneficiary[] ): NavigationResult { // Step 1: Check if user has name if (!profile?.firstName) { return { path: ROUTES.AUTH.ENTER_NAME, }; } // Step 2: Check if user has any beneficiaries if (!beneficiaries || beneficiaries.length === 0) { return { path: ROUTES.AUTH.ADD_LOVED_ONE, }; } // Step 3: Multiple beneficiaries - go to list, let user choose if (beneficiaries.length > 1) { return { path: ROUTES.TABS.BENEFICIARIES, }; } // Step 4: Single beneficiary - check if needs setup const singleBeneficiary = beneficiaries[0]; const isActive = singleBeneficiary.hasDevices || singleBeneficiary.equipmentStatus === 'active' || singleBeneficiary.equipmentStatus === 'demo'; if (isActive) { // Single active beneficiary - go to dashboard return { path: ROUTES.TABS.DASHBOARD, }; } // Single beneficiary needs setup return this.getRouteForBeneficiarySetup(singleBeneficiary); }, /** * Determine where to navigate for beneficiary equipment setup */ getRouteForBeneficiarySetup(beneficiary: Beneficiary): NavigationResult { const status = beneficiary.equipmentStatus || 'none'; switch (status) { case 'none': // No equipment ordered - go to purchase return { path: ROUTES.AUTH.PURCHASE, params: { beneficiaryId: beneficiary.id }, }; case 'ordered': case 'shipped': // Equipment is on the way - can show tracking or wait screen // For now, go to equipment status page return { path: ROUTES.BENEFICIARY.EQUIPMENT(beneficiary.id), params: { beneficiaryId: beneficiary.id }, }; case 'delivered': // Equipment delivered, needs activation return { path: ROUTES.AUTH.ACTIVATE, params: { beneficiaryId: beneficiary.id }, }; case 'active': case 'demo': // Already active, go to dashboard return { path: ROUTES.TABS.DASHBOARD, }; default: // Unknown status - go to dashboard return { path: ROUTES.TABS.DASHBOARD, }; } }, /** * After creating a new beneficiary, determine next step * * @param beneficiaryId - ID of newly created beneficiary * @param hasExistingDevices - User indicated they already have WellNuo devices */ getRouteAfterAddBeneficiary( beneficiaryId: number, hasExistingDevices: boolean ): NavigationResult { if (hasExistingDevices) { // User has existing devices - go directly to activation return { path: ROUTES.AUTH.ACTIVATE, params: { beneficiaryId }, }; } // User needs to purchase equipment return { path: ROUTES.AUTH.PURCHASE, params: { beneficiaryId }, }; }, /** * After successful purchase, navigate to next step */ getRouteAfterPurchase( beneficiaryId: number, purchaseResult: { skipToActivate?: boolean; demo?: boolean } ): NavigationResult { if (purchaseResult.demo || purchaseResult.skipToActivate) { // Demo mode or skip - go to activate return { path: ROUTES.AUTH.ACTIVATE, params: { beneficiaryId, demo: purchaseResult.demo }, }; } // Normal purchase - wait for equipment delivery // Go to equipment tracking page return { path: ROUTES.BENEFICIARY.EQUIPMENT(beneficiaryId), params: { beneficiaryId }, }; }, /** * After successful activation, navigate to beneficiary detail page */ getRouteAfterActivation(beneficiaryId: number): NavigationResult { return { path: ROUTES.BENEFICIARY.DETAIL(beneficiaryId), params: { justActivated: true }, }; }, /** * Get route for returning user (already has account) * * Used when user logs in with existing account */ getRouteForReturningUser( profile: UserProfile, beneficiaries: Beneficiary[] ): NavigationResult { // Same logic as after login, but could add welcome-back screen return this.getRouteAfterLogin(profile, beneficiaries); }, /** * Check if user should see onboarding */ shouldShowOnboarding( isNewUser: boolean, profile: UserProfile | null ): boolean { return isNewUser || !profile?.firstName; }, /** * Get route for a specific beneficiary action */ getBeneficiaryRoute( beneficiary: Beneficiary, action: 'view' | 'subscription' | 'equipment' | 'share' ): NavigationResult { switch (action) { case 'view': return { path: ROUTES.BENEFICIARY.DETAIL(beneficiary.id) }; case 'subscription': return { path: ROUTES.BENEFICIARY.SUBSCRIPTION(beneficiary.id) }; case 'equipment': return { path: ROUTES.BENEFICIARY.EQUIPMENT(beneficiary.id) }; case 'share': return { path: ROUTES.BENEFICIARY.SHARE(beneficiary.id) }; default: return { path: ROUTES.BENEFICIARY.DETAIL(beneficiary.id) }; } }, /** * Determine if beneficiary card should show "Setup Equipment" button */ shouldShowEquipmentSetup(beneficiary: Beneficiary): boolean { return !beneficiary.hasDevices && beneficiary.equipmentStatus !== 'active' && beneficiary.equipmentStatus !== 'demo'; }, /** * Get call-to-action for beneficiary based on status */ getBeneficiaryCallToAction( beneficiary: Beneficiary ): { label: string; action: () => NavigationResult } | null { const status = beneficiary.equipmentStatus || 'none'; if (beneficiary.hasDevices || status === 'active' || status === 'demo') { // No CTA needed - beneficiary is active return null; } switch (status) { case 'none': return { label: 'Setup Equipment', action: () => ({ path: ROUTES.AUTH.PURCHASE, params: { beneficiaryId: beneficiary.id }, }), }; case 'ordered': return { label: 'Track Delivery', action: () => ({ path: ROUTES.BENEFICIARY.EQUIPMENT(beneficiary.id), }), }; case 'shipped': return { label: 'Track Package', action: () => ({ path: ROUTES.BENEFICIARY.EQUIPMENT(beneficiary.id), }), }; case 'delivered': return { label: 'Activate Equipment', action: () => ({ path: ROUTES.AUTH.ACTIVATE, params: { beneficiaryId: beneficiary.id }, }), }; default: return null; } }, /** * Get status text for equipment */ getEquipmentStatusText(status: EquipmentStatus | undefined): string { switch (status) { case 'none': return 'No equipment'; case 'ordered': return 'Equipment ordered'; case 'shipped': return 'In transit'; case 'delivered': return 'Ready to activate'; case 'active': return 'Active'; case 'demo': return 'Demo mode'; default: return 'Unknown'; } }, /** * Get status color for equipment */ getEquipmentStatusColor(status: EquipmentStatus | undefined): string { switch (status) { case 'none': return '#888888'; // gray case 'ordered': return '#FFA500'; // orange case 'shipped': return '#007AFF'; // blue case 'delivered': return '#34C759'; // green case 'active': return '#34C759'; // green case 'demo': return '#9C27B0'; // purple default: return '#888888'; // gray } }, }; export default NavigationController;