Files
casadoc-app/app/profile/edit.tsx
T
2026-08-26 23:01:07 +03:00

173 lines
5.8 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { Alert, KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Image } from 'react-native';
import { useRouter } from 'expo-router';
import { useMutation } from '@tanstack/react-query';
import { Ionicons } from '@expo/vector-icons';
import { api, LocalFile } from '@/api';
import { useAuth } from '@/auth/AuthContext';
import { useT } from '@/i18n';
import { AppBar } from '@/components/AppBar';
import { Avatar, Button, Field, TextField } from '@/components/ui';
import { pickImageFromLibrary, takePhotoWithCamera } from '@/lib/pickImage';
import { colors, fontSize, spacing } from '@/theme';
export default function EditProfileScreen() {
const { user, applyUser } = useAuth();
const router = useRouter();
const t = useT();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [phone, setPhone] = useState('');
const [address, setAddress] = useState('');
const [avatar, setAvatar] = useState<LocalFile | null>(null);
useEffect(() => {
if (!user) return;
setFirstName(user.firstName ?? '');
setLastName(user.lastName ?? '');
setPhone(user.phone ?? '');
setAddress(user.address ?? '');
}, [user]);
const save = useMutation({
mutationFn: async () => {
const updated = await api.auth.updateProfile({
firstName: firstName.trim() || null,
lastName: lastName.trim() || null,
phone: phone.trim() || null,
address: address.trim() || null,
});
// The avatar endpoint answers last, so its user object is the newer one.
if (avatar) {
try {
return await api.auth.uploadAvatar(avatar);
} catch {
Alert.alert(t('addProperty.photoFailed'));
}
}
return updated;
},
onSuccess: (updated) => {
applyUser(updated);
router.back();
},
onError: (e: any) => Alert.alert(t('editProfile.failed'), e?.message ?? t('common.tryAgain')),
});
function pickAvatar() {
Alert.alert(t('editProfile.photo'), undefined, [
{
text: t('addProperty.takePhoto'),
onPress: async () => {
const picked = await takePhotoWithCamera({ allowsEditing: true, aspect: [1, 1], fallbackName: 'avatar' });
if (picked) setAvatar(picked);
},
},
{
text: t('addProperty.fromGallery'),
onPress: async () => {
const picked = await pickImageFromLibrary({ allowsEditing: true, aspect: [1, 1], fallbackName: 'avatar' });
if (picked) setAvatar(picked);
},
},
{ text: t('common.cancel'), style: 'cancel' },
]);
}
const initials = ((firstName[0] ?? '') + (lastName[0] ?? '')).toUpperCase() || 'U';
const avatarUri = avatar?.uri ?? user?.avatar ?? null;
return (
<KeyboardAvoidingView
style={{ flex: 1, backgroundColor: colors.bgApp }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<AppBar title={t('editProfile.title')} back />
<ScrollView contentContainerStyle={styles.body} keyboardShouldPersistTaps="handled">
<Pressable style={styles.avatarWrap} onPress={pickAvatar}>
{avatarUri ? (
<Image source={{ uri: avatarUri }} style={styles.avatarImage} />
) : (
<Avatar initials={initials} size={92} />
)}
<View style={styles.avatarBadge}>
<Ionicons name="camera" size={14} color={colors.onDark} />
</View>
</Pressable>
<Text style={styles.avatarHint}>{t('editProfile.photo')}</Text>
<View style={{ flexDirection: 'row', gap: spacing[3] }}>
<Field label={t('register.firstName')} style={{ flex: 1 }}>
<TextField value={firstName} onChangeText={setFirstName} autoCapitalize="words" />
</Field>
<Field label={t('register.lastName')} style={{ flex: 1 }}>
<TextField value={lastName} onChangeText={setLastName} autoCapitalize="words" />
</Field>
</View>
<Field label={t('login.email')}>
<TextField value={user?.email ?? ''} editable={false} style={{ color: colors.textMuted }} />
<Text style={styles.hint}>{t('editProfile.emailReadOnly')}</Text>
</Field>
<Field label={t('editProfile.phone')} optional>
<TextField
icon="call-outline"
value={phone}
onChangeText={setPhone}
keyboardType="phone-pad"
textContentType="telephoneNumber"
/>
</Field>
<Field label={t('editProfile.address')} optional>
<TextField
icon="location-outline"
value={address}
onChangeText={setAddress}
autoCapitalize="words"
/>
</Field>
<Button
title={t('editProfile.save')}
icon="checkmark"
size="lg"
block
loading={save.isPending}
onPress={() => save.mutate()}
/>
</ScrollView>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
body: { padding: spacing[4], paddingBottom: spacing[9] },
avatarWrap: { alignSelf: 'center', marginBottom: spacing[2] },
avatarImage: { width: 92, height: 92, borderRadius: 46, backgroundColor: colors.surfaceSunken },
avatarBadge: {
position: 'absolute',
right: -2,
bottom: -2,
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: colors.teal600,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 2,
borderColor: colors.bgApp,
},
avatarHint: {
textAlign: 'center',
fontSize: fontSize.sm,
fontWeight: '600',
color: colors.teal700,
marginBottom: spacing[6],
},
hint: { fontSize: fontSize.xs, color: colors.textMuted, marginTop: 6 },
});