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) => {