eletrotupi / tcc/ commit / 78d5ebc

frontend: apply new toast component for handling error messages

Pedro Lucas Porcellis porcellis@eletrotupi.com 29 days ago 78d5ebcc3dc2473e6df0087149adf8877b22f187
Parents: fa665bd
9 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;