| import React, { useState, useEffect, useMemo } from 'react'; |
| import { View, StyleSheet } from 'react-native'; |
| import { |
| Text, |
| TextInput, |
| Button, |
| Avatar, |
| Surface, |
| SegmentedButtons, |
| useTheme, |
| Appbar, |
| } from 'react-native-paper'; |
| import { useNavigation } from '@react-navigation/native'; |
| import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; |
| import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; |
| import { useAuthStore } from '../../store/useAuthStore'; |
| import { useCurrentUser } from '../../hooks/useCurrentUser'; |
| import type { User, UserRole } from '../../types/user'; |
|
|
| |
|
|
| type RootStackParamList = { |
| ProfileSetup: undefined; |
| Main: undefined; |
| }; |
|
|
| type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'ProfileSetup'>; |
|
|
| |
|
|
| export default function ProfileSetupScreen() { |
| const theme = useTheme(); |
| const navigation = useNavigation<NavigationProp>(); |
| const { user, setAuth, setOnboarded, updateUser } = useAuthStore(); |
| const { updateProfile } = useCurrentUser(); |
|
|
| const [displayName, setDisplayName] = useState(user?.displayName ?? ''); |
| const [phone, setPhone] = useState(user?.phone ?? ''); |
| const [selectedRole, setSelectedRole] = useState<UserRole>('rider'); |
| const [saving, setSaving] = useState(false); |
|
|
| |
| useEffect(() => { |
| if (user?.displayName) { |
| setDisplayName(user.displayName); |
| } |
| }, [user?.displayName]); |
|
|
| |
| const initials = useMemo(() => { |
| if (!displayName.trim()) return '?'; |
| return displayName |
| .trim() |
| .split(' ') |
| .map((word) => word[0]) |
| .join('') |
| .toUpperCase() |
| .slice(0, 2); |
| }, [displayName]); |
|
|
| |
|
|
| const handleSubmit = async () => { |
| if (!displayName.trim()) return; |
|
|
| setSaving(true); |
| try { |
| |
| await updateProfile({ |
| fullName: displayName.trim(), |
| phone: phone.trim() || null, |
| }); |
|
|
| |
| updateUser({ |
| displayName: displayName.trim(), |
| phone: phone.trim() || null, |
| currentRole: selectedRole, |
| }); |
|
|
| |
| setOnboarded(true); |
|
|
| |
| navigation.replace('Main'); |
| } catch (error) { |
| console.error('[ProfileSetupScreen] Failed to save profile:', error); |
| } finally { |
| setSaving(false); |
| } |
| }; |
|
|
| |
|
|
| return ( |
| <Surface style={styles.container}> |
| {/* Appbar */} |
| <Appbar.Header |
| style={{ backgroundColor: theme.colors.surface }} |
| elevation={0} |
| > |
| <Appbar.BackAction |
| onPress={() => navigation.goBack()} |
| color={theme.colors.onSurface} |
| /> |
| <Appbar.Content |
| title="Complete Your Profile" |
| titleStyle={{ color: theme.colors.onSurface, fontWeight: '600' }} |
| /> |
| </Appbar.Header> |
| |
| <View style={styles.content}> |
| {/* Avatar */} |
| <View style={styles.avatarContainer}> |
| <Avatar.Text |
| size={96} |
| label={initials} |
| style={[ |
| styles.avatar, |
| { backgroundColor: theme.colors.primaryContainer }, |
| ]} |
| labelStyle={[ |
| styles.avatarLabel, |
| { color: theme.colors.onPrimaryContainer }, |
| ]} |
| /> |
| <Text |
| variant="bodySmall" |
| style={[styles.tapToChange, { color: theme.colors.primary }]} |
| > |
| Tap to change |
| </Text> |
| </View> |
| |
| {/* Display Name */} |
| <TextInput |
| mode="outlined" |
| label="Display Name" |
| value={displayName} |
| onChangeText={setDisplayName} |
| placeholder="Enter your name" |
| style={styles.input} |
| outlineColor={theme.colors.outline} |
| activeOutlineColor={theme.colors.primary} |
| /> |
| |
| {/* Phone */} |
| <TextInput |
| mode="outlined" |
| label="Phone Number" |
| value={phone} |
| onChangeText={(text) => { |
| // Allow only digits after +91 prefix |
| const cleaned = text.replace(/[^0-9+]/g, ''); |
| if (!cleaned.startsWith('+91')) { |
| setPhone(cleaned ? `+91${cleaned.replace('+91', '')}` : ''); |
| } else { |
| setPhone(cleaned); |
| } |
| }} |
| placeholder="+91" |
| keyboardType="phone-pad" |
| maxLength={15} |
| style={styles.input} |
| outlineColor={theme.colors.outline} |
| activeOutlineColor={theme.colors.primary} |
| left={<TextInput.Affix text="+91" textStyle={{ color: theme.colors.onSurfaceVariant }} />} |
| /> |
| |
| {/* Role Selection */} |
| <Text |
| variant="titleSmall" |
| style={[styles.roleLabel, { color: theme.colors.onSurface }]} |
| > |
| How will you use Antaram? |
| </Text> |
| |
| <SegmentedButtons |
| value={selectedRole} |
| onValueChange={(value) => setSelectedRole(value as UserRole)} |
| buttons={[ |
| { |
| value: 'rider', |
| label: 'π Rider', |
| icon: () => ( |
| <MaterialCommunityIcons |
| name="steering" |
| size={18} |
| color={ |
| selectedRole === 'rider' |
| ? theme.colors.onPrimary |
| : theme.colors.onSurface |
| } |
| /> |
| ), |
| }, |
| { |
| value: 'passenger', |
| label: 'π§ Passenger', |
| icon: () => ( |
| <MaterialCommunityIcons |
| name="account-walk" |
| size={18} |
| color={ |
| selectedRole === 'passenger' |
| ? theme.colors.onPrimary |
| : theme.colors.onSurface |
| } |
| /> |
| ), |
| }, |
| ]} |
| style={styles.segmentedButtons} |
| /> |
| |
| {/* Submit */} |
| <Button |
| mode="contained" |
| onPress={handleSubmit} |
| loading={saving} |
| disabled={!displayName.trim() || saving} |
| style={styles.submitButton} |
| buttonColor={theme.colors.primary} |
| textColor={theme.colors.onPrimary} |
| contentStyle={styles.submitButtonContent} |
| > |
| Let's Go! |
| </Button> |
| </View> |
| </Surface> |
| ); |
| } |
|
|
| |
|
|
| const styles = StyleSheet.create({ |
| container: { |
| flex: 1, |
| }, |
| content: { |
| flex: 1, |
| paddingHorizontal: 24, |
| paddingTop: 16, |
| paddingBottom: 32, |
| }, |
| avatarContainer: { |
| alignItems: 'center', |
| marginBottom: 32, |
| }, |
| avatar: { |
| elevation: 2, |
| shadowColor: '#000', |
| shadowOffset: { width: 0, height: 2 }, |
| shadowOpacity: 0.1, |
| shadowRadius: 4, |
| }, |
| avatarLabel: { |
| fontWeight: '600', |
| }, |
| tapToChange: { |
| marginTop: 8, |
| }, |
| input: { |
| marginBottom: 16, |
| }, |
| roleLabel: { |
| marginBottom: 12, |
| fontWeight: '600', |
| }, |
| segmentedButtons: { |
| marginBottom: 32, |
| }, |
| submitButton: { |
| marginTop: 'auto', |
| }, |
| submitButtonContent: { |
| paddingVertical: 6, |
| }, |
| }); |
|
|