Sync all changes - profile restructure and scheme updates
- Restructured profile screens location - Updated beneficiary detail page - Updated API service - Updated all scheme files (MainScheme, ENV API, Discussion, AppStore, SysAnal) - Added PageHeader component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
48384f07c5
commit
abcc380984
@ -125,45 +125,47 @@ export default function BeneficiaryDetailScreen() {
|
||||
</View>
|
||||
|
||||
<Text style={styles.beneficiaryName}>{beneficiary.name}</Text>
|
||||
<Text style={styles.relationship}>{beneficiary.relationship}</Text>
|
||||
{beneficiary.address && (
|
||||
<Text style={styles.relationship}>{beneficiary.address}</Text>
|
||||
)}
|
||||
<Text style={styles.lastSeen}>
|
||||
Last activity: {beneficiary.last_activity}
|
||||
{beneficiary.last_location ? `📍 ${beneficiary.last_location}` : ''} • {beneficiary.last_activity || 'No recent activity'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Sensor Stats */}
|
||||
{/* Sensor Stats - using real API data */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Sensor Overview</Text>
|
||||
|
||||
<View style={styles.statsGrid}>
|
||||
<View style={styles.statCard}>
|
||||
<View style={[styles.statIcon, { backgroundColor: beneficiary.sensor_data?.motion_detected ? '#D1FAE5' : '#F3F4F6' }]}>
|
||||
<View style={[styles.statIcon, { backgroundColor: beneficiary.status === 'online' ? '#D1FAE5' : '#F3F4F6' }]}>
|
||||
<Ionicons
|
||||
name={beneficiary.sensor_data?.motion_detected ? "walk" : "walk-outline"}
|
||||
name={beneficiary.status === 'online' ? "walk" : "walk-outline"}
|
||||
size={24}
|
||||
color={beneficiary.sensor_data?.motion_detected ? AppColors.success : AppColors.textMuted}
|
||||
color={beneficiary.status === 'online' ? AppColors.success : AppColors.textMuted}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.statValue}>
|
||||
{beneficiary.sensor_data?.motion_detected ? 'Active' : 'Inactive'}
|
||||
{beneficiary.status === 'online' ? 'Active' : 'Inactive'}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Motion</Text>
|
||||
<Text style={styles.statUnit}>{beneficiary.sensor_data?.last_motion || '--'}</Text>
|
||||
<Text style={styles.statLabel}>Status</Text>
|
||||
<Text style={styles.statUnit}>{beneficiary.last_activity || '--'}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.statCard}>
|
||||
<View style={[styles.statIcon, { backgroundColor: beneficiary.sensor_data?.door_status === 'open' ? '#FEF3C7' : '#DBEAFE' }]}>
|
||||
<View style={[styles.statIcon, { backgroundColor: '#DBEAFE' }]}>
|
||||
<Ionicons
|
||||
name={beneficiary.sensor_data?.door_status === 'open' ? "enter-outline" : "home-outline"}
|
||||
name="location-outline"
|
||||
size={24}
|
||||
color={beneficiary.sensor_data?.door_status === 'open' ? AppColors.warning : AppColors.primary}
|
||||
color={AppColors.primary}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.statValue}>
|
||||
{beneficiary.sensor_data?.door_status === 'open' ? 'Open' : 'Closed'}
|
||||
<Text style={styles.statValue} numberOfLines={1}>
|
||||
{beneficiary.last_location || '--'}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Door Status</Text>
|
||||
<Text style={styles.statUnit}>Main entrance</Text>
|
||||
<Text style={styles.statLabel}>Location</Text>
|
||||
<Text style={styles.statUnit} numberOfLines={1}>{beneficiary.before_last_location || '--'}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.statCard}>
|
||||
@ -171,10 +173,48 @@ export default function BeneficiaryDetailScreen() {
|
||||
<Ionicons name="thermometer-outline" size={24} color={AppColors.primaryDark} />
|
||||
</View>
|
||||
<Text style={styles.statValue}>
|
||||
{beneficiary.sensor_data?.temperature || '--'}°C
|
||||
{beneficiary.temperature?.toFixed(1) || '--'}{beneficiary.units || '°F'}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Temperature</Text>
|
||||
<Text style={styles.statUnit}>{beneficiary.sensor_data?.humidity || '--'}% humidity</Text>
|
||||
<Text style={styles.statUnit}>Bedroom: {beneficiary.bedroom_temperature?.toFixed(1) || '--'}{beneficiary.units || '°F'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Additional stats row */}
|
||||
<View style={[styles.statsGrid, { marginTop: Spacing.md }]}>
|
||||
<View style={styles.statCard}>
|
||||
<View style={[styles.statIcon, { backgroundColor: '#F3E8FF' }]}>
|
||||
<Ionicons name="moon-outline" size={24} color="#9333EA" />
|
||||
</View>
|
||||
<Text style={styles.statValue}>
|
||||
{beneficiary.sleep_hours?.toFixed(1) || '--'}h
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Sleep</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.statCard}>
|
||||
<View style={[styles.statIcon, { backgroundColor: beneficiary.wellness_score && beneficiary.wellness_score >= 70 ? '#D1FAE5' : '#FEF3C7' }]}>
|
||||
<Ionicons
|
||||
name="heart-outline"
|
||||
size={24}
|
||||
color={beneficiary.wellness_score && beneficiary.wellness_score >= 70 ? AppColors.success : AppColors.warning}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.statValue}>
|
||||
{beneficiary.wellness_score || '--'}%
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Wellness</Text>
|
||||
<Text style={styles.statUnit}>{beneficiary.wellness_descriptor || '--'}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.statCard}>
|
||||
<View style={[styles.statIcon, { backgroundColor: '#DBEAFE' }]}>
|
||||
<Ionicons name="home-outline" size={24} color={AppColors.primary} />
|
||||
</View>
|
||||
<Text style={styles.statValue} numberOfLines={1}>
|
||||
{beneficiary.address?.split(',')[0] || '--'}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Address</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
6
app/(tabs)/profile/_layout.tsx
Normal file
6
app/(tabs)/profile/_layout.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import { Slot } from 'expo-router';
|
||||
|
||||
export default function ProfileLayout() {
|
||||
// Using Slot instead of Stack to keep tab bar visible
|
||||
return <Slot />;
|
||||
}
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface InfoRowProps {
|
||||
label: string;
|
||||
@ -48,14 +49,16 @@ export default function AboutScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="About WellNuo" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* App Logo & Name */}
|
||||
<View style={styles.heroSection}>
|
||||
<View style={styles.logoContainer}>
|
||||
<Ionicons name="heart" size={48} color={AppColors.white} />
|
||||
</View>
|
||||
<Text style={styles.appName}>WellNuo</Text>
|
||||
<Image
|
||||
source={require('@/assets/images/icon.png')}
|
||||
style={styles.logoImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<Text style={styles.appTagline}>Caring for Those Who Matter Most</Text>
|
||||
</View>
|
||||
|
||||
@ -156,32 +159,6 @@ export default function AboutScreen() {
|
||||
title="Follow on Twitter"
|
||||
onPress={() => openURL('https://twitter.com/wellnuo')}
|
||||
/>
|
||||
<View style={styles.linkDivider} />
|
||||
<LinkRow
|
||||
icon="logo-github"
|
||||
title="View on GitHub"
|
||||
onPress={() => openURL('https://github.com/wellnuo')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Acknowledgments */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Acknowledgments</Text>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.acknowledgment}>
|
||||
WellNuo uses the following open-source software:
|
||||
</Text>
|
||||
<Text style={styles.license}>• React Native (MIT License)</Text>
|
||||
<Text style={styles.license}>• Expo (MIT License)</Text>
|
||||
<Text style={styles.license}>• React Navigation (MIT License)</Text>
|
||||
<Text style={styles.license}>• And many other wonderful packages</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.viewLicenses}
|
||||
onPress={() => openURL('https://wellnuo.com/licenses')}
|
||||
>
|
||||
<Text style={styles.viewLicensesText}>View All Licenses</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@ -208,19 +185,10 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: Spacing.xl,
|
||||
backgroundColor: AppColors.background,
|
||||
},
|
||||
logoContainer: {
|
||||
width: 100,
|
||||
logoImage: {
|
||||
width: 180,
|
||||
height: 100,
|
||||
borderRadius: 24,
|
||||
backgroundColor: AppColors.primary,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
appName: {
|
||||
fontSize: 32,
|
||||
fontWeight: '700',
|
||||
color: AppColors.textPrimary,
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
appTagline: {
|
||||
fontSize: FontSizes.base,
|
||||
@ -313,28 +281,6 @@ const styles = StyleSheet.create({
|
||||
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,
|
||||
@ -14,6 +14,7 @@ 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 { PageHeader } from '@/components/PageHeader';
|
||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||
|
||||
export default function EditProfileScreen() {
|
||||
@ -58,7 +59,8 @@ export default function EditProfileScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Edit Profile" />
|
||||
<KeyboardAvoidingView
|
||||
style={styles.keyboardView}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface FAQItemProps {
|
||||
question: string;
|
||||
@ -111,7 +112,8 @@ export default function HelpScreen() {
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Help Center" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Search */}
|
||||
<View style={styles.searchSection}>
|
||||
@ -57,7 +57,6 @@ export default function ProfileScreen() {
|
||||
// Settings states
|
||||
const [pushNotifications, setPushNotifications] = useState(true);
|
||||
const [emailNotifications, setEmailNotifications] = useState(false);
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const [biometricLogin, setBiometricLogin] = useState(false);
|
||||
|
||||
const handleLogout = () => {
|
||||
@ -80,17 +79,17 @@ 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 handleEditProfile = () => router.push('/(tabs)/profile/edit');
|
||||
const handleNotifications = () => router.push('/(tabs)/profile/notifications');
|
||||
const handlePrivacy = () => router.push('/(tabs)/profile/privacy');
|
||||
const handleUpgrade = () => router.push('/(tabs)/profile/subscription');
|
||||
const handlePayment = () => router.push('/(tabs)/profile/subscription');
|
||||
const handleHelp = () => router.push('/(tabs)/profile/help');
|
||||
const handleSupport = () => router.push('/(tabs)/profile/support');
|
||||
const handleTerms = () => router.push('/(tabs)/profile/terms');
|
||||
const handlePrivacyPolicy = () => router.push('/(tabs)/profile/privacy-policy');
|
||||
const handleLanguage = () => router.push('/(tabs)/profile/language');
|
||||
const handleAbout = () => router.push('/(tabs)/profile/about');
|
||||
|
||||
const handleDevInfo = () => {
|
||||
Alert.alert(
|
||||
@ -221,27 +220,6 @@ export default function ProfileScreen() {
|
||||
}
|
||||
/>
|
||||
<View style={styles.menuDivider} />
|
||||
<MenuItem
|
||||
icon="moon-outline"
|
||||
iconBgColor="#E0E7FF"
|
||||
iconColor="#4F46E5"
|
||||
title="Dark Mode"
|
||||
subtitle="Coming soon"
|
||||
showChevron={false}
|
||||
rightElement={
|
||||
<Switch
|
||||
value={darkMode}
|
||||
onValueChange={(value) => {
|
||||
setDarkMode(value);
|
||||
Alert.alert('Dark Mode', 'Dark mode will be available in a future update!');
|
||||
setDarkMode(false);
|
||||
}}
|
||||
trackColor={{ false: '#E5E7EB', true: AppColors.primaryLight }}
|
||||
thumbColor={darkMode ? AppColors.primary : '#9CA3AF'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<View style={styles.menuDivider} />
|
||||
<MenuItem
|
||||
icon="finger-print"
|
||||
iconBgColor="#D1FAE5"
|
||||
@ -11,6 +11,7 @@ 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';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface LanguageOptionProps {
|
||||
code: string;
|
||||
@ -111,7 +112,8 @@ export default function LanguageScreen() {
|
||||
const comingSoonLanguages = languages.filter(l => !l.available);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Language" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Current Language */}
|
||||
<View style={styles.section}>
|
||||
@ -12,6 +12,7 @@ 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';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface NotificationSettingProps {
|
||||
icon: keyof typeof Ionicons.glyphMap;
|
||||
@ -89,7 +90,8 @@ export default function NotificationsScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Notifications" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Alert Types */}
|
||||
<View style={styles.section}>
|
||||
@ -8,6 +8,7 @@ import {
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface PrivacyHighlightProps {
|
||||
icon: keyof typeof Ionicons.glyphMap;
|
||||
@ -39,7 +40,8 @@ function PrivacyHighlight({
|
||||
|
||||
export default function PrivacyPolicyScreen() {
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Privacy Policy" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Highlights */}
|
||||
<View style={styles.highlightsSection}>
|
||||
@ -15,6 +15,7 @@ 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';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface SecurityItemProps {
|
||||
icon: keyof typeof Ionicons.glyphMap;
|
||||
@ -193,7 +194,8 @@ export default function PrivacyScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Privacy & Security" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Password & Authentication */}
|
||||
<View style={styles.section}>
|
||||
@ -11,6 +11,7 @@ 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';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
interface PlanFeatureProps {
|
||||
text: string;
|
||||
@ -64,7 +65,8 @@ export default function SubscriptionScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="WellNuo Pro" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Current Plan Badge */}
|
||||
<View style={styles.currentPlanBanner}>
|
||||
1068
app/(tabs)/profile/support.tsx
Normal file
1068
app/(tabs)/profile/support.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,12 @@ import {
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
|
||||
export default function TermsScreen() {
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<PageHeader title="Terms of Service" />
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.lastUpdated}>Last Updated: December 2024</Text>
|
||||
@ -1,81 +0,0 @@
|
||||
import { Stack } from 'expo-router';
|
||||
import { AppColors } from '@/constants/theme';
|
||||
|
||||
export default function ProfileLayout() {
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: {
|
||||
backgroundColor: AppColors.background,
|
||||
},
|
||||
headerTintColor: AppColors.primary,
|
||||
headerTitleStyle: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
headerBackTitleVisible: false,
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="edit"
|
||||
options={{
|
||||
title: 'Edit Profile',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="notifications"
|
||||
options={{
|
||||
title: 'Notifications',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="privacy"
|
||||
options={{
|
||||
title: 'Privacy & Security',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="help"
|
||||
options={{
|
||||
title: 'Help Center',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="support"
|
||||
options={{
|
||||
title: 'Contact Support',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="subscription"
|
||||
options={{
|
||||
title: 'WellNuo Pro',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="terms"
|
||||
options={{
|
||||
title: 'Terms of Service',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="privacy-policy"
|
||||
options={{
|
||||
title: 'Privacy Policy',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="about"
|
||||
options={{
|
||||
title: 'About WellNuo',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="language"
|
||||
options={{
|
||||
title: 'Language',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@ -1,475 +0,0 @@
|
||||
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 (
|
||||
<TouchableOpacity style={styles.contactMethod} onPress={onPress}>
|
||||
<View style={[styles.contactIcon, { backgroundColor: iconBgColor }]}>
|
||||
<Ionicons name={icon} size={24} color={iconColor} />
|
||||
</View>
|
||||
<View style={styles.contactContent}>
|
||||
<Text style={styles.contactTitle}>{title}</Text>
|
||||
<Text style={styles.contactSubtitle}>{subtitle}</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color={AppColors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<KeyboardAvoidingView
|
||||
style={styles.keyboardView}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* Quick Contact */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Quick Contact</Text>
|
||||
<View style={styles.card}>
|
||||
<ContactMethod
|
||||
icon="call"
|
||||
iconColor="#10B981"
|
||||
iconBgColor="#D1FAE5"
|
||||
title="Call Us"
|
||||
subtitle="+1 (555) 123-4567"
|
||||
onPress={handleCall}
|
||||
/>
|
||||
<View style={styles.divider} />
|
||||
<ContactMethod
|
||||
icon="mail"
|
||||
iconColor="#3B82F6"
|
||||
iconBgColor="#DBEAFE"
|
||||
title="Email Support"
|
||||
subtitle="support@wellnuo.com"
|
||||
onPress={handleEmail}
|
||||
/>
|
||||
<View style={styles.divider} />
|
||||
<ContactMethod
|
||||
icon="chatbubbles"
|
||||
iconColor="#8B5CF6"
|
||||
iconBgColor="#EDE9FE"
|
||||
title="Live Chat"
|
||||
subtitle="Available 24/7"
|
||||
onPress={handleChat}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Support Hours */}
|
||||
<View style={styles.section}>
|
||||
<View style={styles.hoursCard}>
|
||||
<Ionicons name="time" size={24} color={AppColors.primary} />
|
||||
<View style={styles.hoursContent}>
|
||||
<Text style={styles.hoursTitle}>Support Hours</Text>
|
||||
<Text style={styles.hoursText}>Phone: Mon-Fri 8am-8pm EST</Text>
|
||||
<Text style={styles.hoursText}>Email & Chat: 24/7</Text>
|
||||
<Text style={styles.hoursText}>Emergency: 24/7</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Submit a Ticket */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Submit a Ticket</Text>
|
||||
<View style={styles.formCard}>
|
||||
{/* Category */}
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Category *</Text>
|
||||
<View style={styles.categoryContainer}>
|
||||
{categories.map((cat) => (
|
||||
<TouchableOpacity
|
||||
key={cat}
|
||||
style={[
|
||||
styles.categoryChip,
|
||||
category === cat && styles.categoryChipSelected,
|
||||
]}
|
||||
onPress={() => setCategory(cat)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.categoryChipText,
|
||||
category === cat && styles.categoryChipTextSelected,
|
||||
]}
|
||||
>
|
||||
{cat}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Subject */}
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Subject *</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={subject}
|
||||
onChangeText={setSubject}
|
||||
placeholder="Brief description of your issue"
|
||||
placeholderTextColor={AppColors.textMuted}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Message */}
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Message *</Text>
|
||||
<TextInput
|
||||
style={[styles.input, styles.textArea]}
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Please describe your issue in detail. Include any error messages or steps to reproduce the problem."
|
||||
placeholderTextColor={AppColors.textMuted}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
textAlignVertical="top"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Attachments hint */}
|
||||
<View style={styles.attachmentHint}>
|
||||
<Ionicons name="attach" size={16} color={AppColors.textMuted} />
|
||||
<Text style={styles.attachmentHintText}>
|
||||
Need to attach screenshots? Reply to your ticket email.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Submit Button */}
|
||||
<TouchableOpacity
|
||||
style={[styles.submitButton, isSending && styles.submitButtonDisabled]}
|
||||
onPress={handleSendTicket}
|
||||
disabled={isSending}
|
||||
>
|
||||
<Text style={styles.submitButtonText}>
|
||||
{isSending ? 'Sending...' : 'Submit Ticket'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* FAQ Link */}
|
||||
<View style={styles.section}>
|
||||
<TouchableOpacity
|
||||
style={styles.faqLink}
|
||||
onPress={() => router.push('/profile/help')}
|
||||
>
|
||||
<View style={styles.faqLinkContent}>
|
||||
<Ionicons name="help-circle" size={24} color={AppColors.primary} />
|
||||
<View style={styles.faqLinkText}>
|
||||
<Text style={styles.faqLinkTitle}>Check our Help Center</Text>
|
||||
<Text style={styles.faqLinkSubtitle}>
|
||||
Find answers to common questions
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color={AppColors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Emergency Notice */}
|
||||
<View style={styles.emergencyNotice}>
|
||||
<Ionicons name="warning" size={20} color={AppColors.error} />
|
||||
<Text style={styles.emergencyText}>
|
||||
If you're experiencing a medical emergency, please call 911 or your local
|
||||
emergency services immediately.
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
66
components/PageHeader.tsx
Normal file
66
components/PageHeader.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { router } from 'expo-router';
|
||||
import { AppColors, FontSizes, Spacing } from '@/constants/theme';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
onBack?: () => void;
|
||||
rightElement?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PageHeader({ title, onBack, rightElement }: PageHeaderProps) {
|
||||
const handleBack = () => {
|
||||
if (onBack) {
|
||||
onBack();
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.backButton} onPress={handleBack}>
|
||||
<Ionicons name="chevron-back" size={28} color={AppColors.primary} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<View style={styles.rightContainer}>
|
||||
{rightElement || <View style={styles.placeholder} />}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: Spacing.sm,
|
||||
paddingVertical: Spacing.md,
|
||||
backgroundColor: AppColors.background,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: AppColors.border,
|
||||
},
|
||||
backButton: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
fontSize: FontSizes.lg,
|
||||
fontWeight: '600',
|
||||
color: AppColors.textPrimary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
rightContainer: {
|
||||
width: 44,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
placeholder: {
|
||||
width: 44,
|
||||
},
|
||||
});
|
||||
@ -4,6 +4,22 @@ import type { AuthResponse, ChatResponse, Beneficiary, ApiResponse, ApiError, Da
|
||||
const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api';
|
||||
const CLIENT_ID = 'MA_001';
|
||||
|
||||
// Avatar images for elderly beneficiaries (stable Unsplash URLs)
|
||||
const ELDERLY_AVATARS = [
|
||||
'https://images.unsplash.com/photo-1559839734-2b71ea197ec2?w=200&h=200&fit=crop&crop=face', // elderly woman 1
|
||||
'https://images.unsplash.com/photo-1581579438747-104c53d7fbc4?w=200&h=200&fit=crop&crop=face', // elderly man 1
|
||||
'https://images.unsplash.com/photo-1566616213894-2d4e1baee5d8?w=200&h=200&fit=crop&crop=face', // elderly woman 2
|
||||
'https://images.unsplash.com/photo-1552058544-f2b08422138a?w=200&h=200&fit=crop&crop=face', // elderly man 2
|
||||
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=200&h=200&fit=crop&crop=face', // elderly woman 3
|
||||
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop&crop=face', // elderly man 3
|
||||
];
|
||||
|
||||
// Get consistent avatar based on deployment_id
|
||||
function getAvatarForBeneficiary(deploymentId: number): string {
|
||||
const index = deploymentId % ELDERLY_AVATARS.length;
|
||||
return ELDERLY_AVATARS[index];
|
||||
}
|
||||
|
||||
// Helper function to format time ago
|
||||
function formatTimeAgo(date: Date): string {
|
||||
const now = new Date();
|
||||
@ -176,15 +192,39 @@ class ApiService {
|
||||
}
|
||||
|
||||
async getBeneficiary(id: number): Promise<ApiResponse<Beneficiary>> {
|
||||
const response = await this.getBeneficiaries();
|
||||
// Use real API data via getPatientDashboard
|
||||
const response = await this.getPatientDashboard(id.toString());
|
||||
|
||||
if (!response.ok || !response.data) {
|
||||
return { ok: false, error: response.error };
|
||||
return { ok: false, error: response.error || { message: 'Beneficiary not found', code: 'NOT_FOUND' } };
|
||||
}
|
||||
|
||||
const beneficiary = response.data.beneficiaries.find((b) => b.id === id);
|
||||
if (!beneficiary) {
|
||||
return { ok: false, error: { message: 'Beneficiary not found', code: 'NOT_FOUND' } };
|
||||
}
|
||||
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
|
||||
|
||||
const deploymentId = parseInt(data.deployment_id, 10);
|
||||
const beneficiary: Beneficiary = {
|
||||
id: deploymentId,
|
||||
name: data.name,
|
||||
avatar: getAvatarForBeneficiary(deploymentId),
|
||||
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: beneficiary, ok: true };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user