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>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.beneficiaryName}>{beneficiary.name}</Text>
|
<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}>
|
<Text style={styles.lastSeen}>
|
||||||
Last activity: {beneficiary.last_activity}
|
{beneficiary.last_location ? `📍 ${beneficiary.last_location}` : ''} • {beneficiary.last_activity || 'No recent activity'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Sensor Stats */}
|
{/* Sensor Stats - using real API data */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Sensor Overview</Text>
|
<Text style={styles.sectionTitle}>Sensor Overview</Text>
|
||||||
|
|
||||||
<View style={styles.statsGrid}>
|
<View style={styles.statsGrid}>
|
||||||
<View style={styles.statCard}>
|
<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
|
<Ionicons
|
||||||
name={beneficiary.sensor_data?.motion_detected ? "walk" : "walk-outline"}
|
name={beneficiary.status === 'online' ? "walk" : "walk-outline"}
|
||||||
size={24}
|
size={24}
|
||||||
color={beneficiary.sensor_data?.motion_detected ? AppColors.success : AppColors.textMuted}
|
color={beneficiary.status === 'online' ? AppColors.success : AppColors.textMuted}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.statValue}>
|
<Text style={styles.statValue}>
|
||||||
{beneficiary.sensor_data?.motion_detected ? 'Active' : 'Inactive'}
|
{beneficiary.status === 'online' ? 'Active' : 'Inactive'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.statLabel}>Motion</Text>
|
<Text style={styles.statLabel}>Status</Text>
|
||||||
<Text style={styles.statUnit}>{beneficiary.sensor_data?.last_motion || '--'}</Text>
|
<Text style={styles.statUnit}>{beneficiary.last_activity || '--'}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.statCard}>
|
<View style={styles.statCard}>
|
||||||
<View style={[styles.statIcon, { backgroundColor: beneficiary.sensor_data?.door_status === 'open' ? '#FEF3C7' : '#DBEAFE' }]}>
|
<View style={[styles.statIcon, { backgroundColor: '#DBEAFE' }]}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name={beneficiary.sensor_data?.door_status === 'open' ? "enter-outline" : "home-outline"}
|
name="location-outline"
|
||||||
size={24}
|
size={24}
|
||||||
color={beneficiary.sensor_data?.door_status === 'open' ? AppColors.warning : AppColors.primary}
|
color={AppColors.primary}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.statValue}>
|
<Text style={styles.statValue} numberOfLines={1}>
|
||||||
{beneficiary.sensor_data?.door_status === 'open' ? 'Open' : 'Closed'}
|
{beneficiary.last_location || '--'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.statLabel}>Door Status</Text>
|
<Text style={styles.statLabel}>Location</Text>
|
||||||
<Text style={styles.statUnit}>Main entrance</Text>
|
<Text style={styles.statUnit} numberOfLines={1}>{beneficiary.before_last_location || '--'}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.statCard}>
|
<View style={styles.statCard}>
|
||||||
@ -171,10 +173,48 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
<Ionicons name="thermometer-outline" size={24} color={AppColors.primaryDark} />
|
<Ionicons name="thermometer-outline" size={24} color={AppColors.primaryDark} />
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.statValue}>
|
<Text style={styles.statValue}>
|
||||||
{beneficiary.sensor_data?.temperature || '--'}°C
|
{beneficiary.temperature?.toFixed(1) || '--'}{beneficiary.units || '°F'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.statLabel}>Temperature</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>
|
</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 { Ionicons } from '@expo/vector-icons';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface InfoRowProps {
|
interface InfoRowProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -48,14 +49,16 @@ export default function AboutScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="About WellNuo" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* App Logo & Name */}
|
{/* App Logo & Name */}
|
||||||
<View style={styles.heroSection}>
|
<View style={styles.heroSection}>
|
||||||
<View style={styles.logoContainer}>
|
<Image
|
||||||
<Ionicons name="heart" size={48} color={AppColors.white} />
|
source={require('@/assets/images/icon.png')}
|
||||||
</View>
|
style={styles.logoImage}
|
||||||
<Text style={styles.appName}>WellNuo</Text>
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
<Text style={styles.appTagline}>Caring for Those Who Matter Most</Text>
|
<Text style={styles.appTagline}>Caring for Those Who Matter Most</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -156,32 +159,6 @@ export default function AboutScreen() {
|
|||||||
title="Follow on Twitter"
|
title="Follow on Twitter"
|
||||||
onPress={() => openURL('https://twitter.com/wellnuo')}
|
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>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -208,19 +185,10 @@ const styles = StyleSheet.create({
|
|||||||
paddingVertical: Spacing.xl,
|
paddingVertical: Spacing.xl,
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: AppColors.background,
|
||||||
},
|
},
|
||||||
logoContainer: {
|
logoImage: {
|
||||||
width: 100,
|
width: 180,
|
||||||
height: 100,
|
height: 100,
|
||||||
borderRadius: 24,
|
marginBottom: Spacing.sm,
|
||||||
backgroundColor: AppColors.primary,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: Spacing.md,
|
|
||||||
},
|
|
||||||
appName: {
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: '700',
|
|
||||||
color: AppColors.textPrimary,
|
|
||||||
},
|
},
|
||||||
appTagline: {
|
appTagline: {
|
||||||
fontSize: FontSizes.base,
|
fontSize: FontSizes.base,
|
||||||
@ -313,28 +281,6 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: AppColors.border,
|
backgroundColor: AppColors.border,
|
||||||
marginLeft: Spacing.lg + 20 + Spacing.md,
|
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: {
|
footer: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingVertical: Spacing.xl,
|
paddingVertical: Spacing.xl,
|
||||||
@ -14,6 +14,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
|
||||||
export default function EditProfileScreen() {
|
export default function EditProfileScreen() {
|
||||||
@ -58,7 +59,8 @@ export default function EditProfileScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Edit Profile" />
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={styles.keyboardView}
|
style={styles.keyboardView}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface FAQItemProps {
|
interface FAQItemProps {
|
||||||
question: string;
|
question: string;
|
||||||
@ -111,7 +112,8 @@ export default function HelpScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Help Center" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<View style={styles.searchSection}>
|
<View style={styles.searchSection}>
|
||||||
@ -57,7 +57,6 @@ export default function ProfileScreen() {
|
|||||||
// Settings states
|
// Settings states
|
||||||
const [pushNotifications, setPushNotifications] = useState(true);
|
const [pushNotifications, setPushNotifications] = useState(true);
|
||||||
const [emailNotifications, setEmailNotifications] = useState(false);
|
const [emailNotifications, setEmailNotifications] = useState(false);
|
||||||
const [darkMode, setDarkMode] = useState(false);
|
|
||||||
const [biometricLogin, setBiometricLogin] = useState(false);
|
const [biometricLogin, setBiometricLogin] = useState(false);
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
@ -80,17 +79,17 @@ export default function ProfileScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Navigation handlers - now using actual page navigation
|
// Navigation handlers - now using actual page navigation
|
||||||
const handleEditProfile = () => router.push('/profile/edit');
|
const handleEditProfile = () => router.push('/(tabs)/profile/edit');
|
||||||
const handleNotifications = () => router.push('/profile/notifications');
|
const handleNotifications = () => router.push('/(tabs)/profile/notifications');
|
||||||
const handlePrivacy = () => router.push('/profile/privacy');
|
const handlePrivacy = () => router.push('/(tabs)/profile/privacy');
|
||||||
const handleUpgrade = () => router.push('/profile/subscription');
|
const handleUpgrade = () => router.push('/(tabs)/profile/subscription');
|
||||||
const handlePayment = () => router.push('/profile/subscription');
|
const handlePayment = () => router.push('/(tabs)/profile/subscription');
|
||||||
const handleHelp = () => router.push('/profile/help');
|
const handleHelp = () => router.push('/(tabs)/profile/help');
|
||||||
const handleSupport = () => router.push('/profile/support');
|
const handleSupport = () => router.push('/(tabs)/profile/support');
|
||||||
const handleTerms = () => router.push('/profile/terms');
|
const handleTerms = () => router.push('/(tabs)/profile/terms');
|
||||||
const handlePrivacyPolicy = () => router.push('/profile/privacy-policy');
|
const handlePrivacyPolicy = () => router.push('/(tabs)/profile/privacy-policy');
|
||||||
const handleLanguage = () => router.push('/profile/language');
|
const handleLanguage = () => router.push('/(tabs)/profile/language');
|
||||||
const handleAbout = () => router.push('/profile/about');
|
const handleAbout = () => router.push('/(tabs)/profile/about');
|
||||||
|
|
||||||
const handleDevInfo = () => {
|
const handleDevInfo = () => {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
@ -221,27 +220,6 @@ export default function ProfileScreen() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<View style={styles.menuDivider} />
|
<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
|
<MenuItem
|
||||||
icon="finger-print"
|
icon="finger-print"
|
||||||
iconBgColor="#D1FAE5"
|
iconBgColor="#D1FAE5"
|
||||||
@ -11,6 +11,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface LanguageOptionProps {
|
interface LanguageOptionProps {
|
||||||
code: string;
|
code: string;
|
||||||
@ -111,7 +112,8 @@ export default function LanguageScreen() {
|
|||||||
const comingSoonLanguages = languages.filter(l => !l.available);
|
const comingSoonLanguages = languages.filter(l => !l.available);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Language" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* Current Language */}
|
{/* Current Language */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
@ -12,6 +12,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface NotificationSettingProps {
|
interface NotificationSettingProps {
|
||||||
icon: keyof typeof Ionicons.glyphMap;
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
@ -89,7 +90,8 @@ export default function NotificationsScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Notifications" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* Alert Types */}
|
{/* Alert Types */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
@ -8,6 +8,7 @@ import {
|
|||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface PrivacyHighlightProps {
|
interface PrivacyHighlightProps {
|
||||||
icon: keyof typeof Ionicons.glyphMap;
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
@ -39,7 +40,8 @@ function PrivacyHighlight({
|
|||||||
|
|
||||||
export default function PrivacyPolicyScreen() {
|
export default function PrivacyPolicyScreen() {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Privacy Policy" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* Highlights */}
|
{/* Highlights */}
|
||||||
<View style={styles.highlightsSection}>
|
<View style={styles.highlightsSection}>
|
||||||
@ -15,6 +15,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
|||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface SecurityItemProps {
|
interface SecurityItemProps {
|
||||||
icon: keyof typeof Ionicons.glyphMap;
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
@ -193,7 +194,8 @@ export default function PrivacyScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Privacy & Security" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* Password & Authentication */}
|
{/* Password & Authentication */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
@ -11,6 +11,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
interface PlanFeatureProps {
|
interface PlanFeatureProps {
|
||||||
text: string;
|
text: string;
|
||||||
@ -64,7 +65,8 @@ export default function SubscriptionScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="WellNuo Pro" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
{/* Current Plan Badge */}
|
{/* Current Plan Badge */}
|
||||||
<View style={styles.currentPlanBanner}>
|
<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';
|
} from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
|
|
||||||
export default function TermsScreen() {
|
export default function TermsScreen() {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<PageHeader title="Terms of Service" />
|
||||||
<ScrollView showsVerticalScrollIndicator={false}>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<Text style={styles.lastUpdated}>Last Updated: December 2024</Text>
|
<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 API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api';
|
||||||
const CLIENT_ID = 'MA_001';
|
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
|
// Helper function to format time ago
|
||||||
function formatTimeAgo(date: Date): string {
|
function formatTimeAgo(date: Date): string {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@ -176,15 +192,39 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getBeneficiary(id: number): Promise<ApiResponse<Beneficiary>> {
|
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) {
|
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);
|
const data = response.data;
|
||||||
if (!beneficiary) {
|
// Determine if patient is "online" based on last_detected_time
|
||||||
return { ok: false, error: { message: 'Beneficiary not found', code: 'NOT_FOUND' } };
|
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 };
|
return { data: beneficiary, ok: true };
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user