eletrotupi / tcc/ frontend/app/profile.tsx3f9b9d392996e8ee053477a03c7c3b6d4d72759a
7.0 KB | 276 lines Raw
1
import { useState, useEffect } from 'react';
2
import {
3
  View,
4
  StyleSheet,
5
  ScrollView,
6
  KeyboardAvoidingView,
7
} from 'react-native';
8

9
import { Text } from '@/components/ui/Text';
10
import { ThemedView } from '@/components/ui/themed-view'
11
import { Link, useLocalSearchParams, router } from 'expo-router';
12
import { StatusBar } from 'expo-status-bar';
13
import { SafeAreaView } from 'react-native-safe-area-context';
14
import { useThemeColor } from '@/hooks/use-theme-color';
15
import { Button, Input } from '@/components/ui';
16
import { useAuth } from '@/context/AuthContext';
17
import { apiClient, ApiError } from '@/lib/api'
18
import { useToast } from '@/context/ToastContext';
19
import { translateError, translateFields } from '@/lib/errors/translations';
20
import { Card } from '@/components/ui/Cards';
21
import AvatarUpload  from '@/components/ui/AvatarUpload';
22
import { Typography, Spacing, Colors } from '@/constants/theme';
23

24
export default function Profile() {
25
  const { user, updateAuthUser } = useAuth();
26
  const { showToast } = useToast();
27
  const [firstName, setFirstName] = useState(user.firstName);
28
  const [lastName, setLastName] = useState(user.lastName);
29
  const [email, setEmail] = useState(user.email);
30
  const [password, setPassword] = useState();
31
  const [confirmPassword, setConfirmPassword] = useState();
32
  const [loading, setLoading] = useState(false);
33
  const [errors, setErrors] = useState<{
34
    firstName?: string;
35
    password?: string;
36
    confirmPassword?: string;
37
  }>({});
38

39
  useEffect(() => {
40
    setFirstName(user.firstName)
41
    setLastName(user.lastName)
42
    setEmail(user.email)
43
  }, [user])
44

45
  const backgroundColor = useThemeColor({}, 'background');
46
  const textColor = useThemeColor({}, 'text');
47

48
  const validateForm = () => {
49
    const newErrors: {
50
      firstName?: string;
51
      email?: string;
52
      password?: string;
53
      confirmPassword?: string
54
    } = {};
55

56
    if (password) {
57
      if (!confirmPassword) {
58
        newErrors.confirmPassword = 'Precisa confirmar a senha';
59
      } else {
60
        if (password !== confirmPassword) {
61
          newErrors.confirmPassword = "Senhas não conferem";
62
        }
63
      }
64
    }
65

66
    if (!/\S+@\S+\.\S+/.test(email)) {
67
      newErrors.email = 'Por favor, use um email válido';
68
    }
69

70
    if (!firstName) {
71
      newErrors.firstName = 'É necessário ter um nome';
72
    }
73

74
    setErrors(newErrors);
75

76
    return Object.keys(newErrors).length === 0;
77
  }
78

79
  const handleUpdate = async () => {
80
    if (!validateForm()) return;
81

82
    const data = {};
83

84
    data.firstName = firstName
85
    data.lastName = lastName
86
    data.email = email
87

88
    if (password) {
89
      data.password = password
90
    }
91

92
    try {
93
      const id = user.id;
94

95
      const response = await apiClient.updateUser({
96
        id,
97
        firstName,
98
        lastName,
99
        email,
100
        password
101
      });
102

103
      updateAuthUser(response.user);
104

105
      router.replace('/(tabs)/insights');
106
    } catch (error: any) {
107
      console.error("[PROFILE]", error)
108
      if (error instanceof ApiError && error.fields) {
109
        setErrors(translateFields(error.fields));
110
      } else {
111
        showToast(translateError(error.message) || 'Falha ao atualizar o perfil', 'error');
112
      }
113
    }
114
  }
115

116
  const onChangeAvatar = (userData) => {
117
    const { id, avatarKey, avatarURL, firstName, lastName, email } = userData;
118

119
    updateAuthUser({
120
      id,
121
      avatarKey,
122
      avatarURL,
123
      firstName,
124
      lastName,
125
      email
126
    });
127
  }
128

129
  return (
130
    <SafeAreaView style={[styles.container, { backgroundColor }]}>
131
      <StatusBar style={'dark'} />
132
      <KeyboardAvoidingView
133
        behavior='height'
134
        style={styles.keyboardView}
135
      >
136
        <ScrollView
137
          contentContainerStyle={styles.scrollContainer}
138
          showsVerticalScrollIndicator={false}
139
        >
140
          <AvatarUpload
141
            currentAvatar={user.avatarURL}
142
            onUploadSuccess={onChangeAvatar}
143
            onRemoveSuccess={onChangeAvatar} />
144

145
          <View style={styles.form}>
146
            <Card style={styles.cardBlock}>
147
              <Input
148
                label="Primeiro Nome"
149
                type="text"
150
                value={firstName}
151
                onChangeText={setFirstName}
152
                placeholder="Qual seu nome?"
153
                variant="darkGhost"
154
                error={errors.firstName}
155
              />
156

157
              <Input
158
                label="Sobrenome"
159
                type="text"
160
                value={lastName}
161
                variant="darkGhost"
162
                onChangeText={setLastName}
163
                placeholder="Qual seu sobrenome?"
164
                error={errors.lastName}
165
              />
166
            </Card>
167

168
            <Card style={styles.cardBlock}>
169
              <Input
170
                label="Email"
171
                type="text"
172
                value={email}
173
                variant="darkGhost"
174
                onChangeText={setEmail}
175
                placeholder="Qual seu email?"
176
                error={errors.email}
177
              />
178
            </Card>
179

180
            <Card style={styles.cardBlock}>
181
              <Input
182
                label="Nova senha"
183
                type="password"
184
                value={password}
185
                onChangeText={setPassword}
186
                placeholder="Digite uma nova senha"
187
                error={errors.password}
188
                variant="darkGhost"
189
                showPasswordToggle
190
              />
191

192
              <Input
193
                label="Confirmar nova senha"
194
                type="password"
195
                value={confirmPassword}
196
                onChangeText={setConfirmPassword}
197
                placeholder="Confirme a nova senha"
198
                error={errors.confirmPassword}
199
                variant="darkGhost"
200
                showPasswordToggle
201
              />
202

203
              <Text style={styles.cardBlockHint}>
204
                Preencha apenas se quiser mudar a sua senha
205
              </Text>
206
            </Card>
207

208
            <Button
209
              title="Atualizar perfil"
210
              onPress={handleUpdate}
211
              loading={loading}
212
              style={styles.resetButton}
213
            />
214
          </View>
215

216
        </ScrollView>
217
      </KeyboardAvoidingView>
218
    </SafeAreaView>
219
  )
220
}
221

