From 7186f29f35d9e5c339f4b218ab392cf8a223ce74 Mon Sep 17 00:00:00 2001 From: Sergei Date: Tue, 30 Dec 2025 21:34:25 -0800 Subject: [PATCH] Update activate, purchase and dashboard screens --- app/(auth)/activate.tsx | 152 +++++++++++----- app/(auth)/purchase.tsx | 2 +- app/(tabs)/beneficiaries/[id]/dashboard.tsx | 35 +++- app/(tabs)/beneficiaries/[id]/index.tsx | 183 +++++++++++++------- app/(tabs)/index.tsx | 8 +- 5 files changed, 262 insertions(+), 118 deletions(-) diff --git a/app/(auth)/activate.tsx b/app/(auth)/activate.tsx index fe72fdd..53ed994 100644 --- a/app/(auth)/activate.tsx +++ b/app/(auth)/activate.tsx @@ -18,8 +18,8 @@ import { api } from '@/services/api'; export default function ActivateScreen() { - // Get lovedOneName from purchase flow - const params = useLocalSearchParams<{ lovedOneName?: string }>(); + // Get params - lovedOneName from purchase flow, beneficiaryId from existing beneficiary + const params = useLocalSearchParams<{ lovedOneName?: string; beneficiaryId?: string }>(); const [activationCode, setActivationCode] = useState(''); const [isActivating, setIsActivating] = useState(false); @@ -27,7 +27,13 @@ export default function ActivateScreen() { // Pre-fill beneficiary name from params if available const [beneficiaryName, setBeneficiaryName] = useState(params.lovedOneName || ''); - const { addLocalBeneficiary } = useBeneficiary(); + const { addLocalBeneficiary, updateLocalBeneficiary, localBeneficiaries } = useBeneficiary(); + + // Check if we're activating for an existing beneficiary + const existingBeneficiaryId = params.beneficiaryId ? parseInt(params.beneficiaryId, 10) : null; + const existingBeneficiary = existingBeneficiaryId + ? localBeneficiaries.find(b => b.id === existingBeneficiaryId) + : null; // Demo serial for testing without real hardware const DEMO_SERIAL = 'DEMO-00000'; @@ -41,7 +47,7 @@ export default function ActivateScreen() { } // Check for demo serial - const isDemoMode = code === DEMO_SERIAL; + const isDemoMode = code === DEMO_SERIAL || code === 'DEMO-1234-5678'; // Validate code format: minimum 8 characters (or demo serial) if (!isDemoMode && code.length < 8) { @@ -52,23 +58,35 @@ export default function ActivateScreen() { setIsActivating(true); try { - // Demo mode shows simulated data - const beneficiaryData: any = { - name: params.lovedOneName?.trim() || '', - }; - - if (isDemoMode) { - beneficiaryData.isDemo = true; - } - - // If name was already provided from add-loved-one screen, skip to saving - if (params.lovedOneName && params.lovedOneName.trim()) { - await addLocalBeneficiary(beneficiaryData); - await api.setOnboardingCompleted(true); + // If we have an existing beneficiary, update them with device info + if (existingBeneficiaryId && existingBeneficiary) { + await updateLocalBeneficiary(existingBeneficiaryId, { + hasDevices: true, + device_id: code, + }); + setBeneficiaryName(existingBeneficiary.name); setStep('complete'); } else { - // No name provided, show beneficiary form - setStep('beneficiary'); + // Creating new beneficiary + const beneficiaryData: any = { + name: params.lovedOneName?.trim() || '', + hasDevices: true, + device_id: code, + }; + + if (isDemoMode) { + beneficiaryData.isDemo = true; + } + + // If name was already provided from add-loved-one screen, skip to saving + if (params.lovedOneName && params.lovedOneName.trim()) { + await addLocalBeneficiary(beneficiaryData); + await api.setOnboardingCompleted(true); + setStep('complete'); + } else { + // No name provided, show beneficiary form + setStep('beneficiary'); + } } } catch (error) { console.error('Failed to activate:', error); @@ -88,11 +106,13 @@ export default function ActivateScreen() { try { const code = activationCode.trim().toUpperCase(); - const isDemoMode = code === DEMO_SERIAL; + const isDemoMode = code === DEMO_SERIAL || code === 'DEMO-1234-5678'; - // Add beneficiary WITHOUT subscription - user needs to subscribe separately + // Add beneficiary with device info await addLocalBeneficiary({ name: beneficiaryName.trim(), + hasDevices: true, + device_id: code, isDemo: isDemoMode, }); // Mark onboarding as completed @@ -107,8 +127,13 @@ export default function ActivateScreen() { }; const handleComplete = () => { - // Navigate to main app - router.replace('/(tabs)'); + // If updating existing beneficiary, go back to their detail page + if (existingBeneficiaryId) { + router.replace(`/(tabs)/beneficiaries/${existingBeneficiaryId}`); + } else { + // Navigate to main app + router.replace('/(tabs)'); + } }; // Step 1: Enter activation code @@ -118,8 +143,16 @@ export default function ActivateScreen() { {/* Header */} - - Activate Kit + {existingBeneficiary ? ( + router.back()}> + + + ) : ( + + )} + + {existingBeneficiary ? 'Connect Sensors' : 'Activate Kit'} + @@ -130,7 +163,9 @@ export default function ActivateScreen() { {/* Instructions */} - Enter the activation code from your WellNuo Starter Kit packaging + {existingBeneficiary + ? `Connect sensors for ${existingBeneficiary.name}` + : 'Enter the activation code from your WellNuo Starter Kit packaging'} {/* Input */} @@ -168,10 +203,12 @@ export default function ActivateScreen() { )} - {/* Skip for now */} - - Skip for now - + {/* Skip for now - only show for new onboarding, not for existing beneficiary */} + {!existingBeneficiary && ( + + Skip for now + + )} ); @@ -233,6 +270,8 @@ export default function ActivateScreen() { } // Step 3: Complete + const displayName = beneficiaryName || existingBeneficiary?.name || params.lovedOneName; + return ( @@ -242,33 +281,54 @@ export default function ActivateScreen() { - Kit Activated! + + {existingBeneficiary ? 'Sensors Connected!' : 'Kit Activated!'} + - Your WellNuo kit has been successfully activated for{' '} - {beneficiaryName || params.lovedOneName} + {existingBeneficiary + ? `Sensors have been connected for ` + : `Your WellNuo kit has been successfully activated for `} + {displayName} {/* Next Steps */} Next Steps: - - 1 - Place sensors in your loved one's home - - - 2 - Connect the hub to WiFi - - - 3 - Subscribe to start monitoring ($49/month) - + {existingBeneficiary ? ( + <> + + 1 + Connect the hub to WiFi + + + 2 + Subscribe to start monitoring ($49/month) + + + ) : ( + <> + + 1 + Place sensors in your loved one's home + + + 2 + Connect the hub to WiFi + + + 3 + Subscribe to start monitoring ($49/month) + + + )} {/* Complete Button */} - Go to Dashboard + + {existingBeneficiary ? 'Continue' : 'Go to Dashboard'} + diff --git a/app/(auth)/purchase.tsx b/app/(auth)/purchase.tsx index fbbe499..66eb4a3 100644 --- a/app/(auth)/purchase.tsx +++ b/app/(auth)/purchase.tsx @@ -111,7 +111,7 @@ export default function PurchaseScreen() { // 4. Payment successful! Create or update beneficiary with 'ordered' status if (beneficiaryId) { // Update existing beneficiary - await updateLocalBeneficiary(beneficiaryId, { + await updateLocalBeneficiary(parseInt(beneficiaryId, 10), { equipmentStatus: 'ordered', }); } else if (lovedOneName) { diff --git a/app/(tabs)/beneficiaries/[id]/dashboard.tsx b/app/(tabs)/beneficiaries/[id]/dashboard.tsx index ec4a529..be7e7fc 100644 --- a/app/(tabs)/beneficiaries/[id]/dashboard.tsx +++ b/app/(tabs)/beneficiaries/[id]/dashboard.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Modal, Tex import { WebView } from 'react-native-webview'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { useLocalSearchParams, router } from 'expo-router'; +import { useLocalSearchParams, router, useFocusEffect } from 'expo-router'; import * as SecureStore from 'expo-secure-store'; import * as ImagePicker from 'expo-image-picker'; import { useBeneficiary } from '@/contexts/BeneficiaryContext'; @@ -102,6 +102,16 @@ export default function BeneficiaryDashboardScreen() { }).start(); }, [isEditModalVisible]); + // Hide menu when navigating away from page + useFocusEffect( + useCallback(() => { + return () => { + setIsMenuVisible(false); + setIsEditModalVisible(false); + }; + }, []) + ); + const handleEditPress = () => { if (beneficiary) { setEditForm({ @@ -296,11 +306,18 @@ export default function BeneficiaryDashboardScreen() { {currentBeneficiary && ( - - - {currentBeneficiary.name.charAt(0).toUpperCase()} - - + currentBeneficiary.avatar ? ( + + ) : ( + + + {currentBeneficiary.name.charAt(0).toUpperCase()} + + + ) )} {beneficiaryName} @@ -545,6 +562,12 @@ const styles = StyleSheet.create({ alignItems: 'center', marginRight: Spacing.sm, }, + avatarSmallImage: { + width: 36, + height: 36, + borderRadius: 18, + marginRight: Spacing.sm, + }, avatarText: { fontSize: FontSizes.base, fontWeight: '600', diff --git a/app/(tabs)/beneficiaries/[id]/index.tsx b/app/(tabs)/beneficiaries/[id]/index.tsx index 7e494b9..7070bc5 100644 --- a/app/(tabs)/beneficiaries/[id]/index.tsx +++ b/app/(tabs)/beneficiaries/[id]/index.tsx @@ -19,8 +19,10 @@ import { useLocalSearchParams, router } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import * as ImagePicker from 'expo-image-picker'; +import { usePaymentSheet } from '@stripe/stripe-react-native'; import { api } from '@/services/api'; import { useBeneficiary } from '@/contexts/BeneficiaryContext'; +import { useAuth } from '@/contexts/AuthContext'; import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; import { FullScreenError } from '@/components/ui/ErrorMessage'; import { Button } from '@/components/ui/Button'; @@ -47,7 +49,19 @@ const isLocalBeneficiary = (id: string | number): boolean => { // Setup state types type SetupState = 'loading' | 'awaiting_equipment' | 'no_devices' | 'no_subscription' | 'ready'; -// No Devices Screen Component +// Starter Kit info +const STARTER_KIT = { + name: 'WellNuo Starter Kit', + price: '$249', + features: [ + 'Motion sensor (PIR)', + 'Door/window sensor', + 'Temperature & humidity sensor', + 'WellNuo Hub', + ], +}; + +// No Devices Screen Component - Primary: Buy Kit, Secondary: I have sensors function NoDevicesScreen({ beneficiary, onActivate, @@ -62,38 +76,36 @@ function NoDevicesScreen({ - Connect Sensors + Get Started with WellNuo - To start monitoring {beneficiary.name}'s wellness, you need to connect sensors first. + To start monitoring {beneficiary.name}'s wellness, you need WellNuo sensors. - - - - - - I have sensors - - Enter activation code to connect your WellNuo sensors - - - - - + {/* Primary: Buy Kit Card */} + + {STARTER_KIT.name} + {STARTER_KIT.price} - - - - - Get sensors - - Order WellNuo sensor kit for comprehensive monitoring - - - - + + {STARTER_KIT.features.map((feature, index) => ( + + + {feature} + + ))} + + + + + Buy Now - {STARTER_KIT.price} + + {/* Secondary: I already have sensors */} + + I already have sensors + + ); } @@ -239,6 +251,7 @@ export default function BeneficiaryDetailScreen() { const isLocal = useMemo(() => id ? isLocalBeneficiary(id) : false, [id]); // Determine setup state + // Flow: No devices → Connect Sensors → Subscription → Dashboard const setupState = useMemo((): SetupState => { if (isLoading) return 'loading'; if (!beneficiary) return 'loading'; @@ -249,14 +262,14 @@ export default function BeneficiaryDetailScreen() { return 'awaiting_equipment'; } - // Check if has devices + // Check if has devices - required first step const hasDevices = beneficiary.hasDevices || (beneficiary.devices && beneficiary.devices.length > 0) || beneficiary.device_id; if (!hasDevices) return 'no_devices'; - // Check subscription + // Check subscription - required after devices connected const subscription = beneficiary.subscription; if (!subscription || subscription.status === 'none' || subscription.status === 'expired') { return 'no_subscription'; @@ -326,6 +339,16 @@ export default function BeneficiaryDetailScreen() { loadBeneficiary(); }, [loadBeneficiary]); + // Sync beneficiary data when localBeneficiaries changes (especially after avatar update) + useEffect(() => { + if (isLocal && id && beneficiary) { + const updated = localBeneficiaries.find(b => b.id === parseInt(id, 10)); + if (updated && updated.avatar !== beneficiary.avatar) { + setBeneficiary(updated); + } + } + }, [localBeneficiaries, id, isLocal]); + const handleRefresh = useCallback(() => { setIsRefreshing(true); loadBeneficiary(false); @@ -333,17 +356,17 @@ export default function BeneficiaryDetailScreen() { const handleActivateSensors = () => { router.push({ - pathname: '/(tabs)/beneficiaries/[id]/activate', - params: { id: id! }, + pathname: '/(auth)/activate', + params: { beneficiaryId: id!, lovedOneName: beneficiary?.name }, }); }; const handleGetSensors = () => { - // For now, show info or redirect to purchase - toast.info( - 'Get WellNuo Sensors', - 'WellNuo sensor kits include motion sensors, door sensors, and temperature monitors. Visit wellnuo.com to order.' - ); + // Navigate to purchase screen with beneficiary info + router.push({ + pathname: '/(auth)/purchase', + params: { beneficiaryId: id!, lovedOneName: beneficiary?.name }, + }); }; const handleMarkReceived = async () => { @@ -696,29 +719,6 @@ export default function BeneficiaryDetailScreen() { {/* Dropdown Menu */} {isMenuVisible && ( - { - setIsMenuVisible(false); - setCurrentBeneficiary(beneficiary); - router.push('/(tabs)/chat'); - }} - > - - Chat - - - { - setIsMenuVisible(false); - router.push(`/(tabs)/beneficiaries/${id}/dashboard`); - }} - > - - Dashboard - - { @@ -1004,6 +1004,71 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, + // Buy Kit Card Styles + buyKitCard: { + width: '100%', + backgroundColor: AppColors.white, + borderRadius: BorderRadius.xl, + padding: Spacing.xl, + borderWidth: 2, + borderColor: AppColors.primary, + alignItems: 'center', + ...Shadows.md, + }, + buyKitName: { + fontSize: FontSizes.xl, + fontWeight: FontWeights.bold, + color: AppColors.textPrimary, + marginBottom: Spacing.sm, + }, + buyKitPrice: { + fontSize: FontSizes['3xl'], + fontWeight: FontWeights.bold, + color: AppColors.primary, + marginBottom: Spacing.lg, + }, + buyKitFeatures: { + width: '100%', + marginBottom: Spacing.lg, + gap: Spacing.sm, + }, + buyKitFeatureRow: { + flexDirection: 'row', + alignItems: 'center', + gap: Spacing.sm, + }, + buyKitFeatureText: { + fontSize: FontSizes.sm, + color: AppColors.textPrimary, + }, + buyKitButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: Spacing.sm, + backgroundColor: AppColors.primary, + paddingVertical: Spacing.md, + paddingHorizontal: Spacing.xl, + borderRadius: BorderRadius.lg, + width: '100%', + }, + buyKitButtonText: { + fontSize: FontSizes.lg, + fontWeight: FontWeights.semibold, + color: AppColors.white, + }, + alreadyHaveLink: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: Spacing.xs, + marginTop: Spacing.xl, + paddingVertical: Spacing.md, + }, + alreadyHaveLinkText: { + fontSize: FontSizes.base, + color: AppColors.textSecondary, + }, // Equipment Progress Styles progressContainer: { flexDirection: 'row', diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 2310f3b..2f9881c 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -199,12 +199,8 @@ export default function HomeScreen() { const handleBeneficiaryPress = (beneficiary: Beneficiary) => { setCurrentBeneficiary(beneficiary); - // If equipment is not active yet, show status page instead of dashboard - if (beneficiary.equipmentStatus && ['ordered', 'shipped'].includes(beneficiary.equipmentStatus)) { - router.push(`/(tabs)/beneficiaries/${beneficiary.id}`); - } else { - router.push(`/(tabs)/beneficiaries/${beneficiary.id}/dashboard`); - } + // Always go to beneficiary detail page (which includes MockDashboard) + router.push(`/(tabs)/beneficiaries/${beneficiary.id}`); }; const handleActivate = (beneficiary: Beneficiary) => {