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

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

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

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

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

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

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

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

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

75
    setErrors(newErrors);
76

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

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

83
    const data = {};
84

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

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

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

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

104
      updateAuthUser(response.user);
105

106
      router.replace('/(tabs)/insights');
107
    } catch (error: any) {
108
      console.error("[PROFILE]", error)
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
      }
114
    }
115
  }
116

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

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

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

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

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

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

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

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

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

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

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

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