frontend: apply new toast component for handling error messages
Parents:
fa665bd9 file(s) changed
- frontend/app/auth/activate.tsx +12 -16
- frontend/app/auth/forgot-password.tsx +6 -5
- frontend/app/auth/login.tsx +15 -6
- frontend/app/auth/reset-password.tsx +7 -10
- frontend/app/auth/signup.tsx +16 -12
- frontend/app/profile.tsx +9 -4
- frontend/app/sleep/new.tsx +14 -9
- frontend/app/triggers/new.tsx +29 -23
- frontend/components/ui/AvatarUpload.tsx +13 -4
frontend/app/auth/activate.tsx
1 1
import React, { useState } from 'react';
2 2
import {
3 3
View,
4 4
Text,
5 5
StyleSheet,
6 6
ScrollView,
7 7
KeyboardAvoidingView,
8 -
Platform,
9 -
Alert,
10 8
} from 'react-native';
11 9
import { Link, useLocalSearchParams, router } from 'expo-router';
12 10
import { SafeAreaView } from 'react-native-safe-area-context';
13 11
import { useThemeColor } from '@/hooks/use-theme-color';
14 12
import { Button, Input } from '@/components/ui';
15 13
import { Colors } from '@/constants/theme'
16 14
import { useAuth } from '@/context/AuthContext';
15 +
import { useToast } from '@/context/ToastContext';
16 +
import { translateError } from '@/lib/errors/translations';
17 17
18 18
export default function ActivateScreen() {
19 19
const { userId } = useLocalSearchParams();
20 20
const [code, setCode] = useState('');
21 21
const [errors, setErrors] = useState<{
22 22
code?: string;
23 23
}>({});
24 24
25 25
const { activate, requestActivateCode } = useAuth();
26 +
const { showToast } = useToast();
26 27
const [loading, setLoading] = useState(false);
27 28
const textColor = useThemeColor({}, 'text');
28 29
const backgroundColor = useThemeColor({}, 'background');
29 30
const tintColor = useThemeColor({}, 'tint');
30 31
▸ 15 unchanged lines
46 47
if (!validateForm()) return;
47 48
48 49
setLoading(true);
49 50
try {
50 51
await activate(Number(code.replaceAll(' ', '')));
51 -
52 -
Alert.alert('Sucesso!', 'Conta ativada com sucesso', [
53 -
{
54 -
text: 'OK',
55 -
onPress: () => router.replace('/'),
56 -
},
57 -
]);
52 +
showToast('Conta ativada com sucesso!', 'success');
53 +
router.replace('/');
58 54
} catch (error: any) {
59 -
Alert.alert('Erro: ', error.message || 'Falha em ativar a conta');
55 +
showToast(translateError(error.message) || 'Falha em ativar a conta', 'error');
60 56
} finally {
61 57
setLoading(false);
62 58
}
63 59
};
64 60
65 61
const resendCode = async () => {
66 -
await requestActivateCode()
67 -
68 -
Alert.alert(
69 -
"Novo código",
70 -
"Enviado novo código, verifique sua caixa de entrada"
71 -
);
62 +
try {
63 +
await requestActivateCode();
64 +
showToast('Novo código enviado. Verifique sua caixa de entrada.', 'success');
65 +
} catch {
66 +
showToast('Falha ao reenviar o código. Tente novamente.', 'error');
67 +
}
72 68
}
73 69
74 70
return (
75 71
<SafeAreaView style={[styles.container, { backgroundColor }]}>
76 72
<KeyboardAvoidingView
▸ 84 unchanged lines
161 157
footerText: {
162 158
textAlign: 'center',
163 159
marginBottom: 10
164 160
}
165 161
});
frontend/app/auth/forgot-password.tsx
1 1
import React, { useState } from 'react';
2 2
import {
3 3
View,
4 4
Text,
5 5
StyleSheet,
6 6
ScrollView,
7 7
KeyboardAvoidingView,
8 -
Platform,
9 -
Alert,
10 8
} from 'react-native';
11 9
import { Link, router } from 'expo-router';
12 10
import { SafeAreaView } from 'react-native-safe-area-context';
13 11
import { useThemeColor } from '@/hooks/use-theme-color';
14 12
import { Button, Input } from '@/components/ui';
15 13
import { useAuth } from '@/context/AuthContext';
14 +
import { useToast } from '@/context/ToastContext';
15 +
import { translateError } from '@/lib/errors/translations';
16 16
17 17
export default function ForgotPasswordScreen() {
18 18
const [email, setEmail] = useState('');
19 19
const [emailSent, setEmailSent] = useState(false);
20 20
const [resetToken, setResetToken] = useState(''); // Only for development
21 21
const [errors, setErrors] = useState<{ email?: string }>({});
22 22
23 23
const { forgotPassword } = useAuth();
24 +
const { showToast } = useToast();
24 25
const [loading, setLoading] = useState(false);
25 26
const textColor = useThemeColor({}, 'text');
26 27
const backgroundColor = useThemeColor({}, 'background');
27 28
const tintColor = useThemeColor({}, 'tint');
28 29
29 30
const validateForm = () => {
30 31
const newErrors: { email?: string } = {};
31 32
32 33
if (!email) {
33 -
newErrors.email = 'Email is required';
34 +
newErrors.email = 'Email é obrigatório';
34 35
} else if (!/\S+@\S+\.\S+/.test(email)) {
35 -
newErrors.email = 'Please enter a valid email';
36 +
newErrors.email = 'Por favor entre um email válido';
36 37
}
37 38
38 39
setErrors(newErrors);
39 40
return Object.keys(newErrors).length === 0;
40 41
};
▸ 8 unchanged lines
49 50
// In development, show the reset token
50 51
if (data.token) {
51 52
setResetToken(data.token);
52 53
}
53 54
} catch (error: any) {
54 -
Alert.alert('Erro: ', error.message || 'Falha ao enviar link de recuperação');
55 +
showToast(translateError(error.message) || 'Falha ao enviar link de recuperação', 'error');
55 56
} finally {
56 57
setLoading(false);
57 58
}
58 59
};
59 60
▸ 187 unchanged lines
247 248
},
248 249
footerText: {
249 250
fontSize: 14,
250 251
},
251 252
});
frontend/app/auth/login.tsx
1 1
import React, { useState, useEffect, useRef } from "react";
2 2
import {
3 3
View,
4 4
Text,
5 5
StyleSheet,
6 6
ScrollView,
7 7
KeyboardAvoidingView,
8 -
Platform,
9 -
Alert,
10 8
Animated,
11 9
Easing
12 10
} from "react-native";
13 11
import { Link, router } from "expo-router";
14 12
import { Image } from "react-native";
15 13
import { SafeAreaView } from "react-native-safe-area-context";
16 14
import { StatusBar } from "expo-status-bar";
17 15
import { useThemeColor } from "@/hooks/use-theme-color";
18 16
import { Button, Input } from "@/components/ui";
19 17
import { useAuth } from "@/context/AuthContext";
20 -
import { apiClient, User } from "@/lib/api";
18 +
import { apiClient, ApiError } from "@/lib/api";
19 +
import { useToast } from "@/context/ToastContext";
20 +
import { translateError, translateFields } from "@/lib/errors/translations";
21 21
import { registerForPushNotifications } from "@/hooks/useRegisterPushToken";
22 22
23 23
export default function LoginScreen() {
24 24
const [email, setEmail] = useState("");
25 25
const [password, setPassword] = useState("");
26 26
const [errors, setErrors] = useState<{ email?: string; password?: string }>(
27 27
{},
28 28
);
29 +
const [loading, setLoading] = useState(false);
29 30
30 -
const { login, updateAuthUser, isLoading } = useAuth();
31 +
const { login, updateAuthUser } = useAuth();
32 +
const { showToast } = useToast();
31 33
const textColor = useThemeColor({}, "text");
32 34
const backgroundColor = useThemeColor({}, "background");
33 35
const tintColor = useThemeColor({}, "tint");
34 36
35 37
const spinValue = useRef(new Animated.Value(0)).current;
▸ 37 unchanged lines
73 75
};
74 76
75 77
const handleLogin = async () => {
76 78
if (!validateForm()) return;
77 79
80 +
setLoading(true);
78 81
try {
79 82
const response = await login(email, password);
80 83
81 84
if (response?.user) {
82 85
const token = await registerForPushNotifications();
▸ 12 unchanged lines
95 98
}
96 99
97 100
router.replace("/");
98 101
} catch (error: any) {
99 102
console.log("LOGIN error: ", error);
100 -
Alert.alert("Erro: ", "Falha ao fazer login");
103 +
if (error instanceof ApiError && error.fields) {
104 +
setErrors(translateFields(error.fields));
105 +
} else {
106 +
showToast(translateError(error.message) || 'Falha ao fazer login', 'error');
107 +
}
108 +
} finally {
109 +
setLoading(false);
101 110
}
102 111
};
103 112
104 113
return (
105 114
<SafeAreaView style={[styles.container, { backgroundColor }]}>
▸ 44 unchanged lines
150 159
/>
151 160
152 161
<Button
153 162
title="Entrar"
154 163
onPress={handleLogin}
155 -
loading={isLoading}
164 +
loading={loading}
156 165
style={styles.loginButton}
157 166
/>
158 167
159 168
<Link
160 169
href="/auth/forgot-password"
▸ 89 unchanged lines
250 259
},
251 260
footerText: {
252 261
fontSize: 14,
253 262
},
254 263
});
frontend/app/auth/reset-password.tsx
1 1
import React, { useState } from 'react';
2 2
import {
3 3
View,
4 4
Text,
5 5
StyleSheet,
6 6
ScrollView,
7 7
KeyboardAvoidingView,
8 -
Platform,
9 -
Alert,
10 8
} from 'react-native';
11 9
import { Link, useLocalSearchParams, router } from 'expo-router';
12 10
import { SafeAreaView } from 'react-native-safe-area-context';
13 11
import { useThemeColor } from '@/hooks/use-theme-color';
14 12
import { Button, Input } from '@/components/ui';
15 13
import { useAuth } from '@/context/AuthContext';
14 +
import { useToast } from '@/context/ToastContext';
15 +
import { translateError } from '@/lib/errors/translations';
16 16
17 17
export default function ResetPasswordScreen() {
18 18
const { token } = useLocalSearchParams<{ token: string }>();
19 19
const [password, setPassword] = useState('');
20 20
const [confirmPassword, setConfirmPassword] = useState('');
▸ 1 unchanged lines
22 22
password?: string;
23 23
confirmPassword?: string;
24 24
}>({});
25 25
26 26
const { resetPassword } = useAuth();
27 +
const { showToast } = useToast();
27 28
const [loading, setLoading] = useState(false);
28 29
const textColor = useThemeColor({}, 'text');
29 30
const backgroundColor = useThemeColor({}, 'background');
30 31
const tintColor = useThemeColor({}, 'tint');
31 32
▸ 18 unchanged lines
50 51
51 52
const handleResetPassword = async () => {
52 53
if (!validateForm()) return;
53 54
54 55
if (!token) {
55 -
Alert.alert('Erro: ', 'Token de reset não encontrado. Por favor, solicite um novo link de recuperação.');
56 +
showToast('Token não encontrado. Solicite um novo link de recuperação.', 'error');
56 57
return;
57 58
}
58 59
59 60
setLoading(true);
60 61
try {
61 62
await resetPassword(token, password);
62 -
Alert.alert('Sucesso!', 'Senha recuperada com sucesso', [
63 -
{
64 -
text: 'OK',
65 -
onPress: () => router.replace('/auth/login'),
66 -
},
67 -
]);
63 +
showToast('Senha recuperada com sucesso!', 'success');
64 +
router.replace('/auth/login');
68 65
} catch (error: any) {
69 -
Alert.alert('Erro: ', error.message || 'Falha em recuperar a conta');
66 +
showToast(translateError(error.message) || 'Falha em recuperar a conta', 'error');
70 67
} finally {
71 68
setLoading(false);
72 69
}
73 70
};
74 71
▸ 105 unchanged lines
180 177
},
181 178
footerText: {
182 179
fontSize: 14,
183 180
},
184 181
});
frontend/app/auth/signup.tsx
1 1
import React, { useState } from 'react';
2 2
import {
3 3
View,
4 4
Text,
5 5
StyleSheet,
6 6
ScrollView,
7 7
KeyboardAvoidingView,
8 -
Platform,
9 -
Alert,
10 8
} from 'react-native';
11 9
import { Link, router } from 'expo-router';
12 10
import { SafeAreaView } from 'react-native-safe-area-context';
13 11
import { useThemeColor } from '@/hooks/use-theme-color';
14 12
import { Button, Input } from '@/components/ui';
15 13
import { useAuth } from '@/context/AuthContext';
14 +
import { ApiError } from '@/lib/api';
15 +
import { useToast } from '@/context/ToastContext';
16 +
import { translateError, translateFields } from '@/lib/errors/translations';
16 17
17 18
export default function SignupScreen() {
18 19
const [firstName, setFirstName] = useState('');
19 20
const [lastName, setLastName] = useState('');
20 21
const [email, setEmail] = useState('');
▸ 4 unchanged lines
25 26
email?: string;
26 27
password?: string;
27 28
confirmPassword?: string;
28 29
}>({});
29 30
30 -
const { signup, isLoading } = useAuth();
31 +
const [loading, setLoading] = useState(false);
32 +
const { signup } = useAuth();
33 +
const { showToast } = useToast();
31 34
const textColor = useThemeColor({}, 'text');
32 35
const backgroundColor = useThemeColor({}, 'background');
33 36
const tintColor = useThemeColor({}, 'tint');
34 37
35 38
const validateForm = () => {
▸ 26 unchanged lines
62 65
};
63 66
64 67
const handleSignup = async () => {
65 68
if (!validateForm()) return;
66 69
70 +
setLoading(true);
67 71
try {
68 -
await signup({
69 -
firstName,
70 -
lastName,
71 -
email,
72 -
password
73 -
});
74 -
72 +
await signup({ firstName, lastName, email, password });
75 73
router.replace('/');
76 74
} catch (error: any) {
77 -
Alert.alert('Erro:', error.message || 'Falha no cadastro. Tente novamente.');
75 +
if (error instanceof ApiError && error.fields) {
76 +
setErrors(translateFields(error.fields));
77 +
} else {
78 +
showToast(translateError(error.message) || 'Falha no cadastro. Tente novamente.', 'error');
79 +
}
80 +
} finally {
81 +
setLoading(false);
78 82
}
79 83
};
80 84
81 85
return (
82 86
<SafeAreaView style={[styles.container, { backgroundColor }]}>
▸ 59 unchanged lines
142 146
/>
143 147
144 148
<Button
145 149
title="Criar conta"
146 150
onPress={handleSignup}
147 -
loading={isLoading}
151 +
loading={loading}
148 152
style={styles.signupButton}
149 153
/>
150 154
</View>
151 155
152 156
<View style={styles.footer}>
▸ 58 unchanged lines
211 215
},
212 216
footerText: {
213 217
fontSize: 14,
214 218
},
215 219
});
frontend/app/profile.tsx
1 1
import { useState, useEffect } from 'react';
2 2
import {
3 3
View,
4 4
Text,
5 5
StyleSheet,
6 6
ScrollView,
7 7
KeyboardAvoidingView,
8 -
Platform,
9 -
Alert,
10 8
} from 'react-native';
11 9
12 10
import { ThemedView } from '@/components/ui/themed-view'
13 11
import { ThemedText } from '@/components/ui/themed-text'
14 12
import { Link, useLocalSearchParams, router } from 'expo-router';
15 13
import { StatusBar } from 'expo-status-bar';
16 14
import { SafeAreaView } from 'react-native-safe-area-context';
17 15
import { useThemeColor } from '@/hooks/use-theme-color';
18 16
import { Button, Input } from '@/components/ui';
19 17
import { useAuth } from '@/context/AuthContext';
20 -
import { apiClient } from '@/lib/api'
18 +
import { apiClient, ApiError } from '@/lib/api'
19 +
import { useToast } from '@/context/ToastContext';
20 +
import { translateError, translateFields } from '@/lib/errors/translations';
21 21
import { Card } from '@/components/ui/Cards';
22 22
import AvatarUpload from '@/components/ui/AvatarUpload';
23 23
import { Typography, Spacing, Colors } from '@/constants/theme';
24 24
25 25
export default function Profile() {
26 26
const { user, updateAuthUser } = useAuth();
27 +
const { showToast } = useToast();
27 28
const [firstName, setFirstName] = useState(user.firstName);
28 29
const [lastName, setLastName] = useState(user.lastName);
29 30
const [email, setEmail] = useState(user.email);
30 31
const [password, setPassword] = useState();
31 32
const [confirmPassword, setConfirmPassword] = useState();
▸ 71 unchanged lines
103 104
updateAuthUser(response.user);
104 105
105 106
router.replace('/(tabs)/insights');
106 107
} catch (error: any) {
107 108
console.error("[PROFILE]", error)
108 -
Alert.alert('Erro: ', 'Falha ao tentar atualizar o usuário');
109 +
if (error instanceof ApiError && error.fields) {
110 +
setErrors(translateFields(error.fields));
111 +
} else {
112 +
showToast(translateError(error.message) || 'Falha ao atualizar o perfil', 'error');
113 +
}
109 114
}
110 115
}
111 116
112 117
const onChangeAvatar = (userData) => {
113 118
const { id, avatarKey, avatarURL, firstName, lastName, email } = userData;
▸ 154 unchanged lines
268 273
cardBlockHint: {
269 274
...Typography.cardHint,
270 275
color: Colors.light.textSecondary
271 276
}
272 277
});
frontend/app/sleep/new.tsx
1 1
import {
2 2
View,
3 3
Text,
4 4
StyleSheet,
5 5
KeyboardAvoidingView,
▸ 8 unchanged lines
14 14
import { Spacing, Typography, Colors, BorderRadius } from "@/constants/theme";
15 15
import { Ionicons } from "@expo/vector-icons";
16 16
import { DatePickerField } from "@/components/ui/DatePickerField";
17 17
import { ScaleSlider } from "@/components/ui/ScaleSlider";
18 18
import { useCreateSleepRecord } from "@/hooks";
19 +
import { useToast } from "@/context/ToastContext";
20 +
import { ApiError } from "@/lib/api";
21 +
import { translateError } from "@/lib/errors/translations";
19 22
20 23
export default function NewSleepRecord() {
21 24
const [date, setDate] = useState(new Date());
22 25
const [annotations, setAnnotations] = useState("");
23 26
const [average, setAverage] = useState(7.5);
24 27
const createSleepRecord = useCreateSleepRecord();
28 +
const { showToast } = useToast();
25 29
26 30
const formatValue = (value: number): string => {
27 31
return String(value).replaceAll(".", ",");
28 32
};
29 33
30 34
const save = async () => {
31 -
const data = {
32 -
date,
33 -
annotations,
34 -
average,
35 -
};
36 -
37 -
console.log("Sleep record:", data);
38 -
await createSleepRecord.mutateAsync(data);
39 -
router.replace("/");
35 +
try {
36 +
const data = { date, annotations, average };
37 +
await createSleepRecord.mutateAsync(data);
38 +
router.replace("/");
39 +
} catch (error: any) {
40 +
showToast(
41 +
error instanceof ApiError ? translateError(error.message) : 'Falha ao salvar o registro de sono',
42 +
'error',
43 +
);
44 +
}
40 45
};
41 46
42 47
return (
43 48
<KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
44 49
<ScrollView scrollEventThrottle={16}>
▸ 97 unchanged lines
142 147
},
143 148
keyboardView: {
144 149
flex: 1,
145 150
},
146 151
});
frontend/app/triggers/new.tsx
1 1
import {
2 2
View,
3 3
Text,
4 4
StyleSheet,
5 5
KeyboardAvoidingView,
▸ 7 unchanged lines
13 13
import { Button } from "@/components/ui/Button";
14 14
import { Section, SectionHeader } from "@/components/ui/Sections";
15 15
import { Spacing, Typography, Colors, BorderRadius } from "@/constants/theme";
16 16
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
17 17
import { useCreateTrigger, useLinkMoodToTrigger, usePatchCareAction } from "@/hooks";
18 +
import { useToast } from "@/context/ToastContext";
19 +
import { ApiError } from "@/lib/api";
20 +
import { translateError } from "@/lib/errors/translations";
18 21
import { useCareActionLinkStore } from "@/stores";
19 22
import { CategoryChips, CategoryOption } from "@/components/ui/CategoryChips";
20 23
import { TriggerCategory } from "@/constants/triggers";
21 24
import { TRIGGER_CATEGORY_DEFINITIONS } from "@/constants/trigger-categories";
22 25
import { TimePicker } from "@/components/ui/TimePicker";
▸ 13 unchanged lines
36 39
37 40
const [category, setCategory] = useState(initialCategory ?? "WORK");
38 41
const createTrigger = useCreateTrigger();
39 42
const linkMoodToTrigger = useLinkMoodToTrigger();
40 43
const patchCareAction = usePatchCareAction();
44 +
const { showToast } = useToast();
41 45
42 46
const save = async () => {
43 -
const data = {
44 -
moment,
45 -
comment,
46 -
category
47 -
};
47 +
try {
48 +
const data = { moment, comment, category };
49 +
const result = await createTrigger.mutateAsync(data);
48 50
49 -
const result = await createTrigger.mutateAsync(data);
50 -
51 -
const careActionId = useCareActionLinkStore.getState().linkTrigger();
52 -
if (careActionId) {
53 -
await patchCareAction.mutateAsync({
54 -
id: String(careActionId),
55 -
data: { triggerId: result.trigger.id },
56 -
});
57 -
}
51 +
const careActionId = useCareActionLinkStore.getState().linkTrigger();
52 +
if (careActionId) {
53 +
await patchCareAction.mutateAsync({
54 +
id: String(careActionId),
55 +
data: { triggerId: result.trigger.id },
56 +
});
57 +
}
58 58
59 -
if (linkedMoodId) {
60 -
await linkMoodToTrigger.mutateAsync({
61 -
triggerId: String(result.trigger.id),
62 -
moodId: linkedMoodId,
63 -
perceivedImpact: 3,
64 -
});
65 -
router.back();
66 -
} else {
67 -
router.replace("/(tabs)/actions");
59 +
if (linkedMoodId) {
60 +
await linkMoodToTrigger.mutateAsync({
61 +
triggerId: String(result.trigger.id),
62 +
moodId: linkedMoodId,
63 +
perceivedImpact: 3,
64 +
});
65 +
router.back();
66 +
} else {
67 +
router.replace("/(tabs)/actions");
68 +
}
69 +
} catch (error: any) {
70 +
showToast(
71 +
error instanceof ApiError ? translateError(error.message) : 'Falha ao salvar o gatilho',
72 +
'error',
73 +
);
68 74
}
69 75
};
70 76
71 77
return (
72 78
<KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
▸ 107 unchanged lines
180 186
alignItems: "center",
181 187
justifyContent: "center",
182 188
marginTop: 1,
183 189
},
184 190
});
frontend/components/ui/AvatarUpload.tsx
1 1
import React, { useState, useRef } from 'react';
2 2
import {
3 3
View,
4 4
Image,
5 5
TouchableOpacity,
▸ 3 unchanged lines
9 9
Animated,
10 10
StyleSheet,
11 11
} from 'react-native';
12 12
import * as ImagePicker from 'expo-image-picker';
13 13
import { Ionicons } from '@expo/vector-icons';
14 -
import { apiClient } from '@/lib/api';
14 +
import { apiClient, ApiError } from '@/lib/api';
15 +
import { useToast } from '@/context/ToastContext';
16 +
import { translateError } from '@/lib/errors/translations';
15 17
import { Colors } from '@/constants/theme';
16 18
17 19
interface AvatarUploadProps {
18 20
currentAvatar?: string | null;
19 21
onUploadSuccess?: () => void;
▸ 4 unchanged lines
24 26
currentAvatar = null,
25 27
onUploadSuccess,
26 28
onRemoveSuccess,
27 29
}) => {
28 30
const [preview, setPreview] = useState<string | null>(currentAvatar);
31 +
const { showToast } = useToast();
29 32
const [isLoading, setIsLoading] = useState<boolean>(false);
30 33
const scaleAnim = useRef(new Animated.Value(1)).current;
31 34
32 35
const handleImagePick = async (useCamera: boolean = false): Promise<void> => {
33 36
try {
▸ 14 unchanged lines
48 51
setPreview(asset.uri);
49 52
await uploadAvatar(asset.fileName, asset.uri, asset.mimeType);
50 53
}
51 54
} catch (error) {
52 55
console.error('Image picker error:', error);
53 -
Alert.alert('Error', 'Failed to select image');
56 +
showToast('Falha ao selecionar imagem', 'error');
54 57
}
55 58
};
56 59
57 60
const uploadAvatar = async (fileName: string, imageUri: string, mimeType: string): Promise<void> => {
58 61
setIsLoading(true);
▸ 9 unchanged lines
68 71
const response = await apiClient.avatar(formData);
69 72
70 73
onUploadSuccess?.(response.user);
71 74
} catch (error) {
72 75
console.error('Upload error:', error);
73 -
Alert.alert('Error', error instanceof Error ? error.message : 'Upload failed');
76 +
showToast(
77 +
error instanceof ApiError ? translateError(error.message) : 'Falha ao enviar foto',
78 +
'error',
79 +
);
74 80
setPreview(currentAvatar);
75 81
} finally {
76 82
setIsLoading(false);
77 83
}
78 84
};
▸ 15 unchanged lines
94 100
95 101
setPreview(null);
96 102
onRemoveSuccess?.(response.user);
97 103
} catch (error) {
98 104
console.error('Remove error:', error);
99 -
Alert.alert('Error', error instanceof Error ? error.message : 'Failed to remove avatar');
105 +
showToast(
106 +
error instanceof ApiError ? translateError(error.message) : 'Falha ao remover foto',
107 +
'error',
108 +
);
100 109
} finally {
101 110
setIsLoading(false);
102 111
}
103 112
},
104 113
style: 'destructive',
▸ 187 unchanged lines
292 301
textTransform: 'uppercase',
293 302
},
294 303
});
295 304
296 305
export default AvatarUpload;