diff --git a/.gitignore b/.gitignore
index 2a6edee..05890c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,4 @@ app-example
/ios
/android
.git-credentials
+wellnuoSheme/.history/
diff --git a/app.json b/app.json
index 832bbed..2ebd056 100644
--- a/app.json
+++ b/app.json
@@ -10,7 +10,7 @@
"newArchEnabled": true,
"ios": {
"supportsTablet": true,
- "bundleIdentifier": "com.wellnuo.app",
+ "bundleIdentifier": "com.kosyakorel1.wellnuo",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
}
diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx
index ed77ae5..e9e0e11 100644
--- a/app/(auth)/login.tsx
+++ b/app/(auth)/login.tsx
@@ -58,9 +58,11 @@ export default function LoginScreen() {
>
{/* Logo / Header */}
-
- WellNuo
-
+
Welcome Back
Sign in to continue monitoring your loved ones
@@ -149,20 +151,11 @@ const styles = StyleSheet.create({
alignItems: 'center',
marginBottom: Spacing.xl,
},
- logoContainer: {
- width: 80,
- height: 80,
- borderRadius: BorderRadius.xl,
- backgroundColor: AppColors.primary,
- justifyContent: 'center',
- alignItems: 'center',
+ logo: {
+ width: 180,
+ height: 100,
marginBottom: Spacing.lg,
},
- logoText: {
- fontSize: FontSizes.lg,
- fontWeight: '700',
- color: AppColors.white,
- },
title: {
fontSize: FontSizes['2xl'],
fontWeight: '700',
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 84b0b88..1a14824 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -35,7 +35,7 @@ export default function TabLayout() {
options={{
title: 'Dashboard',
tabBarIcon: ({ color, size }) => (
-
+
),
}}
/>
diff --git a/app/(tabs)/beneficiaries/[id]/dashboard.tsx b/app/(tabs)/beneficiaries/[id]/dashboard.tsx
index 22facf7..5e1c283 100644
--- a/app/(tabs)/beneficiaries/[id]/dashboard.tsx
+++ b/app/(tabs)/beneficiaries/[id]/dashboard.tsx
@@ -9,9 +9,9 @@ import { useBeneficiary } from '@/contexts/BeneficiaryContext';
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
import { FullScreenError } from '@/components/ui/ErrorMessage';
-// Start with login page, then redirect to dashboard after auth
-const LOGIN_URL = 'https://react.eluxnetworks.net/login';
-const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard';
+// Dashboard URL with patient ID
+const getDashboardUrl = (deploymentId: string) =>
+ `https://react.eluxnetworks.net/dashboard/${deploymentId}`;
export default function BeneficiaryDashboardScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
@@ -24,7 +24,9 @@ export default function BeneficiaryDashboardScreen() {
const [userName, setUserName] = useState(null);
const [userId, setUserId] = useState(null);
const [isTokenLoaded, setIsTokenLoaded] = useState(false);
- const [webViewUrl, setWebViewUrl] = useState(DASHBOARD_URL);
+
+ // Build dashboard URL with patient ID
+ const dashboardUrl = id ? getDashboardUrl(id) : 'https://react.eluxnetworks.net/dashboard';
const beneficiaryName = currentBeneficiary?.name || 'Dashboard';
@@ -169,7 +171,7 @@ export default function BeneficiaryDashboardScreen() {
setIsLoading(true)}
onLoadEnd={() => setIsLoading(false)}
diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx
index 98033dc..411be98 100644
--- a/app/(tabs)/chat.tsx
+++ b/app/(tabs)/chat.tsx
@@ -52,7 +52,9 @@ export default function ChatScreen() {
? `${beneficiaryContext} ${trimmedInput}`
: trimmedInput;
- const response = await api.sendMessage(questionWithContext);
+ // Pass deployment_id from selected beneficiary (fallback to '21' if not selected)
+ const deploymentId = currentBeneficiary?.id?.toString() || '21';
+ const response = await api.sendMessage(questionWithContext, deploymentId);
if (response.ok && response.data?.response) {
const assistantMessage: Message = {
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index e1fe67d..f3a38e7 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -1,124 +1,145 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
-import { WebView } from 'react-native-webview';
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ FlatList,
+ TouchableOpacity,
+ ActivityIndicator,
+ RefreshControl
+} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';
-import * as SecureStore from 'expo-secure-store';
+import { router } from 'expo-router';
import { useAuth } from '@/contexts/AuthContext';
-import { AppColors, FontSizes, Spacing } from '@/constants/theme';
+import { useBeneficiary } from '@/contexts/BeneficiaryContext';
+import { api } from '@/services/api';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+import type { Beneficiary } from '@/types';
-const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard';
+// Patient card component
+interface PatientCardProps {
+ patient: Beneficiary;
+ onPress: () => void;
+}
+
+function PatientCard({ patient, onPress }: PatientCardProps) {
+ const isOnline = patient.status === 'online';
+ const wellnessColor = patient.wellness_score && patient.wellness_score >= 70
+ ? AppColors.success
+ : patient.wellness_score && patient.wellness_score >= 40
+ ? '#F59E0B'
+ : AppColors.error;
+
+ return (
+
+
+ {/* Avatar */}
+
+
+ {patient.name.charAt(0).toUpperCase()}
+
+ {isOnline && }
+
+
+ {/* Info */}
+
+ {patient.name}
+ {patient.last_location && (
+
+
+ {patient.last_location}
+
+ )}
+
+
+
+ {isOnline ? 'Active' : 'Inactive'}
+
+
+ {patient.last_activity && (
+ {patient.last_activity}
+ )}
+
+
+
+ {/* Wellness Score */}
+ {patient.wellness_score !== undefined && (
+
+
+ {patient.wellness_score}%
+
+ Wellness
+
+ )}
+
+ {/* Arrow */}
+
+
+
+ );
+}
export default function HomeScreen() {
const { user } = useAuth();
- const webViewRef = useRef(null);
+ const { currentBeneficiary, setCurrentBeneficiary } = useBeneficiary();
const [isLoading, setIsLoading] = useState(true);
+ const [isRefreshing, setIsRefreshing] = useState(false);
+ const [patients, setPatients] = useState([]);
const [error, setError] = useState(null);
- const [canGoBack, setCanGoBack] = useState(false);
- const [authToken, setAuthToken] = useState(null);
- const [userName, setUserName] = useState(null);
- const [userId, setUserId] = useState(null);
- const [isTokenLoaded, setIsTokenLoaded] = useState(false);
- // Load credentials from SecureStore
+ // Load patients from API
useEffect(() => {
- const loadCredentials = async () => {
- try {
- const token = await SecureStore.getItemAsync('accessToken');
- const user = await SecureStore.getItemAsync('userName');
- const uid = await SecureStore.getItemAsync('userId');
- setAuthToken(token);
- setUserName(user);
- setUserId(uid);
- console.log('Home: Loaded credentials for WebView:', { hasToken: !!token, user, uid });
- } catch (err) {
- console.error('Failed to load credentials:', err);
- } finally {
- setIsTokenLoaded(true);
- }
- };
- loadCredentials();
+ loadPatients();
}, []);
- // JavaScript to inject auth token into localStorage
- // Web app expects auth2 as JSON: {username, token, user_id}
- const injectedJavaScript = authToken
- ? `
- (function() {
- try {
- var authData = {
- username: '${userName || ''}',
- token: '${authToken}',
- user_id: ${userId || 'null'}
- };
- localStorage.setItem('auth2', JSON.stringify(authData));
- console.log('Auth injected:', authData.username);
- } catch(e) {
- console.error('Failed to inject token:', e);
- }
- })();
- true;
- `
- : '';
-
- const handleRefresh = () => {
- setError(null);
+ const loadPatients = async () => {
setIsLoading(true);
- webViewRef.current?.reload();
- };
-
- const handleWebViewBack = () => {
- if (canGoBack) {
- webViewRef.current?.goBack();
+ setError(null);
+ try {
+ const response = await api.getAllPatients();
+ if (response.ok && response.data) {
+ setPatients(response.data);
+ // Auto-select first beneficiary if none selected
+ if (!currentBeneficiary && response.data.length > 0) {
+ setCurrentBeneficiary(response.data[0]);
+ }
+ } else {
+ setError(response.error?.message || 'Failed to load patients');
+ }
+ } catch (err) {
+ console.error('Failed to load patients:', err);
+ setError('Failed to load patients');
+ } finally {
+ setIsLoading(false);
}
};
- const handleNavigationStateChange = (navState: any) => {
- setCanGoBack(navState.canGoBack);
+ const handleRefresh = async () => {
+ setIsRefreshing(true);
+ await loadPatients();
+ setIsRefreshing(false);
};
- const handleError = () => {
- setError('Failed to load dashboard. Please check your internet connection.');
- setIsLoading(false);
+ const handlePatientPress = (patient: Beneficiary) => {
+ // Set current beneficiary in context
+ setCurrentBeneficiary(patient);
+ // Navigate to patient dashboard with deployment_id
+ router.push(`/(tabs)/beneficiaries/${patient.id}/dashboard`);
};
- // Wait for token to load
- if (!isTokenLoaded) {
+ if (isLoading) {
return (
Hello, {user?.user_name || 'User'}
- Dashboard
+ My Beneficiaries
- Preparing dashboard...
-
-
- );
- }
-
- if (error) {
- return (
-
-
-
- Hello, {user?.user_name || 'User'}
- Dashboard
-
-
-
-
-
-
-
- Connection Error
- {error}
-
- Try Again
-
+ Loading patients...
);
@@ -130,52 +151,42 @@ export default function HomeScreen() {
Hello, {user?.user_name || 'User'}
- Dashboard
-
-
- {canGoBack && (
-
-
-
- )}
-
-
-
+ My Beneficiaries
+
+
+
- {/* WebView Dashboard */}
-
- setIsLoading(true)}
- onLoadEnd={() => setIsLoading(false)}
- onError={handleError}
- onHttpError={handleError}
- onNavigationStateChange={handleNavigationStateChange}
- javaScriptEnabled={true}
- domStorageEnabled={true}
- startInLoadingState={true}
- scalesPageToFit={true}
- allowsBackForwardNavigationGestures={true}
- injectedJavaScriptBeforeContentLoaded={injectedJavaScript}
- injectedJavaScript={injectedJavaScript}
- renderLoading={() => (
-
-
- Loading dashboard...
-
+ {/* Patient List */}
+ {patients.length === 0 ? (
+
+
+ No Patients
+ You don't have any patients assigned yet.
+
+ ) : (
+ item.id.toString()}
+ renderItem={({ item }) => (
+ handlePatientPress(item)}
+ />
)}
+ contentContainerStyle={styles.listContent}
+ showsVerticalScrollIndicator={false}
+ refreshControl={
+
+ }
/>
-
- {isLoading && (
-
-
-
- )}
-
+ )}
);
}
@@ -204,76 +215,151 @@ const styles = StyleSheet.create({
fontWeight: '700',
color: AppColors.textPrimary,
},
- headerActions: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- actionButton: {
- padding: Spacing.xs,
- marginLeft: Spacing.xs,
- },
refreshButton: {
padding: Spacing.xs,
},
- webViewContainer: {
- flex: 1,
- },
- webView: {
- flex: 1,
- },
loadingContainer: {
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
+ flex: 1,
justifyContent: 'center',
alignItems: 'center',
- backgroundColor: AppColors.background,
- },
- loadingOverlay: {
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: 'rgba(255,255,255,0.8)',
},
loadingText: {
marginTop: Spacing.md,
fontSize: FontSizes.base,
color: AppColors.textSecondary,
},
- errorContainer: {
+ emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: Spacing.xl,
},
- errorTitle: {
+ emptyTitle: {
fontSize: FontSizes.lg,
fontWeight: '600',
color: AppColors.textPrimary,
marginTop: Spacing.md,
},
- errorText: {
+ emptyText: {
fontSize: FontSizes.base,
color: AppColors.textSecondary,
textAlign: 'center',
marginTop: Spacing.xs,
},
- retryButton: {
- marginTop: Spacing.lg,
- paddingHorizontal: Spacing.xl,
- paddingVertical: Spacing.md,
- backgroundColor: AppColors.primary,
- borderRadius: 8,
+ listContent: {
+ padding: Spacing.lg,
+ paddingBottom: Spacing.xxl,
},
- retryButtonText: {
- color: AppColors.white,
- fontSize: FontSizes.base,
+ // Card styles
+ card: {
+ backgroundColor: AppColors.white,
+ borderRadius: BorderRadius.lg,
+ marginBottom: Spacing.md,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.05,
+ shadowRadius: 4,
+ elevation: 2,
+ },
+ cardContent: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: Spacing.md,
+ },
+ avatar: {
+ width: 56,
+ height: 56,
+ borderRadius: BorderRadius.full,
+ backgroundColor: AppColors.primaryLight,
+ justifyContent: 'center',
+ alignItems: 'center',
+ position: 'relative',
+ },
+ avatarOnline: {
+ borderWidth: 2,
+ borderColor: AppColors.success,
+ },
+ avatarText: {
+ fontSize: FontSizes.xl,
fontWeight: '600',
+ color: AppColors.white,
+ },
+ onlineIndicator: {
+ position: 'absolute',
+ bottom: 2,
+ right: 2,
+ width: 14,
+ height: 14,
+ borderRadius: 7,
+ backgroundColor: AppColors.success,
+ borderWidth: 2,
+ borderColor: AppColors.white,
+ },
+ info: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ name: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ },
+ relationship: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginTop: 2,
+ },
+ statusRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginTop: Spacing.xs,
+ },
+ statusBadge: {
+ paddingHorizontal: Spacing.sm,
+ paddingVertical: 2,
+ borderRadius: BorderRadius.sm,
+ },
+ statusOnline: {
+ backgroundColor: 'rgba(34, 197, 94, 0.1)',
+ },
+ statusOffline: {
+ backgroundColor: 'rgba(107, 114, 128, 0.1)',
+ },
+ statusText: {
+ fontSize: FontSizes.xs,
+ fontWeight: '500',
+ },
+ statusTextOnline: {
+ color: AppColors.success,
+ },
+ statusTextOffline: {
+ color: AppColors.textMuted,
+ },
+ lastActivity: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginLeft: Spacing.sm,
+ },
+ locationRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginTop: 2,
+ },
+ locationText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textSecondary,
+ marginLeft: 4,
+ },
+ wellnessContainer: {
+ alignItems: 'center',
+ marginRight: Spacing.sm,
+ },
+ wellnessScore: {
+ fontSize: FontSizes.lg,
+ fontWeight: '700',
+ },
+ wellnessLabel: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
},
});
diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx
index 095a28a..2f9b61b 100644
--- a/app/(tabs)/profile.tsx
+++ b/app/(tabs)/profile.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import {
View,
Text,
@@ -6,6 +6,7 @@ import {
ScrollView,
TouchableOpacity,
Alert,
+ Switch,
} from 'react-native';
import { router } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
@@ -21,6 +22,7 @@ interface MenuItemProps {
subtitle?: string;
onPress?: () => void;
showChevron?: boolean;
+ rightElement?: React.ReactNode;
}
function MenuItem({
@@ -31,9 +33,10 @@ function MenuItem({
subtitle,
onPress,
showChevron = true,
+ rightElement,
}: MenuItemProps) {
return (
-
+
@@ -41,7 +44,7 @@ function MenuItem({
{title}
{subtitle && {subtitle}}
- {showChevron && (
+ {rightElement ? rightElement : showChevron && onPress && (
)}
@@ -51,6 +54,12 @@ function MenuItem({
export default function ProfileScreen() {
const { user, logout } = useAuth();
+ // Settings states
+ const [pushNotifications, setPushNotifications] = useState(true);
+ const [emailNotifications, setEmailNotifications] = useState(false);
+ const [darkMode, setDarkMode] = useState(false);
+ const [biometricLogin, setBiometricLogin] = useState(false);
+
const handleLogout = () => {
Alert.alert(
'Logout',
@@ -70,6 +79,34 @@ export default function ProfileScreen() {
);
};
+ // Navigation handlers - now using actual page navigation
+ const handleEditProfile = () => router.push('/profile/edit');
+ const handleNotifications = () => router.push('/profile/notifications');
+ const handlePrivacy = () => router.push('/profile/privacy');
+ const handleUpgrade = () => router.push('/profile/subscription');
+ const handlePayment = () => router.push('/profile/subscription');
+ const handleHelp = () => router.push('/profile/help');
+ const handleSupport = () => router.push('/profile/support');
+ const handleTerms = () => router.push('/profile/terms');
+ const handlePrivacyPolicy = () => router.push('/profile/privacy-policy');
+ const handleLanguage = () => router.push('/profile/language');
+ const handleAbout = () => router.push('/profile/about');
+
+ const handleDevInfo = () => {
+ Alert.alert(
+ 'Developer Info',
+ `User ID: ${user?.user_id || 'N/A'}\n` +
+ `Username: ${user?.user_name || 'N/A'}\n` +
+ `Role: ${user?.max_role || 'N/A'}\n` +
+ `Privileges: ${user?.privileges || 'N/A'}\n\n` +
+ 'Tap "Copy" to copy debug info.',
+ [
+ { text: 'Close' },
+ { text: 'Copy', onPress: () => Alert.alert('Copied', 'Debug info copied to clipboard') },
+ ]
+ );
+ };
+
return (
@@ -88,15 +125,34 @@ export default function ProfileScreen() {
{user?.user_name || 'User'}
- Role: {user?.max_role === 2 ? 'Admin' : 'User'}
+ {user?.max_role === 2 ? 'Administrator' : 'Caregiver'}
+ ID: {user?.user_id || 'N/A'}
-
+
- {/* Menu Sections */}
+ {/* Quick Stats */}
+
+
+ {user?.privileges?.split(',').length || 0}
+ Beneficiaries
+
+
+
+ 24/7
+ Monitoring
+
+
+
+ Free
+ Plan
+
+
+
+ {/* Account Section */}
Account
@@ -104,6 +160,7 @@ export default function ProfileScreen() {
icon="person-outline"
title="Edit Profile"
subtitle="Update your personal information"
+ onPress={handleEditProfile}
/>
+ {/* App Settings */}
+
+ App Settings
+
+
+ }
+ />
+
+
+ }
+ />
+
+
+
+
+ {/* Subscription */}
Subscription
@@ -133,16 +285,19 @@ export default function ProfileScreen() {
iconColor="#9333EA"
title="WellNuo Pro"
subtitle="Upgrade for premium features"
+ onPress={handleUpgrade}
/>
+ {/* Support */}
Support
@@ -150,22 +305,50 @@ export default function ProfileScreen() {
icon="help-circle-outline"
title="Help Center"
subtitle="FAQs and guides"
+ onPress={handleHelp}
/>
+
+
+
+ {/* About */}
+
+ About
+
+
+
+
@@ -179,7 +362,7 @@ export default function ProfileScreen() {
{/* Version */}
- WellNuo v1.0.0
+ WellNuo v1.0.0 (Expo SDK 54)
);
@@ -207,7 +390,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
backgroundColor: AppColors.background,
padding: Spacing.lg,
- marginBottom: Spacing.md,
},
avatarContainer: {
width: 64,
@@ -234,7 +416,12 @@ const styles = StyleSheet.create({
userRole: {
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
- marginTop: Spacing.xs,
+ marginTop: 2,
+ },
+ userId: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
},
editButton: {
width: 40,
@@ -244,6 +431,34 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
+ statsContainer: {
+ flexDirection: 'row',
+ backgroundColor: AppColors.background,
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ borderTopWidth: 1,
+ borderTopColor: AppColors.border,
+ marginBottom: Spacing.md,
+ },
+ statItem: {
+ flex: 1,
+ alignItems: 'center',
+ },
+ statValue: {
+ fontSize: FontSizes.xl,
+ fontWeight: '700',
+ color: AppColors.primary,
+ },
+ statLabel: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ statDivider: {
+ width: 1,
+ backgroundColor: AppColors.border,
+ marginVertical: Spacing.xs,
+ },
section: {
marginBottom: Spacing.md,
},
diff --git a/app/profile/_layout.tsx b/app/profile/_layout.tsx
new file mode 100644
index 0000000..1a82841
--- /dev/null
+++ b/app/profile/_layout.tsx
@@ -0,0 +1,81 @@
+import { Stack } from 'expo-router';
+import { AppColors } from '@/constants/theme';
+
+export default function ProfileLayout() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/profile/about.tsx b/app/profile/about.tsx
new file mode 100644
index 0000000..d68f6f2
--- /dev/null
+++ b/app/profile/about.tsx
@@ -0,0 +1,358 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ Linking,
+ Image,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface InfoRowProps {
+ label: string;
+ value: string;
+}
+
+function InfoRow({ label, value }: InfoRowProps) {
+ return (
+
+ {label}
+ {value}
+
+ );
+}
+
+interface LinkRowProps {
+ icon: keyof typeof Ionicons.glyphMap;
+ title: string;
+ onPress: () => void;
+}
+
+function LinkRow({ icon, title, onPress }: LinkRowProps) {
+ return (
+
+
+ {title}
+
+
+ );
+}
+
+export default function AboutScreen() {
+ const openURL = (url: string) => {
+ Linking.openURL(url).catch(() => {});
+ };
+
+ return (
+
+
+ {/* App Logo & Name */}
+
+
+
+
+ WellNuo
+ Caring for Those Who Matter Most
+
+
+ {/* Version Info */}
+
+ App Information
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Description */}
+
+ About
+
+
+ WellNuo is a comprehensive elderly care monitoring application designed to help
+ families and caregivers stay connected with their loved ones. Using advanced
+ sensor technology and AI-powered analytics, WellNuo provides real-time insights
+ into daily activities, health patterns, and emergency situations.
+
+
+ Our mission is to bring peace of mind to families while preserving the independence
+ and dignity of elderly individuals.
+
+
+
+
+ {/* Features */}
+
+ Key Features
+
+
+
+
+
+
+ Real-time Monitoring
+ 24/7 activity and wellness tracking
+
+
+
+
+
+
+
+ Emergency Alerts
+ Instant notifications for falls and emergencies
+
+
+
+
+
+
+
+ AI-Powered Insights
+ Smart analysis of health patterns
+
+
+
+
+
+
+
+ Family Coordination
+ Share care with multiple caregivers
+
+
+
+
+
+ {/* Links */}
+
+ Resources
+
+ openURL('https://wellnuo.com')}
+ />
+
+ openURL('https://docs.wellnuo.com')}
+ />
+
+ openURL('https://twitter.com/wellnuo')}
+ />
+
+ openURL('https://github.com/wellnuo')}
+ />
+
+
+
+ {/* Acknowledgments */}
+
+ Acknowledgments
+
+
+ WellNuo uses the following open-source software:
+
+ • React Native (MIT License)
+ • Expo (MIT License)
+ • React Navigation (MIT License)
+ • And many other wonderful packages
+ openURL('https://wellnuo.com/licenses')}
+ >
+ View All Licenses
+
+
+
+
+ {/* Footer */}
+
+ © 2024 WellNuo Inc.
+ All rights reserved.
+
+ Made with ❤️ for families worldwide
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ heroSection: {
+ alignItems: 'center',
+ paddingVertical: Spacing.xl,
+ backgroundColor: AppColors.background,
+ },
+ logoContainer: {
+ width: 100,
+ height: 100,
+ borderRadius: 24,
+ backgroundColor: AppColors.primary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginBottom: Spacing.md,
+ },
+ appName: {
+ fontSize: 32,
+ fontWeight: '700',
+ color: AppColors.textPrimary,
+ },
+ appTagline: {
+ fontSize: FontSizes.base,
+ color: AppColors.textSecondary,
+ marginTop: Spacing.xs,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ card: {
+ backgroundColor: AppColors.background,
+ paddingVertical: Spacing.sm,
+ },
+ infoRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: Spacing.sm,
+ paddingHorizontal: Spacing.lg,
+ },
+ infoLabel: {
+ fontSize: FontSizes.base,
+ color: AppColors.textSecondary,
+ },
+ infoValue: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ infoDivider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginHorizontal: Spacing.lg,
+ },
+ description: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ lineHeight: 22,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ },
+ featureItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.sm,
+ paddingHorizontal: Spacing.lg,
+ },
+ featureIcon: {
+ width: 40,
+ height: 40,
+ borderRadius: BorderRadius.md,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ featureContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ featureTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ featureDescription: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ linkRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ linkText: {
+ flex: 1,
+ fontSize: FontSizes.base,
+ color: AppColors.primary,
+ marginLeft: Spacing.md,
+ },
+ linkDivider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginLeft: Spacing.lg + 20 + Spacing.md,
+ },
+ acknowledgment: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ },
+ license: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: 2,
+ },
+ viewLicenses: {
+ marginTop: Spacing.md,
+ paddingVertical: Spacing.sm,
+ alignItems: 'center',
+ },
+ viewLicensesText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.primary,
+ fontWeight: '500',
+ },
+ footer: {
+ alignItems: 'center',
+ paddingVertical: Spacing.xl,
+ paddingBottom: Spacing.xxl,
+ },
+ copyright: {
+ fontSize: FontSizes.sm,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ footerText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ madeWith: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textSecondary,
+ marginTop: Spacing.md,
+ },
+});
diff --git a/app/profile/edit.tsx b/app/profile/edit.tsx
new file mode 100644
index 0000000..c506506
--- /dev/null
+++ b/app/profile/edit.tsx
@@ -0,0 +1,325 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TextInput,
+ TouchableOpacity,
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { useAuth } from '@/contexts/AuthContext';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+export default function EditProfileScreen() {
+ const { user } = useAuth();
+
+ const [displayName, setDisplayName] = useState(user?.user_name || '');
+ const [email, setEmail] = useState('');
+ const [phone, setPhone] = useState('');
+ const [timezone, setTimezone] = useState('UTC');
+ const [isSaving, setIsSaving] = useState(false);
+
+ const handleSave = async () => {
+ if (!displayName.trim()) {
+ Alert.alert('Error', 'Display name is required');
+ return;
+ }
+
+ setIsSaving(true);
+
+ // Simulate API call
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ setIsSaving(false);
+ Alert.alert(
+ 'Profile Updated',
+ 'Your profile has been updated successfully.',
+ [{ text: 'OK', onPress: () => router.back() }]
+ );
+ };
+
+ const handleChangePhoto = () => {
+ Alert.alert(
+ 'Change Photo',
+ 'Choose an option',
+ [
+ { text: 'Take Photo', onPress: () => Alert.alert('Camera', 'Camera functionality coming soon!') },
+ { text: 'Choose from Library', onPress: () => Alert.alert('Library', 'Photo library coming soon!') },
+ { text: 'Remove Photo', style: 'destructive', onPress: () => Alert.alert('Removed', 'Photo removed') },
+ { text: 'Cancel', style: 'cancel' },
+ ]
+ );
+ };
+
+ return (
+
+
+
+ {/* Avatar Section */}
+
+
+
+ {displayName?.charAt(0).toUpperCase() || 'U'}
+
+
+
+
+
+ Tap to change photo
+
+
+ {/* Form */}
+
+
+ Display Name *
+
+
+
+
+ Email Address
+
+ Used for notifications and account recovery
+
+
+
+ Phone Number
+
+ For emergency contact and SMS alerts
+
+
+
+ Timezone
+ {
+ Alert.alert(
+ 'Select Timezone',
+ 'Choose your timezone',
+ [
+ { text: 'UTC', onPress: () => setTimezone('UTC') },
+ { text: 'America/New_York', onPress: () => setTimezone('America/New_York') },
+ { text: 'America/Los_Angeles', onPress: () => setTimezone('America/Los_Angeles') },
+ { text: 'Europe/London', onPress: () => setTimezone('Europe/London') },
+ { text: 'Cancel', style: 'cancel' },
+ ]
+ );
+ }}
+ >
+ {timezone}
+
+
+
+
+ {/* Account Info (read-only) */}
+
+ Account Information
+
+
+ User ID
+ {user?.user_id || 'N/A'}
+
+
+
+ Username
+ {user?.user_name || 'N/A'}
+
+
+
+ Role
+
+ {user?.max_role === 2 ? 'Administrator' : 'Caregiver'}
+
+
+
+
+ Assigned Beneficiaries
+
+ {user?.privileges?.split(',').length || 0}
+
+
+
+
+
+
+ {/* Save Button */}
+
+
+
+ {isSaving ? 'Saving...' : 'Save Changes'}
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ keyboardView: {
+ flex: 1,
+ },
+ avatarSection: {
+ alignItems: 'center',
+ paddingVertical: Spacing.xl,
+ backgroundColor: AppColors.background,
+ },
+ avatarContainer: {
+ width: 100,
+ height: 100,
+ borderRadius: 50,
+ backgroundColor: AppColors.primary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ avatarText: {
+ fontSize: 40,
+ fontWeight: '600',
+ color: AppColors.white,
+ },
+ changePhotoButton: {
+ position: 'absolute',
+ bottom: 50,
+ right: '35%',
+ width: 32,
+ height: 32,
+ borderRadius: 16,
+ backgroundColor: AppColors.primaryLight,
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderWidth: 2,
+ borderColor: AppColors.background,
+ },
+ changePhotoText: {
+ marginTop: Spacing.sm,
+ fontSize: FontSizes.sm,
+ color: AppColors.primary,
+ },
+ form: {
+ padding: Spacing.lg,
+ },
+ inputGroup: {
+ marginBottom: Spacing.lg,
+ },
+ label: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginBottom: Spacing.xs,
+ },
+ input: {
+ backgroundColor: AppColors.background,
+ borderRadius: BorderRadius.md,
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.md,
+ fontSize: FontSizes.base,
+ color: AppColors.textPrimary,
+ borderWidth: 1,
+ borderColor: AppColors.border,
+ },
+ selectInput: {
+ backgroundColor: AppColors.background,
+ borderRadius: BorderRadius.md,
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.md,
+ borderWidth: 1,
+ borderColor: AppColors.border,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ selectText: {
+ fontSize: FontSizes.base,
+ color: AppColors.textPrimary,
+ },
+ hint: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: Spacing.xs,
+ },
+ readOnlySection: {
+ marginTop: Spacing.lg,
+ backgroundColor: AppColors.background,
+ borderRadius: BorderRadius.lg,
+ padding: Spacing.md,
+ },
+ readOnlyTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ marginBottom: Spacing.md,
+ },
+ readOnlyItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: Spacing.sm,
+ borderBottomWidth: 1,
+ borderBottomColor: AppColors.border,
+ },
+ readOnlyLabel: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ },
+ readOnlyValue: {
+ fontSize: FontSizes.sm,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ footer: {
+ padding: Spacing.lg,
+ backgroundColor: AppColors.background,
+ borderTopWidth: 1,
+ borderTopColor: AppColors.border,
+ },
+ saveButton: {
+ backgroundColor: AppColors.primary,
+ borderRadius: BorderRadius.lg,
+ paddingVertical: Spacing.md,
+ alignItems: 'center',
+ },
+ saveButtonDisabled: {
+ opacity: 0.6,
+ },
+ saveButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.white,
+ },
+});
diff --git a/app/profile/help.tsx b/app/profile/help.tsx
new file mode 100644
index 0000000..6875713
--- /dev/null
+++ b/app/profile/help.tsx
@@ -0,0 +1,419 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ TextInput,
+ Linking,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface FAQItemProps {
+ question: string;
+ answer: string;
+ isExpanded: boolean;
+ onToggle: () => void;
+}
+
+function FAQItem({ question, answer, isExpanded, onToggle }: FAQItemProps) {
+ return (
+
+
+ {question}
+
+
+ {isExpanded && (
+ {answer}
+ )}
+
+ );
+}
+
+interface GuideCategoryProps {
+ icon: keyof typeof Ionicons.glyphMap;
+ iconColor: string;
+ iconBgColor: string;
+ title: string;
+ description: string;
+ onPress: () => void;
+}
+
+function GuideCategory({
+ icon,
+ iconColor,
+ iconBgColor,
+ title,
+ description,
+ onPress,
+}: GuideCategoryProps) {
+ return (
+
+
+
+
+ {title}
+ {description}
+
+ );
+}
+
+export default function HelpScreen() {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [expandedFAQ, setExpandedFAQ] = useState(null);
+
+ const faqs = [
+ {
+ question: 'How do I add a new beneficiary?',
+ answer: 'Beneficiaries are assigned by your organization administrator. Contact your admin or support team to request access to additional beneficiaries. Once assigned, they will automatically appear in your dashboard.',
+ },
+ {
+ question: 'What does the wellness score mean?',
+ answer: 'The wellness score (0-100%) reflects the overall health pattern of your beneficiary based on daily activities, sleep patterns, movement data, and vital signs. A score above 70% indicates healthy patterns, 40-70% suggests some concerns, and below 40% may require attention.',
+ },
+ {
+ question: 'How often is the data updated?',
+ answer: 'Sensor data is collected in real-time and synced every few minutes. Dashboard summaries are calculated daily. Emergency alerts are instant and will notify you immediately.',
+ },
+ {
+ question: 'What triggers an emergency alert?',
+ answer: 'Emergency alerts are triggered by: falls detected by motion sensors, prolonged inactivity exceeding normal patterns, SOS button press by the beneficiary, and abnormal vital sign readings (if health monitors are connected).',
+ },
+ {
+ question: 'Can I share access with family members?',
+ answer: 'Yes! Contact your administrator to add additional caregivers. Each caregiver will have their own account and can set their own notification preferences while sharing access to the same beneficiary data.',
+ },
+ {
+ question: 'How do I change notification sounds?',
+ answer: 'Go to Profile > Notifications to customize your alert preferences. You can set different sounds for emergency alerts vs regular notifications, and configure quiet hours for non-urgent alerts.',
+ },
+ {
+ question: 'Is my data secure?',
+ answer: 'Yes. WellNuo uses end-to-end encryption for all data transmission. Your data is stored in HIPAA-compliant servers, and we never share personal information with third parties. You can export or delete your data at any time.',
+ },
+ {
+ question: 'What devices are compatible?',
+ answer: 'WellNuo works with most motion sensors, door/window sensors, and smart home devices. Supported health monitors include select blood pressure cuffs, pulse oximeters, and weight scales. Check our device compatibility list for specifics.',
+ },
+ ];
+
+ const filteredFAQs = faqs.filter(
+ faq =>
+ faq.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ faq.answer.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ return (
+
+
+ {/* Search */}
+
+
+
+
+ {searchQuery.length > 0 && (
+ setSearchQuery('')}>
+
+
+ )}
+
+
+
+ {/* Quick Guides */}
+
+ Quick Guides
+
+ Linking.openURL('https://wellnuo.com/guides/getting-started')}
+ />
+ Linking.openURL('https://wellnuo.com/guides/managing-care')}
+ />
+ Linking.openURL('https://wellnuo.com/guides/alerts')}
+ />
+ Linking.openURL('https://wellnuo.com/guides/devices')}
+ />
+
+
+
+ {/* FAQs */}
+
+ Frequently Asked Questions
+
+ {filteredFAQs.length > 0 ? (
+ filteredFAQs.map((faq, index) => (
+
+ setExpandedFAQ(expandedFAQ === index ? null : index)}
+ />
+ {index < filteredFAQs.length - 1 && }
+
+ ))
+ ) : (
+
+
+ No results found
+ Try different keywords
+
+ )}
+
+
+
+ {/* Video Tutorials */}
+
+ Video Tutorials
+
+ {[
+ { title: 'App Overview', duration: '3:45' },
+ { title: 'Setting Up Alerts', duration: '2:30' },
+ { title: 'Understanding Data', duration: '4:15' },
+ { title: 'Troubleshooting', duration: '5:00' },
+ ].map((video, index) => (
+
+
+
+
+ {video.title}
+ {video.duration}
+
+ ))}
+
+
+
+ {/* Still Need Help */}
+
+
+
+
+
+ Still need help?
+
+ Our support team is available 24/7 to assist you with any questions.
+
+ Linking.openURL('mailto:support@wellnuo.com')}
+ >
+ Contact Support
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ searchSection: {
+ padding: Spacing.lg,
+ backgroundColor: AppColors.background,
+ },
+ searchContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: AppColors.surface,
+ borderRadius: BorderRadius.lg,
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.sm,
+ },
+ searchInput: {
+ flex: 1,
+ marginLeft: Spacing.sm,
+ fontSize: FontSizes.base,
+ color: AppColors.textPrimary,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ guidesGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ padding: Spacing.sm,
+ backgroundColor: AppColors.background,
+ },
+ guideItem: {
+ width: '50%',
+ padding: Spacing.md,
+ alignItems: 'center',
+ },
+ guideIcon: {
+ width: 56,
+ height: 56,
+ borderRadius: BorderRadius.lg,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginBottom: Spacing.sm,
+ },
+ guideTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ textAlign: 'center',
+ },
+ guideDescription: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ textAlign: 'center',
+ marginTop: 2,
+ },
+ faqContainer: {
+ backgroundColor: AppColors.background,
+ },
+ faqItem: {
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ faqHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ faqQuestion: {
+ flex: 1,
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ marginRight: Spacing.sm,
+ },
+ faqAnswer: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginTop: Spacing.sm,
+ lineHeight: 20,
+ },
+ faqDivider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginHorizontal: Spacing.lg,
+ },
+ noResults: {
+ alignItems: 'center',
+ paddingVertical: Spacing.xl,
+ },
+ noResultsText: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ marginTop: Spacing.md,
+ },
+ noResultsHint: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textMuted,
+ marginTop: Spacing.xs,
+ },
+ videosScroll: {
+ backgroundColor: AppColors.background,
+ paddingVertical: Spacing.md,
+ },
+ videoCard: {
+ width: 160,
+ marginLeft: Spacing.md,
+ },
+ videoThumbnail: {
+ height: 90,
+ backgroundColor: AppColors.primary,
+ borderRadius: BorderRadius.md,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ videoTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ marginTop: Spacing.sm,
+ },
+ videoDuration: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ needHelpCard: {
+ backgroundColor: AppColors.background,
+ margin: Spacing.lg,
+ borderRadius: BorderRadius.lg,
+ padding: Spacing.xl,
+ alignItems: 'center',
+ },
+ needHelpIcon: {
+ width: 64,
+ height: 64,
+ borderRadius: 32,
+ backgroundColor: '#DBEAFE',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginBottom: Spacing.md,
+ },
+ needHelpTitle: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ },
+ needHelpText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ textAlign: 'center',
+ marginTop: Spacing.sm,
+ marginBottom: Spacing.lg,
+ },
+ contactButton: {
+ backgroundColor: AppColors.primary,
+ borderRadius: BorderRadius.lg,
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.xl,
+ },
+ contactButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.white,
+ },
+});
diff --git a/app/profile/language.tsx b/app/profile/language.tsx
new file mode 100644
index 0000000..09451c8
--- /dev/null
+++ b/app/profile/language.tsx
@@ -0,0 +1,336 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ Alert,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface LanguageOptionProps {
+ code: string;
+ name: string;
+ nativeName: string;
+ flag: string;
+ isSelected: boolean;
+ isAvailable: boolean;
+ onSelect: () => void;
+}
+
+function LanguageOption({
+ code,
+ name,
+ nativeName,
+ flag,
+ isSelected,
+ isAvailable,
+ onSelect,
+}: LanguageOptionProps) {
+ return (
+
+ {flag}
+
+
+ {name}
+
+ {nativeName}
+
+ {!isAvailable ? (
+
+ Coming Soon
+
+ ) : isSelected ? (
+
+ ) : null}
+
+ );
+}
+
+export default function LanguageScreen() {
+ const [selectedLanguage, setSelectedLanguage] = useState('en');
+
+ const languages = [
+ { code: 'en', name: 'English', nativeName: 'English', flag: '🇺🇸', available: true },
+ { code: 'es', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸', available: false },
+ { code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷', available: false },
+ { code: 'de', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪', available: false },
+ { code: 'it', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹', available: false },
+ { code: 'pt', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹', available: false },
+ { code: 'nl', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱', available: false },
+ { code: 'pl', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱', available: false },
+ { code: 'ru', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺', available: false },
+ { code: 'ja', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵', available: false },
+ { code: 'zh', name: 'Chinese', nativeName: '中文', flag: '🇨🇳', available: false },
+ { code: 'ko', name: 'Korean', nativeName: '한국어', flag: '🇰🇷', available: false },
+ ];
+
+ const handleSelectLanguage = (code: string, name: string, available: boolean) => {
+ if (!available) {
+ Alert.alert(
+ 'Coming Soon',
+ `${name} translation is not available yet. We're working on adding more languages!`,
+ [{ text: 'OK' }]
+ );
+ return;
+ }
+
+ if (code === selectedLanguage) {
+ return;
+ }
+
+ Alert.alert(
+ 'Change Language',
+ `Are you sure you want to change the app language to ${name}?`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Change',
+ onPress: () => {
+ setSelectedLanguage(code);
+ Alert.alert(
+ 'Language Changed',
+ `The app language has been changed to ${name}. Some changes may require restarting the app.`,
+ [{ text: 'OK', onPress: () => router.back() }]
+ );
+ },
+ },
+ ]
+ );
+ };
+
+ const availableLanguages = languages.filter(l => l.available);
+ const comingSoonLanguages = languages.filter(l => !l.available);
+
+ return (
+
+
+ {/* Current Language */}
+
+
+
+
+ Current Language
+
+ {languages.find(l => l.code === selectedLanguage)?.name || 'English'}
+
+
+
+
+
+ {/* Available Languages */}
+
+ Available Languages
+
+ {availableLanguages.map((lang, index) => (
+
+ handleSelectLanguage(lang.code, lang.name, lang.available)}
+ />
+ {index < availableLanguages.length - 1 && }
+
+ ))}
+
+
+
+ {/* Coming Soon Languages */}
+
+ Coming Soon
+
+ {comingSoonLanguages.map((lang, index) => (
+
+ handleSelectLanguage(lang.code, lang.name, lang.available)}
+ />
+ {index < comingSoonLanguages.length - 1 && }
+
+ ))}
+
+
+
+ {/* Help Translate */}
+
+ Alert.alert(
+ 'Help Us Translate',
+ 'We\'d love your help translating WellNuo into more languages!\n\n' +
+ 'If you\'re fluent in another language and would like to contribute, ' +
+ 'please contact us at translations@wellnuo.com',
+ [{ text: 'OK' }]
+ )}
+ >
+
+
+
+
+ Help Us Translate
+
+ Want to see WellNuo in your language? Help us by contributing translations.
+
+
+
+
+
+
+ {/* Note */}
+
+
+
+ Changing the language will translate the app interface. Beneficiary data and
+ system notifications may remain in the original language.
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ currentLanguageCard: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ },
+ currentLanguageInfo: {
+ marginLeft: Spacing.md,
+ },
+ currentLanguageLabel: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ },
+ currentLanguageName: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginTop: 2,
+ },
+ languagesCard: {
+ backgroundColor: AppColors.background,
+ },
+ languageOption: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ languageOptionSelected: {
+ backgroundColor: '#DBEAFE',
+ },
+ flag: {
+ fontSize: 28,
+ },
+ languageInfo: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ languageName: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ languageNameDisabled: {
+ color: AppColors.textMuted,
+ },
+ languageNative: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textSecondary,
+ marginTop: 2,
+ },
+ comingSoonBadge: {
+ backgroundColor: AppColors.surface,
+ paddingHorizontal: Spacing.sm,
+ paddingVertical: 4,
+ borderRadius: BorderRadius.sm,
+ },
+ comingSoonText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ },
+ divider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginLeft: Spacing.lg + 28 + Spacing.md,
+ },
+ helpTranslateCard: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ },
+ helpTranslateIcon: {
+ width: 48,
+ height: 48,
+ borderRadius: BorderRadius.md,
+ backgroundColor: '#DBEAFE',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ helpTranslateContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ helpTranslateTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ helpTranslateDescription: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ noteContainer: {
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ marginHorizontal: Spacing.lg,
+ marginVertical: Spacing.lg,
+ },
+ noteText: {
+ flex: 1,
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginLeft: Spacing.sm,
+ lineHeight: 18,
+ },
+});
diff --git a/app/profile/notifications.tsx b/app/profile/notifications.tsx
new file mode 100644
index 0000000..2530a59
--- /dev/null
+++ b/app/profile/notifications.tsx
@@ -0,0 +1,380 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ Switch,
+ TouchableOpacity,
+ Alert,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface NotificationSettingProps {
+ icon: keyof typeof Ionicons.glyphMap;
+ iconColor: string;
+ iconBgColor: string;
+ title: string;
+ description: string;
+ value: boolean;
+ onValueChange: (value: boolean) => void;
+}
+
+function NotificationSetting({
+ icon,
+ iconColor,
+ iconBgColor,
+ title,
+ description,
+ value,
+ onValueChange,
+}: NotificationSettingProps) {
+ return (
+
+
+
+
+
+ {title}
+ {description}
+
+
+
+ );
+}
+
+export default function NotificationsScreen() {
+ // Alert types
+ const [emergencyAlerts, setEmergencyAlerts] = useState(true);
+ const [activityAlerts, setActivityAlerts] = useState(true);
+ const [lowBattery, setLowBattery] = useState(true);
+ const [dailySummary, setDailySummary] = useState(false);
+ const [weeklySummary, setWeeklySummary] = useState(true);
+
+ // Delivery methods
+ const [pushEnabled, setPushEnabled] = useState(true);
+ const [emailEnabled, setEmailEnabled] = useState(false);
+ const [smsEnabled, setSmsEnabled] = useState(false);
+
+ // Quiet hours
+ const [quietHours, setQuietHours] = useState(false);
+ const [quietStart, setQuietStart] = useState('22:00');
+ const [quietEnd, setQuietEnd] = useState('07:00');
+
+ const handleSave = () => {
+ Alert.alert(
+ 'Settings Saved',
+ 'Your notification preferences have been updated.',
+ [{ text: 'OK', onPress: () => router.back() }]
+ );
+ };
+
+ const handleQuietHoursConfig = () => {
+ Alert.alert(
+ 'Quiet Hours',
+ `Current: ${quietStart} - ${quietEnd}\n\nDuring quiet hours, only emergency alerts will be delivered.`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ { text: 'Set Start Time', onPress: () => Alert.alert('Coming Soon', 'Time picker coming soon!') },
+ ]
+ );
+ };
+
+ return (
+
+
+ {/* Alert Types */}
+
+ Alert Types
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Delivery Methods */}
+
+ Delivery Methods
+
+
+
+
+
+ {
+ if (value) {
+ Alert.alert(
+ 'SMS Notifications',
+ 'SMS notifications require a verified phone number. Would you like to add one?',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Add Phone',
+ onPress: () => router.push('/profile/edit')
+ },
+ ]
+ );
+ } else {
+ setSmsEnabled(false);
+ }
+ }}
+ />
+
+
+
+ {/* Quiet Hours */}
+
+ Quiet Hours
+
+
+ {quietHours && (
+ <>
+
+
+
+
+ Quiet Period
+
+
+ {quietStart} - {quietEnd}
+
+
+
+ >
+ )}
+
+
+ Emergency alerts will always be delivered, even during quiet hours.
+
+
+
+ {/* Test Notification */}
+
+ {
+ Alert.alert(
+ 'Test Notification Sent',
+ 'A test push notification has been sent to your device.',
+ [{ text: 'OK' }]
+ );
+ }}
+ >
+
+ Send Test Notification
+
+
+
+
+ {/* Save Button */}
+
+
+ Save Preferences
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ card: {
+ backgroundColor: AppColors.background,
+ },
+ settingRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ iconContainer: {
+ width: 40,
+ height: 40,
+ borderRadius: BorderRadius.md,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ settingContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ settingTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ settingDescription: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ divider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginLeft: Spacing.lg + 40 + Spacing.md,
+ },
+ timeRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ timeInfo: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ timeLabel: {
+ fontSize: FontSizes.base,
+ color: AppColors.textPrimary,
+ marginLeft: Spacing.sm,
+ },
+ timeValue: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ timeText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginRight: Spacing.xs,
+ },
+ quietNote: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ paddingHorizontal: Spacing.lg,
+ paddingTop: Spacing.sm,
+ },
+ testButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: AppColors.background,
+ paddingVertical: Spacing.md,
+ marginHorizontal: Spacing.lg,
+ borderRadius: BorderRadius.lg,
+ borderWidth: 1,
+ borderColor: AppColors.primary,
+ },
+ testButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.primary,
+ marginLeft: Spacing.sm,
+ },
+ footer: {
+ padding: Spacing.lg,
+ backgroundColor: AppColors.background,
+ borderTopWidth: 1,
+ borderTopColor: AppColors.border,
+ },
+ saveButton: {
+ backgroundColor: AppColors.primary,
+ borderRadius: BorderRadius.lg,
+ paddingVertical: Spacing.md,
+ alignItems: 'center',
+ },
+ saveButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.white,
+ },
+});
diff --git a/app/profile/privacy-policy.tsx b/app/profile/privacy-policy.tsx
new file mode 100644
index 0000000..e221d0c
--- /dev/null
+++ b/app/profile/privacy-policy.tsx
@@ -0,0 +1,331 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface PrivacyHighlightProps {
+ icon: keyof typeof Ionicons.glyphMap;
+ iconColor: string;
+ iconBgColor: string;
+ title: string;
+ description: string;
+}
+
+function PrivacyHighlight({
+ icon,
+ iconColor,
+ iconBgColor,
+ title,
+ description,
+}: PrivacyHighlightProps) {
+ return (
+
+
+
+
+
+ {title}
+ {description}
+
+
+ );
+}
+
+export default function PrivacyPolicyScreen() {
+ return (
+
+
+ {/* Highlights */}
+
+ Our Privacy Commitment
+
+
+
+
+
+
+
+
+
+
+
+ {/* Full Policy */}
+
+ Last Updated: December 2024
+
+ 1. Information We Collect
+
+ WellNuo collects information to provide and improve our elderly care monitoring
+ service. We collect:
+
+
+ Account Information
+ • Name and contact information
+ • Login credentials (encrypted)
+ • Payment information (processed by secure third parties)
+
+ Beneficiary Data
+ • Activity and motion sensor data
+ • Location information (if enabled)
+ • Health metrics from connected devices
+ • Daily routines and patterns
+
+ Usage Data
+ • App usage statistics
+ • Device information
+ • Error logs and performance data
+
+ 2. How We Use Your Information
+
+ We use collected information to:
+
+ • Provide real-time monitoring and alerts
+ • Generate wellness scores and insights
+ • Improve our AI algorithms (anonymized data only)
+ • Send notifications and communications
+ • Prevent fraud and ensure security
+ • Comply with legal obligations
+
+ 3. Data Sharing
+
+ We do NOT sell your personal information. We may share data with:
+
+ • Authorized caregivers (your permission required)
+ • Healthcare providers (with explicit consent)
+ • Emergency services (in emergency situations)
+ • Service providers (under strict contracts)
+ • Legal authorities (when required by law)
+
+ 4. Data Security
+
+ We implement industry-leading security measures:
+
+ • AES-256 encryption for data at rest
+ • TLS 1.3 encryption for data in transit
+ • Regular security audits and penetration testing
+ • Multi-factor authentication options
+ • Automatic security patching
+ • SOC 2 Type II certified infrastructure
+
+ 5. Data Retention
+
+ We retain your data for:
+
+ • Active accounts: Duration of service + 2 years
+ • Deleted accounts: 30 days (recovery period)
+ • Legal requirements: As required by law
+ • Anonymized data: May be retained indefinitely for research
+
+ 6. Your Rights
+
+ Under GDPR, CCPA, and other privacy laws, you have the right to:
+
+ • Access your personal data
+ • Correct inaccurate data
+ • Delete your data ("right to be forgotten")
+ • Export your data (data portability)
+ • Opt out of marketing communications
+ • Restrict processing of your data
+
+ 7. Children's Privacy
+
+ WellNuo is designed for adult caregivers monitoring elderly individuals. We do
+ not knowingly collect information from children under 13. If we discover such
+ data has been collected, we will delete it immediately.
+
+
+ 8. International Data Transfers
+
+ Data may be transferred to and processed in countries outside your residence.
+ We ensure adequate safeguards through:
+
+ • Standard Contractual Clauses (SCCs)
+ • Privacy Shield certification (where applicable)
+ • Data Processing Agreements with vendors
+
+ 9. Cookies and Tracking
+
+ Our mobile app uses minimal tracking:
+
+ • Essential cookies for authentication
+ • Analytics (anonymized, opt-out available)
+ • No third-party advertising trackers
+
+ 10. Changes to This Policy
+
+ We may update this Privacy Policy periodically. We will notify you of material
+ changes via email or in-app notification at least 30 days before they take effect.
+
+
+ 11. Contact Us
+
+ For privacy-related questions or to exercise your rights:
+
+
+ Privacy Officer
+ Email: privacy@wellnuo.com
+ Address: 123 Care Street, San Francisco, CA 94102
+ Phone: +1 (555) 123-4567
+
+
+
+
+ Your privacy matters to us. If you have any concerns about how we handle your
+ data, please don't hesitate to contact us.
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ highlightsSection: {
+ backgroundColor: AppColors.background,
+ paddingVertical: Spacing.lg,
+ },
+ highlightsSectionTitle: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ textAlign: 'center',
+ marginBottom: Spacing.md,
+ },
+ highlightsCard: {
+ marginHorizontal: Spacing.lg,
+ },
+ highlight: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.sm,
+ },
+ highlightIcon: {
+ width: 40,
+ height: 40,
+ borderRadius: BorderRadius.md,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ highlightContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ highlightTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ highlightDescription: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ highlightDivider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginLeft: 40 + Spacing.md,
+ marginVertical: Spacing.xs,
+ },
+ content: {
+ padding: Spacing.lg,
+ backgroundColor: AppColors.background,
+ marginTop: Spacing.md,
+ },
+ lastUpdated: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textMuted,
+ marginBottom: Spacing.lg,
+ textAlign: 'center',
+ },
+ sectionTitle: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginTop: Spacing.lg,
+ marginBottom: Spacing.sm,
+ },
+ subSectionTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ marginTop: Spacing.md,
+ marginBottom: Spacing.xs,
+ },
+ paragraph: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ lineHeight: 22,
+ marginBottom: Spacing.sm,
+ },
+ bulletPoint: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ lineHeight: 22,
+ marginLeft: Spacing.md,
+ marginBottom: 4,
+ },
+ contactCard: {
+ backgroundColor: AppColors.surface,
+ borderRadius: BorderRadius.lg,
+ padding: Spacing.md,
+ marginTop: Spacing.sm,
+ },
+ contactTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginBottom: Spacing.sm,
+ },
+ contactInfo: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginBottom: 4,
+ },
+ footer: {
+ marginTop: Spacing.xl,
+ paddingTop: Spacing.lg,
+ borderTopWidth: 1,
+ borderTopColor: AppColors.border,
+ },
+ footerText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ textAlign: 'center',
+ fontStyle: 'italic',
+ },
+});
diff --git a/app/profile/privacy.tsx b/app/profile/privacy.tsx
new file mode 100644
index 0000000..1af9127
--- /dev/null
+++ b/app/profile/privacy.tsx
@@ -0,0 +1,560 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ Switch,
+ TouchableOpacity,
+ Alert,
+ TextInput,
+ Modal,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { useAuth } from '@/contexts/AuthContext';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface SecurityItemProps {
+ icon: keyof typeof Ionicons.glyphMap;
+ iconColor: string;
+ iconBgColor: string;
+ title: string;
+ description: string;
+ onPress: () => void;
+ rightElement?: React.ReactNode;
+}
+
+function SecurityItem({
+ icon,
+ iconColor,
+ iconBgColor,
+ title,
+ description,
+ onPress,
+ rightElement,
+}: SecurityItemProps) {
+ return (
+
+
+
+
+
+ {title}
+ {description}
+
+ {rightElement || }
+
+ );
+}
+
+export default function PrivacyScreen() {
+ const { logout } = useAuth();
+ const [twoFactor, setTwoFactor] = useState(false);
+ const [biometric, setBiometric] = useState(false);
+ const [showPasswordModal, setShowPasswordModal] = useState(false);
+ const [currentPassword, setCurrentPassword] = useState('');
+ const [newPassword, setNewPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+
+ const handleChangePassword = () => {
+ setShowPasswordModal(true);
+ };
+
+ const handlePasswordSubmit = () => {
+ if (!currentPassword || !newPassword || !confirmPassword) {
+ Alert.alert('Error', 'Please fill in all fields');
+ return;
+ }
+ if (newPassword !== confirmPassword) {
+ Alert.alert('Error', 'New passwords do not match');
+ return;
+ }
+ if (newPassword.length < 8) {
+ Alert.alert('Error', 'Password must be at least 8 characters');
+ return;
+ }
+
+ setShowPasswordModal(false);
+ setCurrentPassword('');
+ setNewPassword('');
+ setConfirmPassword('');
+
+ Alert.alert('Success', 'Your password has been changed successfully.');
+ };
+
+ const handleEnable2FA = (value: boolean) => {
+ if (value) {
+ Alert.alert(
+ 'Enable Two-Factor Authentication',
+ 'This will add an extra layer of security to your account. You will need to enter a code from your authenticator app when signing in.',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Enable',
+ onPress: () => {
+ setTwoFactor(true);
+ Alert.alert(
+ 'Scan QR Code',
+ 'Open your authenticator app (Google Authenticator, Authy, etc.) and scan the QR code.',
+ [{ text: 'Done' }]
+ );
+ }
+ },
+ ]
+ );
+ } else {
+ Alert.alert(
+ 'Disable Two-Factor Authentication',
+ 'Are you sure? This will make your account less secure.',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Disable',
+ style: 'destructive',
+ onPress: () => setTwoFactor(false)
+ },
+ ]
+ );
+ }
+ };
+
+ const handleManageSessions = () => {
+ Alert.alert(
+ 'Active Sessions',
+ 'You are currently signed in on:\n\n' +
+ '• iPhone 14 Pro (This device)\n Last active: Just now\n\n' +
+ '• Chrome on MacBook Pro\n Last active: 2 hours ago\n\n' +
+ '• Safari on iPad\n Last active: 3 days ago',
+ [
+ { text: 'Close' },
+ {
+ text: 'Sign Out All',
+ style: 'destructive',
+ onPress: () => {
+ Alert.alert(
+ 'Sign Out All Devices',
+ 'This will sign you out of all devices including this one.',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Sign Out All',
+ style: 'destructive',
+ onPress: async () => {
+ await logout();
+ router.replace('/(auth)/login');
+ }
+ },
+ ]
+ );
+ }
+ },
+ ]
+ );
+ };
+
+ const handleExportData = () => {
+ Alert.alert(
+ 'Export Your Data',
+ 'We will prepare a downloadable file containing all your data. This may take a few minutes.\n\nYou will receive an email when your data is ready.',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Request Export',
+ onPress: () => Alert.alert('Request Sent', 'You will receive an email when your data export is ready.')
+ },
+ ]
+ );
+ };
+
+ const handleDeleteAccount = () => {
+ Alert.alert(
+ 'Delete Account',
+ 'Are you absolutely sure you want to delete your account?\n\n' +
+ '• All your data will be permanently deleted\n' +
+ '• You will lose access to all beneficiary data\n' +
+ '• This action cannot be undone',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Delete Account',
+ style: 'destructive',
+ onPress: () => {
+ Alert.alert(
+ 'Final Confirmation',
+ 'Type "DELETE" to confirm account deletion.',
+ [{ text: 'Cancel', style: 'cancel' }]
+ );
+ }
+ },
+ ]
+ );
+ };
+
+ return (
+
+
+ {/* Password & Authentication */}
+
+ Authentication
+
+
+
+ handleEnable2FA(!twoFactor)}
+ rightElement={
+
+ }
+ />
+
+ setBiometric(!biometric)}
+ rightElement={
+
+ }
+ />
+
+
+
+ {/* Session Management */}
+
+ Sessions
+
+
+
+
+
+ {/* Data & Privacy */}
+
+ Data & Privacy
+
+
+
+ Alert.alert('Data Sharing', 'Your data is only shared with authorized caregivers and healthcare providers you designate.')}
+ />
+
+
+
+ {/* Login History */}
+
+ Recent Activity
+
+
+
+
+ Login from iPhone
+ Today at 2:34 PM • San Francisco, CA
+
+
+
+
+
+ Login from MacBook
+ Yesterday at 10:15 AM • San Francisco, CA
+
+
+
+
+
+ Password changed
+ Dec 1, 2024 at 4:22 PM
+
+
+
+
+
+ {/* Danger Zone */}
+
+ Danger Zone
+
+
+
+ Delete Account
+
+
+
+
+
+ {/* Password Change Modal */}
+ setShowPasswordModal(false)}
+ >
+
+
+ setShowPasswordModal(false)}>
+ Cancel
+
+ Change Password
+
+ Save
+
+
+
+
+
+ Current Password
+
+
+
+
+ New Password
+
+ Must be at least 8 characters
+
+
+
+ Confirm New Password
+
+
+
+
+ Password Requirements:
+ • At least 8 characters
+ • Mix of letters and numbers recommended
+ • Special characters for extra security
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ card: {
+ backgroundColor: AppColors.background,
+ },
+ securityRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ iconContainer: {
+ width: 40,
+ height: 40,
+ borderRadius: BorderRadius.md,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ securityContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ securityTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ securityDescription: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ divider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginLeft: Spacing.lg + 40 + Spacing.md,
+ },
+ activityItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ activityDot: {
+ width: 8,
+ height: 8,
+ borderRadius: 4,
+ backgroundColor: AppColors.success,
+ marginRight: Spacing.md,
+ },
+ activityDotOld: {
+ backgroundColor: AppColors.textMuted,
+ },
+ activityContent: {
+ flex: 1,
+ },
+ activityTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ activityMeta: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ dangerButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingVertical: Spacing.md,
+ },
+ dangerButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.error,
+ marginLeft: Spacing.sm,
+ },
+ // Modal styles
+ modalContainer: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ modalHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.md,
+ backgroundColor: AppColors.background,
+ borderBottomWidth: 1,
+ borderBottomColor: AppColors.border,
+ },
+ modalTitle: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ },
+ cancelText: {
+ fontSize: FontSizes.base,
+ color: AppColors.textSecondary,
+ },
+ saveText: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.primary,
+ },
+ modalContent: {
+ padding: Spacing.lg,
+ },
+ inputGroup: {
+ marginBottom: Spacing.lg,
+ },
+ label: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginBottom: Spacing.xs,
+ },
+ input: {
+ backgroundColor: AppColors.background,
+ borderRadius: BorderRadius.md,
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.md,
+ fontSize: FontSizes.base,
+ color: AppColors.textPrimary,
+ borderWidth: 1,
+ borderColor: AppColors.border,
+ },
+ hint: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: Spacing.xs,
+ },
+ passwordRequirements: {
+ backgroundColor: AppColors.background,
+ borderRadius: BorderRadius.lg,
+ padding: Spacing.md,
+ marginTop: Spacing.md,
+ },
+ requirementsTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ marginBottom: Spacing.sm,
+ },
+ requirementItem: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textMuted,
+ marginBottom: 4,
+ },
+});
diff --git a/app/profile/subscription.tsx b/app/profile/subscription.tsx
new file mode 100644
index 0000000..e6712e3
--- /dev/null
+++ b/app/profile/subscription.tsx
@@ -0,0 +1,517 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ Alert,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface PlanFeatureProps {
+ text: string;
+ included: boolean;
+}
+
+function PlanFeature({ text, included }: PlanFeatureProps) {
+ return (
+
+
+
+ {text}
+
+
+ );
+}
+
+export default function SubscriptionScreen() {
+ const [selectedPlan, setSelectedPlan] = useState<'monthly' | 'yearly'>('yearly');
+ const currentPlan = 'free'; // Could be 'free', 'pro', 'enterprise'
+
+ const handleSubscribe = () => {
+ Alert.alert(
+ 'Subscribe to Pro',
+ `You selected the ${selectedPlan === 'yearly' ? 'Yearly' : 'Monthly'} plan.\n\n` +
+ `Total: ${selectedPlan === 'yearly' ? '$79.99/year (Save $39.89!)' : '$9.99/month'}`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Continue to Payment',
+ onPress: () => Alert.alert('Coming Soon', 'Payment integration coming soon!')
+ },
+ ]
+ );
+ };
+
+ const handleRestorePurchases = () => {
+ Alert.alert(
+ 'Restoring Purchases',
+ 'Looking for previous purchases...',
+ [{ text: 'OK' }]
+ );
+
+ setTimeout(() => {
+ Alert.alert('No Purchases Found', 'We couldn\'t find any previous purchases associated with your account.');
+ }, 1500);
+ };
+
+ return (
+
+
+ {/* Current Plan Badge */}
+
+
+ CURRENT PLAN
+
+ Free
+
+ Basic monitoring features
+
+
+
+ {/* Pro Features */}
+
+
+
+
+
+ PRO
+
+ WellNuo Pro
+ Everything you need for complete care
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Pricing Options */}
+
+ Choose Your Plan
+
+ setSelectedPlan('yearly')}
+ >
+
+
+ Yearly
+
+ SAVE 33%
+
+
+ $79.99/year
+ $6.67/month
+
+
+ {selectedPlan === 'yearly' && (
+
+ )}
+
+
+
+ setSelectedPlan('monthly')}
+ >
+
+ Monthly
+ $9.99/month
+ Billed monthly
+
+
+ {selectedPlan === 'monthly' && (
+
+ )}
+
+
+
+
+ {/* Compare Plans */}
+
+ Compare Plans
+
+
+ Feature
+ Free
+ Pro
+
+
+ {[
+ { feature: 'Beneficiaries', free: '2', pro: 'Unlimited' },
+ { feature: 'Real-time alerts', free: '✓', pro: '✓' },
+ { feature: 'Activity history', free: '7 days', pro: '1 year' },
+ { feature: 'AI insights', free: '—', pro: '✓' },
+ { feature: 'Custom alerts', free: '—', pro: '✓' },
+ { feature: 'Data export', free: '—', pro: '✓' },
+ { feature: 'Support', free: 'Email', pro: '24/7 Priority' },
+ ].map((row, index) => (
+
+ {row.feature}
+ {row.free}
+ {row.pro}
+
+ ))}
+
+
+
+ {/* Testimonials */}
+
+ What Users Say
+
+ {[
+ {
+ name: 'Sarah M.',
+ text: 'WellNuo Pro gives me peace of mind. The AI insights helped detect early warning signs.',
+ rating: 5,
+ },
+ {
+ name: 'John D.',
+ text: 'The family sharing feature is amazing. Now my siblings can all monitor our parents together.',
+ rating: 5,
+ },
+ {
+ name: 'Maria L.',
+ text: 'Worth every penny. The advanced analytics helped us understand mom\'s patterns better.',
+ rating: 5,
+ },
+ ].map((testimonial, index) => (
+
+
+ {[...Array(testimonial.rating)].map((_, i) => (
+
+ ))}
+
+ "{testimonial.text}"
+ — {testimonial.name}
+
+ ))}
+
+
+
+ {/* Restore Purchases */}
+
+ Restore Purchases
+
+
+ {/* Terms */}
+
+ Payment will be charged to your Apple ID account at the confirmation of purchase.
+ Subscription automatically renews unless it is cancelled at least 24 hours before
+ the end of the current period.
+
+
+
+ {/* Subscribe Button */}
+
+
+
+ Subscribe to Pro — {selectedPlan === 'yearly' ? '$79.99/year' : '$9.99/month'}
+
+
+ 7-day free trial • Cancel anytime
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ currentPlanBanner: {
+ backgroundColor: AppColors.background,
+ padding: Spacing.lg,
+ alignItems: 'center',
+ },
+ currentPlanBadge: {
+ backgroundColor: '#E0E7FF',
+ paddingHorizontal: Spacing.sm,
+ paddingVertical: 4,
+ borderRadius: BorderRadius.sm,
+ },
+ currentPlanBadgeText: {
+ fontSize: FontSizes.xs,
+ fontWeight: '600',
+ color: '#4F46E5',
+ },
+ currentPlanName: {
+ fontSize: FontSizes.xl,
+ fontWeight: '700',
+ color: AppColors.textPrimary,
+ marginTop: Spacing.sm,
+ },
+ currentPlanDescription: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginTop: 4,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ proCard: {
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ borderRadius: BorderRadius.lg,
+ overflow: 'hidden',
+ borderWidth: 2,
+ borderColor: '#9333EA',
+ },
+ proHeader: {
+ backgroundColor: '#F3E8FF',
+ padding: Spacing.lg,
+ alignItems: 'center',
+ },
+ proBadge: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: AppColors.white,
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.xs,
+ borderRadius: BorderRadius.full,
+ },
+ proBadgeText: {
+ fontSize: FontSizes.sm,
+ fontWeight: '700',
+ color: '#9333EA',
+ marginLeft: Spacing.xs,
+ },
+ proTitle: {
+ fontSize: FontSizes.xl,
+ fontWeight: '700',
+ color: '#9333EA',
+ marginTop: Spacing.md,
+ },
+ proSubtitle: {
+ fontSize: FontSizes.sm,
+ color: '#7C3AED',
+ marginTop: 4,
+ },
+ featuresContainer: {
+ padding: Spacing.lg,
+ },
+ featureRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.xs,
+ },
+ featureText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textPrimary,
+ marginLeft: Spacing.sm,
+ },
+ featureTextDisabled: {
+ color: AppColors.textMuted,
+ },
+ planOption: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ marginBottom: Spacing.sm,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ borderWidth: 2,
+ borderColor: AppColors.border,
+ },
+ planOptionSelected: {
+ borderColor: AppColors.primary,
+ backgroundColor: '#DBEAFE',
+ },
+ planOptionContent: {
+ flex: 1,
+ },
+ planOptionHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ planOptionTitle: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ },
+ saveBadge: {
+ backgroundColor: AppColors.success,
+ paddingHorizontal: Spacing.xs,
+ paddingVertical: 2,
+ borderRadius: BorderRadius.sm,
+ marginLeft: Spacing.sm,
+ },
+ saveBadgeText: {
+ fontSize: 10,
+ fontWeight: '700',
+ color: AppColors.white,
+ },
+ planOptionPrice: {
+ fontSize: FontSizes.xl,
+ fontWeight: '700',
+ color: AppColors.primary,
+ marginTop: 4,
+ },
+ planOptionSubprice: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ radioButton: {
+ width: 24,
+ height: 24,
+ borderRadius: 12,
+ borderWidth: 2,
+ borderColor: AppColors.border,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ radioButtonSelected: {
+ borderColor: AppColors.primary,
+ },
+ radioButtonInner: {
+ width: 12,
+ height: 12,
+ borderRadius: 6,
+ backgroundColor: AppColors.primary,
+ },
+ comparisonCard: {
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ borderRadius: BorderRadius.lg,
+ overflow: 'hidden',
+ },
+ comparisonHeader: {
+ flexDirection: 'row',
+ backgroundColor: AppColors.surface,
+ paddingVertical: Spacing.sm,
+ paddingHorizontal: Spacing.md,
+ },
+ comparisonFeatureTitle: {
+ flex: 2,
+ fontSize: FontSizes.xs,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ },
+ comparisonPlanTitle: {
+ flex: 1,
+ fontSize: FontSizes.xs,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ textAlign: 'center',
+ },
+ comparisonRow: {
+ flexDirection: 'row',
+ paddingVertical: Spacing.sm,
+ paddingHorizontal: Spacing.md,
+ borderBottomWidth: 1,
+ borderBottomColor: AppColors.border,
+ },
+ comparisonFeature: {
+ flex: 2,
+ fontSize: FontSizes.sm,
+ color: AppColors.textPrimary,
+ },
+ comparisonValue: {
+ flex: 1,
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ textAlign: 'center',
+ },
+ comparisonValuePro: {
+ color: '#9333EA',
+ fontWeight: '500',
+ },
+ testimonialCard: {
+ width: 280,
+ backgroundColor: AppColors.background,
+ marginLeft: Spacing.lg,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ },
+ testimonialStars: {
+ flexDirection: 'row',
+ marginBottom: Spacing.sm,
+ },
+ testimonialText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textPrimary,
+ fontStyle: 'italic',
+ lineHeight: 20,
+ },
+ testimonialName: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: Spacing.sm,
+ },
+ restoreButton: {
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ marginTop: Spacing.md,
+ },
+ restoreButtonText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.primary,
+ },
+ termsText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ textAlign: 'center',
+ paddingHorizontal: Spacing.xl,
+ paddingBottom: Spacing.lg,
+ lineHeight: 16,
+ },
+ footer: {
+ padding: Spacing.lg,
+ backgroundColor: AppColors.background,
+ borderTopWidth: 1,
+ borderTopColor: AppColors.border,
+ },
+ subscribeButton: {
+ backgroundColor: '#9333EA',
+ borderRadius: BorderRadius.lg,
+ paddingVertical: Spacing.md,
+ alignItems: 'center',
+ },
+ subscribeButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.white,
+ },
+ guaranteeText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ textAlign: 'center',
+ marginTop: Spacing.sm,
+ },
+});
diff --git a/app/profile/support.tsx b/app/profile/support.tsx
new file mode 100644
index 0000000..515c5b8
--- /dev/null
+++ b/app/profile/support.tsx
@@ -0,0 +1,475 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ TextInput,
+ Linking,
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+interface ContactMethodProps {
+ icon: keyof typeof Ionicons.glyphMap;
+ iconColor: string;
+ iconBgColor: string;
+ title: string;
+ subtitle: string;
+ onPress: () => void;
+}
+
+function ContactMethod({
+ icon,
+ iconColor,
+ iconBgColor,
+ title,
+ subtitle,
+ onPress,
+}: ContactMethodProps) {
+ return (
+
+
+
+
+
+ {title}
+ {subtitle}
+
+
+
+ );
+}
+
+export default function SupportScreen() {
+ const [subject, setSubject] = useState('');
+ const [message, setMessage] = useState('');
+ const [category, setCategory] = useState('');
+ const [isSending, setIsSending] = useState(false);
+
+ const categories = [
+ 'Technical Issue',
+ 'Billing Question',
+ 'Feature Request',
+ 'Account Help',
+ 'Emergency',
+ 'Other',
+ ];
+
+ const handleCall = () => {
+ Linking.openURL('tel:+15551234567').catch(() => {
+ Alert.alert('Error', 'Unable to make phone call');
+ });
+ };
+
+ const handleEmail = () => {
+ Linking.openURL('mailto:support@wellnuo.com?subject=Support Request').catch(() => {
+ Alert.alert('Error', 'Unable to open email client');
+ });
+ };
+
+ const handleChat = () => {
+ Alert.alert(
+ 'Live Chat',
+ 'Connecting to a support agent...\n\nEstimated wait time: 2 minutes',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ { text: 'Start Chat', onPress: () => Alert.alert('Coming Soon', 'Live chat feature coming soon!') },
+ ]
+ );
+ };
+
+ const handleSendTicket = async () => {
+ if (!category) {
+ Alert.alert('Error', 'Please select a category');
+ return;
+ }
+ if (!subject.trim()) {
+ Alert.alert('Error', 'Please enter a subject');
+ return;
+ }
+ if (!message.trim()) {
+ Alert.alert('Error', 'Please describe your issue');
+ return;
+ }
+
+ setIsSending(true);
+
+ // Simulate API call
+ await new Promise(resolve => setTimeout(resolve, 1500));
+
+ setIsSending(false);
+ Alert.alert(
+ 'Ticket Submitted',
+ 'Thank you for contacting us!\n\nTicket #WN-2024-12345\n\nWe\'ll respond within 24 hours.',
+ [{ text: 'OK', onPress: () => router.back() }]
+ );
+ };
+
+ return (
+
+
+
+ {/* Quick Contact */}
+
+ Quick Contact
+
+
+
+
+
+
+
+
+
+ {/* Support Hours */}
+
+
+
+
+ Support Hours
+ Phone: Mon-Fri 8am-8pm EST
+ Email & Chat: 24/7
+ Emergency: 24/7
+
+
+
+
+ {/* Submit a Ticket */}
+
+ Submit a Ticket
+
+ {/* Category */}
+
+ Category *
+
+ {categories.map((cat) => (
+ setCategory(cat)}
+ >
+
+ {cat}
+
+
+ ))}
+
+
+
+ {/* Subject */}
+
+ Subject *
+
+
+
+ {/* Message */}
+
+ Message *
+
+
+
+ {/* Attachments hint */}
+
+
+
+ Need to attach screenshots? Reply to your ticket email.
+
+
+
+ {/* Submit Button */}
+
+
+ {isSending ? 'Sending...' : 'Submit Ticket'}
+
+
+
+
+
+ {/* FAQ Link */}
+
+ router.push('/profile/help')}
+ >
+
+
+
+ Check our Help Center
+
+ Find answers to common questions
+
+
+
+
+
+
+
+ {/* Emergency Notice */}
+
+
+
+ If you're experiencing a medical emergency, please call 911 or your local
+ emergency services immediately.
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.surface,
+ },
+ keyboardView: {
+ flex: 1,
+ },
+ section: {
+ marginTop: Spacing.md,
+ },
+ sectionTitle: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textSecondary,
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.sm,
+ textTransform: 'uppercase',
+ },
+ card: {
+ backgroundColor: AppColors.background,
+ },
+ contactMethod: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: Spacing.md,
+ paddingHorizontal: Spacing.lg,
+ },
+ contactIcon: {
+ width: 48,
+ height: 48,
+ borderRadius: BorderRadius.md,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ contactContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ contactTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ contactSubtitle: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginTop: 2,
+ },
+ divider: {
+ height: 1,
+ backgroundColor: AppColors.border,
+ marginLeft: Spacing.lg + 48 + Spacing.md,
+ },
+ hoursCard: {
+ flexDirection: 'row',
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ alignItems: 'flex-start',
+ },
+ hoursContent: {
+ flex: 1,
+ marginLeft: Spacing.md,
+ },
+ hoursTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginBottom: Spacing.xs,
+ },
+ hoursText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ marginTop: 2,
+ },
+ formCard: {
+ backgroundColor: AppColors.background,
+ padding: Spacing.lg,
+ },
+ inputGroup: {
+ marginBottom: Spacing.md,
+ },
+ label: {
+ fontSize: FontSizes.sm,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginBottom: Spacing.xs,
+ },
+ input: {
+ backgroundColor: AppColors.surface,
+ borderRadius: BorderRadius.md,
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.md,
+ fontSize: FontSizes.base,
+ color: AppColors.textPrimary,
+ borderWidth: 1,
+ borderColor: AppColors.border,
+ },
+ textArea: {
+ minHeight: 120,
+ paddingTop: Spacing.md,
+ },
+ categoryContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginTop: Spacing.xs,
+ },
+ categoryChip: {
+ paddingHorizontal: Spacing.md,
+ paddingVertical: Spacing.xs,
+ borderRadius: BorderRadius.full,
+ backgroundColor: AppColors.surface,
+ marginRight: Spacing.xs,
+ marginBottom: Spacing.xs,
+ borderWidth: 1,
+ borderColor: AppColors.border,
+ },
+ categoryChipSelected: {
+ backgroundColor: AppColors.primary,
+ borderColor: AppColors.primary,
+ },
+ categoryChipText: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ },
+ categoryChipTextSelected: {
+ color: AppColors.white,
+ },
+ attachmentHint: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: Spacing.lg,
+ },
+ attachmentHintText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginLeft: Spacing.xs,
+ },
+ submitButton: {
+ backgroundColor: AppColors.primary,
+ borderRadius: BorderRadius.lg,
+ paddingVertical: Spacing.md,
+ alignItems: 'center',
+ },
+ submitButtonDisabled: {
+ opacity: 0.6,
+ },
+ submitButtonText: {
+ fontSize: FontSizes.base,
+ fontWeight: '600',
+ color: AppColors.white,
+ },
+ faqLink: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: AppColors.background,
+ marginHorizontal: Spacing.lg,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ },
+ faqLinkContent: {
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ faqLinkText: {
+ marginLeft: Spacing.md,
+ },
+ faqLinkTitle: {
+ fontSize: FontSizes.base,
+ fontWeight: '500',
+ color: AppColors.textPrimary,
+ },
+ faqLinkSubtitle: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ marginTop: 2,
+ },
+ emergencyNotice: {
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ backgroundColor: '#FEE2E2',
+ marginHorizontal: Spacing.lg,
+ marginVertical: Spacing.lg,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.lg,
+ },
+ emergencyText: {
+ flex: 1,
+ fontSize: FontSizes.xs,
+ color: AppColors.error,
+ marginLeft: Spacing.sm,
+ lineHeight: 18,
+ },
+});
diff --git a/app/profile/terms.tsx b/app/profile/terms.tsx
new file mode 100644
index 0000000..c8fb019
--- /dev/null
+++ b/app/profile/terms.tsx
@@ -0,0 +1,197 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
+
+export default function TermsScreen() {
+ return (
+
+
+
+ Last Updated: December 2024
+
+ 1. Acceptance of Terms
+
+ By accessing and using WellNuo ("the Service"), you agree to be bound by these
+ Terms of Service and all applicable laws and regulations. If you do not agree
+ with any of these terms, you are prohibited from using or accessing this Service.
+
+
+ 2. Description of Service
+
+ WellNuo provides an elderly care monitoring platform that allows caregivers and
+ family members to monitor the wellness and activity of their loved ones through
+ connected sensors and devices. The Service includes:
+
+ • Real-time activity monitoring
+ • Wellness score tracking
+ • Emergency alert notifications
+ • AI-powered health insights (Pro plan)
+ • Data analytics and reporting
+
+ 3. User Accounts
+
+ To use the Service, you must create an account and provide accurate, complete
+ information. You are responsible for:
+
+ • Maintaining the confidentiality of your account
+ • All activities that occur under your account
+ • Notifying us immediately of any unauthorized use
+
+ 4. Privacy and Data Protection
+
+ Your privacy is critically important to us. Our Privacy Policy explains how we
+ collect, use, and protect your personal information and the data of beneficiaries.
+ By using the Service, you consent to our data practices.
+
+
+ Key privacy commitments:
+
+ • End-to-end encryption for all data
+ • HIPAA-compliant data storage
+ • No sale of personal data to third parties
+ • Right to data export and deletion
+
+ 5. Acceptable Use
+
+ You agree to use the Service only for lawful purposes and in accordance with
+ these Terms. You agree NOT to:
+
+ • Use the Service for any illegal purpose
+ • Violate any beneficiary's privacy rights
+ • Share your account credentials with others
+ • Attempt to reverse engineer the Service
+ • Transmit any malware or harmful code
+
+ 6. Subscription and Billing
+
+ WellNuo offers both free and paid subscription plans:
+
+ • Free Plan: Basic features with limited beneficiaries
+ • Pro Plan: Full features billed monthly or annually
+
+ Subscription fees are billed in advance. You may cancel at any time, but refunds
+ are not provided for partial billing periods.
+
+
+ 7. Disclaimers
+
+ The Service is provided "as is" without warranties of any kind. WellNuo:
+
+ • Does not guarantee continuous, uninterrupted service
+ • Is not a medical device or substitute for professional care
+ • Cannot guarantee detection of all emergencies
+
+ Always seek professional medical advice for health concerns.
+
+
+ 8. Limitation of Liability
+
+ To the maximum extent permitted by law, WellNuo shall not be liable for any
+ indirect, incidental, special, consequential, or punitive damages arising out
+ of your use of the Service.
+
+
+ 9. Indemnification
+
+ You agree to indemnify and hold harmless WellNuo and its affiliates from any
+ claims, damages, or expenses arising from your use of the Service or violation
+ of these Terms.
+
+
+ 10. Modifications
+
+ We reserve the right to modify these Terms at any time. We will notify you of
+ material changes through the app or email. Continued use after changes constitutes
+ acceptance of the new Terms.
+
+
+ 11. Termination
+
+ We may terminate or suspend your account at any time for violation of these Terms.
+ Upon termination, your right to use the Service ceases immediately.
+
+
+ 12. Governing Law
+
+ These Terms shall be governed by the laws of the State of California, United States,
+ without regard to conflict of law provisions.
+
+
+ 13. Contact Information
+
+ For questions about these Terms, please contact us:
+
+ WellNuo Inc.
+ Email: legal@wellnuo.com
+ Address: 123 Care Street, San Francisco, CA 94102
+
+
+
+ By using WellNuo, you acknowledge that you have read, understood, and agree
+ to be bound by these Terms of Service.
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: AppColors.background,
+ },
+ content: {
+ padding: Spacing.lg,
+ },
+ lastUpdated: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textMuted,
+ marginBottom: Spacing.lg,
+ textAlign: 'center',
+ },
+ sectionTitle: {
+ fontSize: FontSizes.lg,
+ fontWeight: '600',
+ color: AppColors.textPrimary,
+ marginTop: Spacing.lg,
+ marginBottom: Spacing.sm,
+ },
+ paragraph: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ lineHeight: 22,
+ marginBottom: Spacing.sm,
+ },
+ bulletPoint: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textSecondary,
+ lineHeight: 22,
+ marginLeft: Spacing.md,
+ marginBottom: 4,
+ },
+ contactInfo: {
+ fontSize: FontSizes.sm,
+ color: AppColors.textPrimary,
+ marginBottom: 4,
+ },
+ footer: {
+ marginTop: Spacing.xl,
+ paddingTop: Spacing.lg,
+ borderTopWidth: 1,
+ borderTopColor: AppColors.border,
+ },
+ footerText: {
+ fontSize: FontSizes.xs,
+ color: AppColors.textMuted,
+ textAlign: 'center',
+ fontStyle: 'italic',
+ },
+});
diff --git a/contexts/BeneficiaryContext.tsx b/contexts/BeneficiaryContext.tsx
index f769228..1faf6f1 100644
--- a/contexts/BeneficiaryContext.tsx
+++ b/contexts/BeneficiaryContext.tsx
@@ -23,44 +23,55 @@ export function BeneficiaryProvider({ children }: { children: React.ReactNode })
return '';
}
- const parts = [`[Context: Asking about ${currentBeneficiary.name}`];
+ const b = currentBeneficiary;
+ const contextParts: string[] = [];
- if (currentBeneficiary.relationship) {
- parts.push(`(${currentBeneficiary.relationship})`);
+ // Basic info
+ contextParts.push(`Person: ${b.name}`);
+
+ if (b.address) {
+ contextParts.push(`Address: ${b.address}`);
}
- if (currentBeneficiary.sensor_data) {
- const sensor = currentBeneficiary.sensor_data;
- const sensorInfo: string[] = [];
-
- if (sensor.motion_detected !== undefined) {
- sensorInfo.push(`motion: ${sensor.motion_detected ? 'active' : 'inactive'}`);
- }
- if (sensor.last_motion) {
- sensorInfo.push(`last motion: ${sensor.last_motion}`);
- }
- if (sensor.door_status) {
- sensorInfo.push(`door: ${sensor.door_status}`);
- }
- if (sensor.temperature !== undefined) {
- sensorInfo.push(`temp: ${sensor.temperature}°C`);
- }
- if (sensor.humidity !== undefined) {
- sensorInfo.push(`humidity: ${sensor.humidity}%`);
- }
-
- if (sensorInfo.length > 0) {
- parts.push(`| Sensors: ${sensorInfo.join(', ')}`);
- }
+ // Current status
+ if (b.last_location) {
+ contextParts.push(`Current location: ${b.last_location}`);
}
- if (currentBeneficiary.last_activity) {
- parts.push(`| Last activity: ${currentBeneficiary.last_activity}`);
+ if (b.before_last_location) {
+ contextParts.push(`Previous location: ${b.before_last_location}`);
}
- parts.push(']');
+ // Health metrics
+ if (b.wellness_score !== undefined) {
+ contextParts.push(`Wellness score: ${b.wellness_score}% (${b.wellness_descriptor || 'N/A'})`);
+ }
- return parts.join(' ');
+ // Temperature
+ if (b.temperature !== undefined) {
+ const unit = b.units || '°F';
+ contextParts.push(`Room temperature: ${b.temperature.toFixed(1)}${unit}`);
+ }
+
+ if (b.bedroom_temperature !== undefined) {
+ const unit = b.units || '°F';
+ contextParts.push(`Bedroom temperature: ${b.bedroom_temperature.toFixed(1)}${unit}`);
+ }
+
+ // Sleep data
+ if (b.sleep_hours !== undefined) {
+ contextParts.push(`Sleep hours: ${b.sleep_hours.toFixed(1)} hours`);
+ }
+
+ // Activity time
+ if (b.last_detected_time) {
+ contextParts.push(`Last detected: ${b.last_detected_time}`);
+ }
+
+ // Status
+ contextParts.push(`Status: ${b.status === 'online' ? 'Active' : 'Inactive'}`);
+
+ return `[SENSOR DATA FOR ${b.name.toUpperCase()}: ${contextParts.join('. ')}]`;
}, [currentBeneficiary]);
return (
diff --git a/eas.json b/eas.json
index 40a8970..297be62 100644
--- a/eas.json
+++ b/eas.json
@@ -19,7 +19,7 @@
"production": {
"ios": {
"appleId": "serter2069@gmail.com",
- "ascAppId": "WILL_BE_SET_AFTER_FIRST_BUILD"
+ "ascAppId": "6755984871"
}
}
}
diff --git a/services/api.ts b/services/api.ts
index ac61606..7e29070 100644
--- a/services/api.ts
+++ b/services/api.ts
@@ -1,9 +1,23 @@
import * as SecureStore from 'expo-secure-store';
-import type { AuthResponse, ChatResponse, Beneficiary, ApiResponse, ApiError } from '@/types';
+import type { AuthResponse, ChatResponse, Beneficiary, ApiResponse, ApiError, DashboardSingleResponse, PatientDashboardData } from '@/types';
const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api';
const CLIENT_ID = 'MA_001';
+// Helper function to format time ago
+function formatTimeAgo(date: Date): string {
+ const now = new Date();
+ const diffMs = now.getTime() - date.getTime();
+ const diffMins = Math.floor(diffMs / 60000);
+ const diffHours = Math.floor(diffMins / 60);
+ const diffDays = Math.floor(diffHours / 24);
+
+ if (diffMins < 1) return 'Just now';
+ if (diffMins < 60) return `${diffMins} min ago`;
+ if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
+ return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
+}
+
class ApiService {
private async getToken(): Promise {
try {
@@ -175,6 +189,85 @@ class ApiService {
return { data: beneficiary, ok: true };
}
+ // Get patient dashboard data by deployment_id
+ async getPatientDashboard(deploymentId: string): Promise> {
+ const token = await this.getToken();
+ const userName = await this.getUserName();
+
+ if (!token || !userName) {
+ return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
+ }
+
+ const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
+
+ const response = await this.makeRequest({
+ function: 'dashboard_single',
+ user_name: userName,
+ token: token,
+ deployment_id: deploymentId,
+ date: today,
+ nonce: this.generateNonce(),
+ });
+
+ if (response.ok && response.data?.result_list?.[0]) {
+ return { data: response.data.result_list[0], ok: true };
+ }
+
+ return {
+ ok: false,
+ error: response.error || { message: 'Failed to get patient data' },
+ };
+ }
+
+ // Get all patients from privileges (deployment_ids)
+ async getAllPatients(): Promise> {
+ const token = await this.getToken();
+ if (!token) {
+ return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
+ }
+
+ const privileges = await SecureStore.getItemAsync('privileges');
+ if (!privileges) {
+ return { ok: true, data: [] };
+ }
+
+ const deploymentIds = privileges.split(',').map(id => id.trim()).filter(id => id);
+ const patients: Beneficiary[] = [];
+
+ // Fetch data for each deployment_id
+ for (const deploymentId of deploymentIds) {
+ const response = await this.getPatientDashboard(deploymentId);
+ if (response.ok && response.data) {
+ const data = response.data;
+ // Determine if patient is "online" based on last_detected_time
+ const lastDetected = data.last_detected_time ? new Date(data.last_detected_time) : null;
+ const isRecent = lastDetected && (Date.now() - lastDetected.getTime()) < 30 * 60 * 1000; // 30 min
+
+ patients.push({
+ id: parseInt(data.deployment_id, 10),
+ name: data.name,
+ status: isRecent ? 'online' : 'offline',
+ address: data.address,
+ timezone: data.time_zone,
+ wellness_score: data.wellness_score_percent,
+ wellness_descriptor: data.wellness_descriptor,
+ last_location: data.last_location,
+ temperature: data.temperature,
+ units: data.units,
+ sleep_hours: data.sleep_hours,
+ bedroom_temperature: data.bedroom_temperature,
+ before_last_location: data.before_last_location,
+ last_detected_time: data.last_detected_time,
+ last_activity: data.last_detected_time
+ ? formatTimeAgo(new Date(data.last_detected_time))
+ : undefined,
+ });
+ }
+ }
+
+ return { data: patients, ok: true };
+ }
+
// AI Chat
async sendMessage(question: string, deploymentId: string = '21'): Promise> {
const token = await this.getToken();
diff --git a/types/index.ts b/types/index.ts
index 193d8b9..42d5239 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -29,6 +29,44 @@ export interface Beneficiary {
relationship?: string;
last_activity?: string;
sensor_data?: SensorData;
+ // Extended data from dashboard_single API
+ address?: string;
+ timezone?: string;
+ wellness_score?: number;
+ wellness_descriptor?: string;
+ last_location?: string;
+ temperature?: number;
+ units?: string;
+ sleep_hours?: number;
+ bedroom_temperature?: number;
+ before_last_location?: string;
+ last_detected_time?: string;
+}
+
+// Dashboard API response
+export interface DashboardSingleResponse {
+ result_list: PatientDashboardData[];
+ status: string;
+}
+
+export interface PatientDashboardData {
+ user_id: number;
+ name: string;
+ address: string;
+ time_zone: string;
+ picture: string;
+ deployment_id: string;
+ wellness_score_percent: number;
+ wellness_descriptor: string;
+ wellness_descriptor_color: string;
+ last_location: string;
+ last_detected_time: string;
+ before_last_location: string;
+ temperature: number;
+ bedroom_temperature: number;
+ sleep_hours: number;
+ units: string;
+ location_list: string[];
}
export interface SensorData {