eletrotupi / tcc/ frontend/app/auth/activate.tsx13a51f682d4b5fcaa5c7daa8c766d0d8738d29da
5.0 KB | 184 lines Raw
1
import React, { useEffect, useRef, useState } from 'react';
2
import {
3
  View,
4
  StyleSheet,
5
  ScrollView,
6
  KeyboardAvoidingView,
7
} from 'react-native';
8
import { Text } from '@/components/ui/Text';
9
import { router } from 'expo-router';
10
import { SafeAreaView } from 'react-native-safe-area-context';
11
import { useThemeColor } from '@/hooks/use-theme-color';
12
import { Button, CodeInput } from '@/components/ui';
13
import type { CodeInputHandle } from '@/components/ui/CodeInput';
14
import { Colors } from '@/constants/theme';
15
import { useAuth } from '@/context/AuthContext';
16
import { useToast } from '@/context/ToastContext';
17
import { translateError } from '@/lib/errors/translations';
18

19
const CODE_LENGTH = 6;
20
const RESEND_COOLDOWN_SECONDS = 60;
21

22
export default function ActivateScreen() {
23
  const [code, setCode] = useState('');
24
  const [codeError, setCodeError] = useState<string>();
25
  const [loading, setLoading] = useState(false);
26
  const [secondsLeft, setSecondsLeft] = useState(RESEND_COOLDOWN_SECONDS);
27
  const codeInputRef = useRef<CodeInputHandle>(null);
28

29
  const { activate, requestActivateCode } = useAuth();
30
  const { showToast } = useToast();
31
  const textColor = useThemeColor({}, 'text');
32
  const backgroundColor = useThemeColor({}, 'background');
33

34
  useEffect(() => {
35
    if (secondsLeft <= 0) return;
36

37
    const timer = setTimeout(() => setSecondsLeft((value) => value - 1), 1000);
38
    return () => clearTimeout(timer);
39
  }, [secondsLeft]);
40

41
  const submitCode = async (value: string) => {
42
    if (loading) return;
43

44
    if (value.length !== CODE_LENGTH) {
45
      setCodeError(`O código deve ter ${CODE_LENGTH} dígitos`);
46
      return;
47
    }
48

49
    setCodeError(undefined);
50
    setLoading(true);
51
    try {
52
      await activate(value);
53
      showToast('Conta ativada com sucesso!', 'success');
54
      router.replace('/');
55
    } catch (error) {
56
      setCode('');
57
      setCodeError(
58
        translateError(error) ||
59
          'Código incorreto. Verifique o código e tente novamente.',
60
      );
61
      codeInputRef.current?.focus();
62
    } finally {
63
      setLoading(false);
64
    }
65
  };
66

67
  const handleCodeChange = (value: string) => {
68
    setCode(value);
69
    setCodeError(undefined);
70
  };
71

72
  const resendCode = async () => {
73
    if (secondsLeft > 0) return;
74

75
    setSecondsLeft(RESEND_COOLDOWN_SECONDS);
76
    try {
77
      await requestActivateCode();
78
      showToast('Novo código enviado. Verifique sua caixa de entrada.', 'success');
79
    } catch {
80
      setSecondsLeft(0);
81
      showToast('Falha ao reenviar o código. Tente novamente.', 'error');
82
    }
83
  };
84

85
  return (
86
    <SafeAreaView style={[styles.container, { backgroundColor }]}>
87
      <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
88
        <ScrollView
89
          contentContainerStyle={styles.scrollContainer}
90
          showsVerticalScrollIndicator={false}
91
        >
92
          <View style={styles.header}>
93
            <Text style={[styles.title, { color: textColor }]}>Ativar a conta</Text>
94
            <Text style={[styles.subtitle, { color: textColor, opacity: 0.7 }]}>
95
              Digite o código de 6 dígitos enviado para seu email
96
            </Text>
97
          </View>
98

99
          <View style={styles.form}>
100
            <CodeInput
101
              ref={codeInputRef}
102
              value={code}
103
              onChange={handleCodeChange}
104
              onComplete={submitCode}
105
              length={CODE_LENGTH}
106
              error={codeError}
107
              editable={!loading}
108
              accessibilityLabel="Código de ativação de 6 dígitos"
109
              testIdPrefix="activate-code"
110
            />
111

112
            <Button
113
              title="Ativar conta"
114
              onPress={() => submitCode(code)}
115
              loading={loading}
116
              style={styles.activateButton}
117
            />
118
          </View>
119

120
          <View style={styles.footer}>
121
            <Text style={[styles.footerText, { color: textColor, opacity: 0.7 }]}>
122
              Não recebeu?{' '}
123
            </Text>
124

125
            <Button
126
              variant="outline"
127
              title={
128
                secondsLeft > 0
129
                  ? `Reenviar em ${secondsLeft}s`
130
                  : 'Reenviar um novo código'
131
              }
132
              onPress={resendCode}
133
              disabled={secondsLeft > 0}
134
            />
135
          </View>
136
        </ScrollView>
137
      </KeyboardAvoidingView>
138
    </SafeAreaView>
139
  );
140
}
141

142
const styles = StyleSheet.create({
143
  container: {
144
    flex: 1,
145
  },
146
  keyboardView: {
147
    flex: 1,
148
  },
149
  scrollContainer: {
150
    flexGrow: 1,
151
    paddingHorizontal: 24,
152
    justifyContent: 'center',
153
    minHeight: '100%',
154
  },
155
  header: {
156
    alignItems: 'center',
157
    marginBottom: 40,
158
  },
159
  title: {
160
    fontSize: 28,
161
    fontWeight: 'bold',
162
    marginBottom: 8,
163
  },
164
  subtitle: {
165
    fontSize: 16,
166
    textAlign: 'center',
167
  },
168
  form: {
169
    marginBottom: 32,
170
  },
171
  activateButton: {
172
    marginTop: 8,
173
  },
174
  footer: {
175
    marginTop: 20,
176
    borderTopColor: Colors.light.accentBlue,
177
    borderTopWidth: 1,
178
    paddingTop: 20,
179
  },
180
  footerText: {
181
    textAlign: 'center',
182
    marginBottom: 10,
183
  },
184
});