eletrotupi / tcc/ frontend/app/auth/signup.tsxc901c35af9c84b6a29e5185545ced1129f33b8f4
6.3 KB | 229 lines Raw
1
import React, { useState } from 'react';
2
import { Pressable, StyleSheet, View } from 'react-native';
3
import { Ionicons } from '@expo/vector-icons';
4
import { Link, router } from 'expo-router';
5
import { useThemeColor } from '@/hooks/use-theme-color';
6
import { Button, Input } from '@/components/ui';
7
import { Text } from '@/components/ui/Text';
8
import { AuthScreen } from '@/components/auth/AuthScreen';
9
import { useAuth } from '@/context/AuthContext';
10
import { ApiError } from '@/lib/api';
11
import { useToast } from '@/context/ToastContext';
12
import { translateError, translateFields } from '@/lib/errors/translations';
13

14
export default function SignupScreen() {
15
  const [firstName, setFirstName] = useState('');
16
  const [lastName, setLastName] = useState('');
17
  const [email, setEmail] = useState('');
18
  const [password, setPassword] = useState('');
19
  const [confirmPassword, setConfirmPassword] = useState('');
20
  const [acceptedTerms, setAcceptedTerms] = useState(false);
21
  const [errors, setErrors] = useState<{
22
    firstName?: string;
23
    email?: string;
24
    password?: string;
25
    confirmPassword?: string;
26
    acceptedTerms?: string;
27
  }>({});
28

29
  const [loading, setLoading] = useState(false);
30
  const { signup } = useAuth();
31
  const { showToast } = useToast();
32
  const textColor = useThemeColor({}, 'text');
33
  const tintColor = useThemeColor({}, 'tint');
34

35
  const validateForm = () => {
36
    const newErrors: typeof errors = {};
37

38
    if (!firstName.trim()) {
39
      newErrors.firstName = 'O nome é obrigatório';
40
    }
41

42
    if (!email) {
43
      newErrors.email = 'Email é obrigatório';
44
    } else if (!/\S+@\S+\.\S+/.test(email)) {
45
      newErrors.email = 'Por favor entre um email válido';
46
    }
47

48
    if (!password) {
49
      newErrors.password = 'Senha é obrigatório';
50
    } else if (password.length < 6) {
51
      newErrors.password = 'A senha deve conter pelo menos 6 caracteres';
52
    }
53

54
    if (!confirmPassword) {
55
      newErrors.confirmPassword = 'Por favor confirme sua senha';
56
    } else if (password !== confirmPassword) {
57
      newErrors.confirmPassword = 'As senhas não coincidem';
58
    }
59

60
    if (!acceptedTerms) {
61
      newErrors.acceptedTerms = 'Você precisa aceitar os termos para continuar';
62
    }
63

64
    setErrors(newErrors);
65
    return Object.keys(newErrors).length === 0;
66
  };
67

68
  const handleSignup = async () => {
69
    if (!validateForm()) return;
70

71
    setLoading(true);
72
    try {
73
      await signup({ firstName, lastName, email, password, acceptedTerms });
74
      router.replace('/');
75
    } catch (error) {
76
      if (error instanceof ApiError && error.fields) {
77
        setErrors(translateFields(error.fields));
78
      } else {
79
        showToast(translateError(error) || 'Falha no cadastro. Tente novamente.', 'error');
80
      }
81
    } finally {
82
      setLoading(false);
83
    }
84
  };
85

86
  return (
87
    <AuthScreen
88
      title="Criar conta"
89
      subtitle="Cadastre-se para começar a monitorar o seu humor."
90
      footer={
91
        <View style={styles.footer}>
92
          <Text style={[styles.footerText, { color: textColor, opacity: 0.7 }]}>
93
            Já tem uma conta?{' '}
94
          </Text>
95
          <Link href="/auth/login" asChild style={StyleSheet.flatten([styles.link, { color: tintColor }])}>
96
            <Text style={StyleSheet.flatten([styles.link, { color: tintColor }])}>
97
              Faça login
98
            </Text>
99
          </Link>
100
        </View>
101
      }
102
    >
103
      <View style={styles.form}>
104
        <Input
105
          label="Nome"
106
          value={firstName}
107
          onChangeText={setFirstName}
108
          placeholder="Digite seu nome"
109
          error={errors.firstName}
110
        />
111

112
        <Input
113
          label="Sobrenome (opcional)"
114
          value={lastName}
115
          onChangeText={setLastName}
116
          placeholder="Digite seu sobrenome"
117
        />
118

119
        <Input
120
          label="Email"
121
          type="email"
122
          value={email}
123
          onChangeText={setEmail}
124
          placeholder="Qual seu email?"
125
          error={errors.email}
126
          autoCapitalize="none"
127
        />
128

129
        <Input
130
          label="Senha"
131
          type="password"
132
          value={password}
133
          onChangeText={setPassword}
134
          placeholder="Deve ter no min. 6 caracteres"
135
          error={errors.password}
136
          showPasswordToggle
137
        />
138

139
        <Input
140
          label="Confirme a senha"
141
          type="password"
142
          value={confirmPassword}
143
          onChangeText={setConfirmPassword}
144
          placeholder="Confirme sua senha"
145
          error={errors.confirmPassword}
146
          showPasswordToggle
147
        />
148

149
        <View style={styles.termsRow}>
150
          <Pressable
151
            onPress={() => setAcceptedTerms((value) => !value)}
152
            hitSlop={8}
153
            accessibilityRole="checkbox"
154
            accessibilityState={{ checked: acceptedTerms }}
155
            testID="checkbox-terms"
156
          >
157
            <Ionicons
158
              name={acceptedTerms ? 'checkbox' : 'square-outline'}
159
              size={22}
160
              color={acceptedTerms ? tintColor : textColor}
161
            />
162
          </Pressable>
163

164
          <Text style={[styles.termsText, { color: textColor }]}>
165
            Li e aceito os{' '}
166
          </Text>
167

168
          <Link href="/terms" asChild style={StyleSheet.flatten([styles.termsLink, { color: tintColor }])}>
169
            <Text style={StyleSheet.flatten([styles.termsLink, { color: tintColor }])}>
170
              Termos e Condições
171
            </Text>
172
          </Link>
173
        </View>
174

175
        {errors.acceptedTerms && (
176
          <Text style={[styles.termsError, { color: '#ef4444' }]}>
177
            {errors.acceptedTerms}
178
          </Text>
179
        )}
180

181
        <Button
182
          title="Criar conta"
183
          onPress={handleSignup}
184
          loading={loading}
185
          style={styles.signupButton}
186
        />
187
      </View>
188
    </AuthScreen>
189
  );
190
}
191

192
const styles = StyleSheet.create({
193
  form: {
194
    marginBottom: 32,
195
  },
196
  signupButton: {
197
    marginTop: 8,
198
  },
199
  termsRow: {
200
    flexDirection: 'row',
201
    alignItems: 'center',
202
    flexWrap: 'wrap',
203
    marginBottom: 8,
204
  },
205
  termsText: {
206
    fontSize: 14,
207
  },
208
  termsLink: {
209
    fontSize: 14,
210
    fontWeight: '600',
211
  },
212
  termsError: {
213
    fontSize: 12,
214
    marginBottom: 8,
215
  },
216
  link: {
217
    textAlign: 'center',
218
    fontSize: 14,
219
    fontWeight: '500',
220
  },
221
  footer: {
222
    flexDirection: 'row',
223
    justifyContent: 'center',
224
    alignItems: 'center',
225
  },
226
  footerText: {
227
    fontSize: 14,
228
  },
229
});