222
const styles = StyleSheet.create({
223
  container: {
224
    flex: 1,
225
  },
226
  keyboardView: {
227
    flex: 1,
228
  },
229
  scrollContainer: {
230
    flexGrow: 1,
231
    paddingHorizontal: 24,
232
    justifyContent: 'start',
233
    minHeight: '100%',
234
  },
235
  header: {
236
    alignItems: 'left',
237
    marginBottom: 40,
238
  },
239
  title: {
240
    fontSize: 28,
241
    fontWeight: 'bold',
242
    marginBottom: 8,
243
  },
244
  subtitle: {
245
    fontSize: 16,
246
    textAlign: 'left',
247
  },
248
  form: {
249
    marginBottom: 32,
250
  },
251
  resetButton: {
252
    marginTop: 8,
253
  },
254
  link: {
255
    textAlign: 'center',
256
    fontSize: 14,
257
    fontWeight: '500',
258
  },
259
  footer: {
260
    flexDirection: 'row',
261
    justifyContent: 'center',
262
    alignItems: 'center',
263
    marginTop: 'auto',
264
    paddingBottom: 20,
265
  },
266
  footerText: {
267
    fontSize: 14,
268
  },
269
  cardBlock: {
270
    padding: Spacing.cardGap
271
  },
272
  cardBlockHint: {
273
    ...Typography.cardHint,
274
    color: Colors.light.textSecondary
275
  }
276
});