eletrotupi / tcc/ frontend/app/auth/login.tsxcacc7caae1c71809cb9f2e1662f9eac1b0406113
6.8 KB | 263 lines Raw
1
import React, { useState, useEffect, useRef } from "react";
2
import {
3
  View,
4
  StyleSheet,
5
  ScrollView,
6
  KeyboardAvoidingView,
7
  Animated,
8
  Easing
9
} from "react-native";
10
import { Text } from "@/components/ui/Text";
11
import { Link, router } from "expo-router";
12
import { Image } from "react-native";
13
import { SafeAreaView } from "react-native-safe-area-context";
14
import { StatusBar } from "expo-status-bar";
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 { registerForPushNotifications } from "@/hooks/useRegisterPushToken";
22

23
export default function LoginScreen() {
24
  const [email, setEmail] = useState("");
25
  const [password, setPassword] = useState("");
26
  const [errors, setErrors] = useState<{ email?: string; password?: string }>(
27
    {},
28
  );
29
  const [loading, setLoading] = useState(false);
30

31
  const { login, updateAuthUser } = useAuth();
32
  const { showToast } = useToast();
33
  const textColor = useThemeColor({}, "text");
34
  const backgroundColor = useThemeColor({}, "background");
35
  const tintColor = useThemeColor({}, "tint");
36

37
  const spinValue = useRef(new Animated.Value(0)).current;
38
  useEffect(() => {
39
    const animation = Animated.loop(
40
      Animated.timing(spinValue, {
41
        toValue: 1,
42
        duration: 12000, // 12s per full turn (slow)
43
        easing: Easing.linear,
44
        useNativeDriver: true,
45
      })
46
    );
47

48
    animation.start();
49

50
    return () => {
51
      animation.stop();
52
    };
53
  }, [spinValue]);
54

55
  const spin = spinValue.interpolate({
56
    inputRange: [0, 1],
57
    outputRange: ["0deg", "360deg"],
58
  });
59

60
  const validateForm = () => {
61
    const newErrors: { email?: string; password?: string } = {};
62

63
    if (!email) {
64
      newErrors.email = "Email é obrigatório";
65
    } else if (!/\S+@\S+\.\S+/.test(email)) {
66
      newErrors.email = "Por favor, use um email válido";
67
    }
68

69
    if (!password) {
70
      newErrors.password = "Senha é obrigatória";
71
    }
72

73
    setErrors(newErrors);
74
    return Object.keys(newErrors).length === 0;
75
  };
76

77
  const handleLogin = async () => {
78
    if (!validateForm()) return;
79

80
    setLoading(true);
81
    try {
82
      const response = await login(email, password);
83

84
      if (response?.user) {
85
        const token = await registerForPushNotifications();
86
        if (token) {
87
          const updateResponse = await apiClient.updateUser({
88
            id: response.user.id,
89
            pushToken: token,
90
          });
91

92
          console.log("UPDATE USER response: ", updateResponse);
93

94
          if (updateResponse) {
95
            updateAuthUser(updateResponse.user);
96
          }
97
        }
98
      }
99

100
      router.replace("/");
101
    } catch (error: any) {
102
      console.log("LOGIN error: ", error);
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);
110
    }
111
  };
112

113
  return (
114
    <SafeAreaView style={[styles.container, { backgroundColor }]}>
115
      <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
116
        <StatusBar style={'dark'} />
117
        <ScrollView
118
          contentContainerStyle={styles.scrollContainer}
119
          showsVerticalScrollIndicator={false}
120
        >
121
          <View style={styles.logoContainer}>
122
            <Animated.View style={{ transform: [{ rotate: spin }] }}>
123
              <Image
124
                source={require("@/assets/images/logo.png")}
125
                style={styles.logo}
126
                resizeMode="contain"
127
              />
128
            </Animated.View>
129
          </View>
130

131
          <View style={styles.header}>
132
            <Text style={[styles.title, { color: textColor }]}>
133
              Bem vindo de volta
134
            </Text>
135
            <Text style={[styles.subtitle, { color: textColor, opacity: 0.7 }]}>
136
              Logue na sua conta nexo
137
            </Text>
138
          </View>
139

140
          <View style={styles.form}>
141
            <Input
142
              label="Email"
143
              type="email"
144
              value={email}
145
              onChangeText={setEmail}
146
              placeholder="Qual seu email?"
147
              error={errors.email}
148
              autoCapitalize="none"
149
            />
150

151
            <Input
152
              label="Senha"
153
              type="password"
154
              value={password}
155
              onChangeText={setPassword}
156
              placeholder="Qual sua senha?"
157
              error={errors.password}
158
              showPasswordToggle
159
            />
160

161
            <Button
162
              title="Entrar"
163
              onPress={handleLogin}
164
              loading={loading}
165
              style={styles.loginButton}
166
            />
167

168
            <Link
169
              href="/auth/forgot-password"
170
              style={StyleSheet.flatten([styles.link, { color: tintColor }])}
171
              asChild
172
            >
173
              <Text
174
                style={StyleSheet.flatten([styles.footerText, { color: textColor, opacity: 0.7 }])}
175
              >
176
                Esqueceu a senha?
177
              </Text>
178
            </Link>
179
          </View>
180

181
          <View style={styles.footer}>
182
            <Text
183
              style={[styles.footerText, { color: textColor, opacity: 0.7 }]}
184
            >
185
              Não tem uma conta ainda?{" "}
186
            </Text>
187

188
            <Link
189
              href="/auth/signup"
190
              asChild
191
              style={StyleSheet.flatten([styles.link, { color: tintColor }])}
192
            >
193
              <Text
194
                style={StyleSheet.flatten([styles.footerText, { color: textColor, opacity: 0.7 }])}
195
              >
196
                Cadastre-se
197
              </Text>
198
            </Link>
199
          </View>
200
        </ScrollView>
201
      </KeyboardAvoidingView>
202
    </SafeAreaView>
203
  );
204
}
205

206
const styles = StyleSheet.create({
207
  container: {
208
    flex: 1,
209
  },
210
  keyboardView: {
211
    flex: 1,
212
  },
213
  logoContainer: {
214
    alignItems: "center",
215
    marginBottom: 40,
216
    marginTop: 10,
217
  },
218
  logo: {
219
    width: 120,
220
    height: 120,
221
  },
222
  scrollContainer: {
223
    flexGrow: 1,
224
    paddingHorizontal: 24,
225
    justifyContent: "center",
226
    minHeight: "100%",
227
  },
228
  header: {
229
    alignItems: "center",
230
    marginBottom: 40,
231
  },
232
  title: {
233
    fontSize: 28,
234
    fontWeight: "bold",
235
    marginBottom: 8,
236
  },
237
  subtitle: {
238
    fontSize: 16,
239
    textAlign: "center",
240
  },
241
  form: {
242
    marginBottom: 32,
243
  },
244
  loginButton: {
245
    marginTop: 8,
246
    marginBottom: 16,
247
  },
248
  link: {
249
    textAlign: "center",
250
    fontSize: 14,
251
    fontWeight: "500",
252
  },
253
  footer: {
254
    flexDirection: "row",
255
    justifyContent: "center",
256
    alignItems: "center",
257
    marginTop: "auto",
258
    paddingBottom: 40,
259
  },
260
  footerText: {
261
    fontSize: 14,
262
  },
263
});