Sergei 7cb07c09ce Major UI/UX updates: Voice, Subscription, Beneficiaries, Profile
- Voice tab: simplified interface, voice picker improvements
- Subscription: Stripe integration, purchase flow updates
- Beneficiaries: dashboard, sharing, improved management
- Profile: drawer, edit, help, privacy sections
- Theme: expanded constants, new colors
- New components: MockDashboard, ProfileDrawer, Toast
- Backend: Stripe routes additions
- Auth: activate, add-loved-one, purchase screens
2025-12-29 15:36:44 -08:00

375 lines
11 KiB
TypeScript

import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
Alert,
ActivityIndicator,
ScrollView,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';
import { router, useLocalSearchParams } from 'expo-router';
import { useBeneficiary } from '@/contexts/BeneficiaryContext';
import {
AppColors,
BorderRadius,
FontSizes,
FontWeights,
Spacing,
Shadows,
} from '@/constants/theme';
type Role = 'caretaker' | 'guardian';
export default function ShareAccessScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const { currentBeneficiary } = useBeneficiary();
const [email, setEmail] = useState('');
const [label, setLabel] = useState('');
const [role, setRole] = useState<Role>('caretaker');
const [isLoading, setIsLoading] = useState(false);
const beneficiaryName = currentBeneficiary?.name || 'this person';
const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const handleSendInvite = async () => {
const trimmedEmail = email.trim().toLowerCase();
const trimmedLabel = label.trim();
if (!trimmedEmail) {
Alert.alert('Email Required', 'Please enter an email address.');
return;
}
if (!validateEmail(trimmedEmail)) {
Alert.alert('Invalid Email', 'Please enter a valid email address.');
return;
}
setIsLoading(true);
try {
// TODO: Send invitation via API
// await api.sendInvitation({
// beneficiaryId: id,
// email: trimmedEmail,
// label: trimmedLabel,
// role: role,
// });
// For now, show success message
Alert.alert(
'Invitation Sent',
`An invitation has been sent to ${trimmedEmail} to ${role === 'guardian' ? 'manage' : 'view'} ${beneficiaryName}.`,
[{ text: 'OK', onPress: () => router.back() }]
);
} catch (error) {
console.error('Failed to send invitation:', error);
Alert.alert('Error', 'Failed to send invitation. Please try again.');
} finally {
setIsLoading(false);
}
};
return (
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
<Ionicons name="arrow-back" size={24} color={AppColors.textPrimary} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Share Access</Text>
<View style={styles.placeholder} />
</View>
<KeyboardAvoidingView
style={styles.content}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* Info Banner */}
<View style={styles.infoBanner}>
<Ionicons name="people" size={24} color={AppColors.primary} />
<Text style={styles.infoBannerText}>
Invite family members or caregivers to help monitor {beneficiaryName}
</Text>
</View>
{/* Email Input */}
<View style={styles.inputGroup}>
<Text style={styles.inputLabel}>Email Address</Text>
<View style={styles.inputContainer}>
<Ionicons name="mail-outline" size={20} color={AppColors.textMuted} />
<TextInput
style={styles.input}
placeholder="Enter email address"
placeholderTextColor={AppColors.textMuted}
value={email}
onChangeText={setEmail}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
editable={!isLoading}
/>
</View>
</View>
{/* Label Input */}
<View style={styles.inputGroup}>
<Text style={styles.inputLabel}>Label (optional)</Text>
<View style={styles.inputContainer}>
<Ionicons name="pricetag-outline" size={20} color={AppColors.textMuted} />
<TextInput
style={styles.input}
placeholder="e.g., Sister, Nurse, Neighbor"
placeholderTextColor={AppColors.textMuted}
value={label}
onChangeText={setLabel}
autoCapitalize="words"
editable={!isLoading}
/>
</View>
</View>
{/* Role Selection */}
<View style={styles.inputGroup}>
<Text style={styles.inputLabel}>Access Level</Text>
<TouchableOpacity
style={[styles.roleOption, role === 'caretaker' && styles.roleOptionSelected]}
onPress={() => setRole('caretaker')}
disabled={isLoading}
>
<View style={styles.roleHeader}>
<View style={[styles.roleIcon, role === 'caretaker' && styles.roleIconSelected]}>
<Ionicons
name="eye-outline"
size={20}
color={role === 'caretaker' ? AppColors.white : AppColors.primary}
/>
</View>
<View style={styles.roleInfo}>
<Text style={[styles.roleTitle, role === 'caretaker' && styles.roleTitleSelected]}>
Caretaker
</Text>
<Text style={styles.roleDescription}>
Can view activity and chat with Julia
</Text>
</View>
{role === 'caretaker' && (
<Ionicons name="checkmark-circle" size={24} color={AppColors.primary} />
)}
</View>
</TouchableOpacity>
<TouchableOpacity
style={[styles.roleOption, role === 'guardian' && styles.roleOptionSelected]}
onPress={() => setRole('guardian')}
disabled={isLoading}
>
<View style={styles.roleHeader}>
<View style={[styles.roleIcon, role === 'guardian' && styles.roleIconSelected]}>
<Ionicons
name="shield-outline"
size={20}
color={role === 'guardian' ? AppColors.white : AppColors.accent}
/>
</View>
<View style={styles.roleInfo}>
<Text style={[styles.roleTitle, role === 'guardian' && styles.roleTitleSelected]}>
Guardian
</Text>
<Text style={styles.roleDescription}>
Full access: edit info, manage subscription, invite others
</Text>
</View>
{role === 'guardian' && (
<Ionicons name="checkmark-circle" size={24} color={AppColors.accent} />
)}
</View>
</TouchableOpacity>
</View>
{/* Send Button */}
<TouchableOpacity
style={[styles.sendButton, isLoading && styles.sendButtonDisabled]}
onPress={handleSendInvite}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color={AppColors.white} />
) : (
<>
<Ionicons name="send" size={20} color={AppColors.white} />
<Text style={styles.sendButtonText}>Send Invitation</Text>
</>
)}
</TouchableOpacity>
{/* Help Text */}
<Text style={styles.helpText}>
The person will receive an email with instructions to access {beneficiaryName}'s information.
</Text>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.background,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: Spacing.md,
paddingVertical: Spacing.sm,
borderBottomWidth: 1,
borderBottomColor: AppColors.border,
},
backButton: {
padding: Spacing.xs,
},
headerTitle: {
fontSize: FontSizes.lg,
fontWeight: FontWeights.semibold,
color: AppColors.textPrimary,
},
placeholder: {
width: 32,
},
content: {
flex: 1,
},
scrollContent: {
padding: Spacing.lg,
paddingBottom: Spacing.xxl,
},
infoBanner: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: AppColors.primaryLighter,
padding: Spacing.md,
borderRadius: BorderRadius.lg,
marginBottom: Spacing.xl,
gap: Spacing.md,
},
infoBannerText: {
flex: 1,
fontSize: FontSizes.sm,
color: AppColors.textPrimary,
lineHeight: 20,
},
inputGroup: {
marginBottom: Spacing.lg,
},
inputLabel: {
fontSize: FontSizes.sm,
fontWeight: FontWeights.medium,
color: AppColors.textSecondary,
marginBottom: Spacing.sm,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.lg,
paddingHorizontal: Spacing.md,
borderWidth: 1,
borderColor: AppColors.border,
},
input: {
flex: 1,
paddingVertical: Spacing.md,
marginLeft: Spacing.sm,
fontSize: FontSizes.base,
color: AppColors.textPrimary,
},
roleOption: {
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.lg,
padding: Spacing.md,
marginBottom: Spacing.sm,
borderWidth: 2,
borderColor: AppColors.border,
},
roleOptionSelected: {
borderColor: AppColors.primary,
backgroundColor: AppColors.primaryLighter,
},
roleHeader: {
flexDirection: 'row',
alignItems: 'center',
},
roleIcon: {
width: 40,
height: 40,
borderRadius: BorderRadius.md,
backgroundColor: AppColors.surfaceSecondary,
justifyContent: 'center',
alignItems: 'center',
marginRight: Spacing.md,
},
roleIconSelected: {
backgroundColor: AppColors.primary,
},
roleInfo: {
flex: 1,
},
roleTitle: {
fontSize: FontSizes.base,
fontWeight: FontWeights.semibold,
color: AppColors.textPrimary,
},
roleTitleSelected: {
color: AppColors.primary,
},
roleDescription: {
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
marginTop: 2,
},
sendButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: AppColors.primary,
paddingVertical: Spacing.md,
borderRadius: BorderRadius.lg,
marginTop: Spacing.lg,
gap: Spacing.sm,
...Shadows.primary,
},
sendButtonDisabled: {
opacity: 0.7,
},
sendButtonText: {
fontSize: FontSizes.base,
fontWeight: FontWeights.semibold,
color: AppColors.white,
},
helpText: {
fontSize: FontSizes.sm,
color: AppColors.textMuted,
textAlign: 'center',
marginTop: Spacing.lg,
lineHeight: 20,
},
});