eletrotupi / tcc/ frontend/app/(tabs)/settings.tsx6b62f4dcf96987b8b2e55bd210139a3ac9f0d976
11.7 KB | 406 lines Raw
1
import {
2
  TouchableOpacity,
3
  StyleSheet,
4
  View,
5
  Switch,
6
} from "react-native";
7
import { Text } from '@/components/ui/Text';
8
import { useState } from "react";
9
import { router } from "expo-router";
10
import { Card } from "@/components/ui/Cards";
11
import { ScreenLayout } from "@/components/ui/ScreenLayout";
12
import { BorderRadius, Colors, Spacing, Typography } from "@/constants/theme";
13
import { useThemeColor } from "@/hooks/use-theme-color";
14
import { useAuth } from "@/context/AuthContext";
15
import { Section, SectionHeader } from "@/components/ui/Sections";
16
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
17
import { Button } from "@/components/ui/Button";
18
import { ConfirmModal } from "@/components/ui/ConfirmModal";
19
import { TimePicker } from "@/components/ui/TimePicker";
20
import { usePatchUserMe } from "@/hooks/useUserPreferences.queries";
21
import { useToast } from '@/context/ToastContext';
22
import { apiClient } from "@/lib/api";
23

24
export default function Settings() {
25
  const { user, logout, updateAuthUser } = useAuth();
26
  const [logoutVisible, setLogoutVisible] = useState(false);
27
  const [exportVisible, setExportVisible] = useState(false);
28
  const [exporting, setExporting] = useState(false);
29
  const { showToast } = useToast();
30

31
  const notificationsEnabled = user?.notificationsEnabled ?? true;
32
  const dailyReminderTime = user?.dailyReminderTime ?? "20:00";
33
  const weeklyResumeEnabled = user?.weeklyResumeEnabled ?? false;
34

35
  const patchUserMe = usePatchUserMe((updatedUser) => {
36
    updateAuthUser(updatedUser);
37
  });
38

39
  const handleNotificationToggle = (value: boolean) => {
40
    patchUserMe.mutate({ notificationsEnabled: value });
41
  };
42

43
  const handleWeeklyResumeToggle = (value: boolean) => {
44
    patchUserMe.mutate({ weeklyResumeEnabled: value });
45
  };
46

47
  const handleTimeChange = (date: Date) => {
48
    const time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
49
    patchUserMe.mutate({ dailyReminderTime: time });
50
  };
51

52
  const textColor = useThemeColor({}, "text");
53

54
  const handleLogout = () => {
55
    setLogoutVisible(true);
56
  };
57

58
  const handleProfilePress = () => {
59
    router.push("/profile");
60
  };
61

62
  const handleInvisibleMode = () => {};
63

64
  const handleDataExport = () => {
65
    setExportVisible(true);
66
  };
67

68
  const handleExportConfirm = async () => {
69
    setExporting(true);
70

71
    try {
72
      const { message } = await apiClient.exportData();
73

74
      setExportVisible(false);
75
      showToast(message, "success");
76
    } catch (error) {
77
      showToast(
78
        error instanceof Error
79
          ? error.message
80
          : "Não foi possível exportar seus dados",
81
        "error",
82
      );
83
    } finally {
84
      setExporting(false);
85
    }
86
  };
87

88
  return (
89
    <ScreenLayout
90
      userName={user?.firstName}
91
      userAvatar={user?.avatarURL}
92
      showNotificationBadge={true}
93
    >
94
      <Section>
95
        <SectionHeader title="Perfil" />
96

97
        <Card style={{ padding: Spacing.cardGap }} onPress={handleProfilePress}>
98
          <View
99
            style={{
100
              flexDirection: "row",
101
              alignItems: "center",
102
              justifyContent: "space-between",
103
            }}
104
          >
105
            <View
106
              style={{
107
                flexDirection: "column",
108
                alignItems: "center",
109
                justifyContent: "space-between",
110
              }}
111
            >
112
              <Text style={[styles.rowLabel, { width: '100%' }]}>Dados Pessoais</Text>
113
              <Text style={styles.rowSubtitle}>Nome, Email e senha</Text>
114
            </View>
115

116
            <Ionicons name="chevron-forward" size={24} color={textColor} />
117
          </View>
118
        </Card>
119
      </Section>
120

121
      <Section>
122
        <SectionHeader title="Notificações" />
123

124
        <Card style={{ padding: Spacing.cardGap }}>
125
          <View
126
            style={{
127
              flexDirection: "row",
128
              alignItems: "center",
129
              justifyContent: "space-between",
130
            }}
131
          >
132
            <View
133
              style={[
134
                styles.cardIcon,
135
                { backgroundColor: Colors.light.accentBlue, marginBottom: 5 },
136
              ]}
137
            >
138
              <Ionicons
139
                name="notifications-outline"
140
                size={20}
141
                color={Colors.light.black}
142
              />
143
            </View>
144

145
            <View
146
              style={{
147
                flexDirection: "column",
148
                alignItems: "flex-start",
149
                justifyContent: "space-between",
150
                marginLeft: 12,
151
                flex: 1,
152
              }}
153
            >
154
              <Text style={styles.rowLabel}>Lembrete diário de humor</Text>
155
              <Text style={styles.rowSubtitle}>
156
                {notificationsEnabled ? `Diário às ${dailyReminderTime}` : "Desativado"}
157
              </Text>
158
            </View>
159

160
            <Switch
161
              onValueChange={handleNotificationToggle}
162
              thumbColor={Colors.light.tint}
163
              trackColor={{
164
                false: Colors.light.disabled,
165
                true: Colors.light.gray,
166
              }}
167
              value={notificationsEnabled}
168
              disabled={patchUserMe.isPending}
169
            />
170
          </View>
171

172
          {notificationsEnabled && (
173
            <TimePicker
174
              value={(() => {
175
                const [h, m] = dailyReminderTime.split(":").map(Number);
176
                const d = new Date();
177
                d.setHours(h, m, 0, 0);
178
                return d;
179
              })()}
180
              onChange={handleTimeChange}
181
            />
182
          )}
183

184
          <View style={styles.divider} />
185

186
          <View
187
            style={{
188
              flexDirection: "row",
189
              alignItems: "center",
190
              justifyContent: "space-between",
191
            }}
192
          >
193
            <View
194
              style={[
195
                styles.cardIcon,
196
                { backgroundColor: Colors.light.accentPurple },
197
              ]}
198
            >
199
              <Ionicons name="sparkles" size={20} color={Colors.light.black} />
200
            </View>
201

202
            <View
203
              style={{
204
                flexDirection: "column",
205
                alignItems: "flex-start",
206
                justifyContent: "space-between",
207
                marginLeft: 12,
208
                flex: 1,
209
              }}
210
            >
211
              <Text style={styles.rowLabel}>Resumo semanal</Text>
212
              <Text style={styles.rowSubtitle}>
213
                Mostra um resumo no topo da Jornada, a partir dos seus dados.
214
                Desativar não afeta os indicadores semanais.
215
              </Text>
216
            </View>
217

218
            <Switch
219
              onValueChange={handleWeeklyResumeToggle}
220
              value={weeklyResumeEnabled}
221
              thumbColor={Colors.light.tint}
222
              trackColor={{
223
                false: Colors.light.disabled,
224
                true: Colors.light.gray,
225
              }}
226
              disabled={patchUserMe.isPending}
227
            />
228
          </View>
229
        </Card>
230
      </Section>
231

232
      <Section>
233
        <SectionHeader title="Relatório" />
234
        <Card
235
          style={{ padding: Spacing.cardGap }}
236
          onPress={() => router.push("/report")}
237
        >
238
          <View
239
            style={{
240
              flexDirection: "row",
241
              alignItems: "center",
242
              justifyContent: "space-between",
243
            }}
244
          >
245
            <View style={{ flexDirection: "row", alignItems: "center" }}>
246
              <MaterialIcons
247
                name="picture-as-pdf"
248
                size={24}
249
                color={Colors.light.textSecondary}
250
              />
251
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
252
                Gerar Relatório
253
              </Text>
254
            </View>
255
            <Ionicons name="chevron-forward" size={24} color={textColor} />
256
          </View>
257
        </Card>
258
      </Section>
259

260
      <Section>
261
        <SectionHeader title="Privacidade" />
262

263
        <Card style={{ padding: Spacing.cardGap }}>
264
          <TouchableOpacity
265
            style={{
266
              flexDirection: "row",
267
              alignItems: "center",
268
              justifyContent: "space-between",
269
            }}
270
            onPress={handleInvisibleMode}
271
          >
272
            <View style={{ flexDirection: "row", alignItems: "center" }}>
273
              <MaterialIcons
274
                name="lock"
275
                size={24}
276
                color={Colors.light.textSecondary}
277
              />
278

279
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
280
                Privacidade
281
              </Text>
282
            </View>
283

284
            <Ionicons name="chevron-forward" size={24} color={textColor} />
285
          </TouchableOpacity>
286

287
          <View style={styles.divider} />
288

289
          <TouchableOpacity
290
            style={{
291
              flexDirection: "row",
292
              alignItems: "center",
293
              justifyContent: "space-between",
294
            }}
295
            onPress={handleDataExport}
296
          >
297
            <View style={{ flexDirection: "row", alignItems: "center" }}>
298
              <MaterialIcons
299
                name="storage"
300
                size={24}
301
                color={Colors.light.textSecondary}
302
              />
303

304
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
305
                Exportar Meus Dados
306
              </Text>
307
            </View>
308

309
            <MaterialIcons
310
              name="file-download"
311
              size={24}
312
              color={Colors.light.textSecondary}
313
            />
314
          </TouchableOpacity>
315

316
          <View style={styles.divider} />
317

318
          <TouchableOpacity
319
            style={{
320
              flexDirection: "row",
321
              alignItems: "center",
322
              justifyContent: "space-between",
323
            }}
324
            onPress={() => router.push("/terms")}
325
          >
326
            <View style={{ flexDirection: "row", alignItems: "center" }}>
327
              <MaterialIcons
328
                name="description"
329
                size={24}
330
                color={Colors.light.textSecondary}
331
              />
332

333
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
334
                Termos e Condições
335
              </Text>
336
            </View>
337

338
            <Ionicons name="chevron-forward" size={24} color={textColor} />
339
          </TouchableOpacity>
340
        </Card>
341
      </Section>
342

343
      <Button
344
        title="Sair da Conta"
345
        onPress={handleLogout}
346
        variant="outline"
347
        style={{ borderColor: Colors.light.danger }}
348
        testID="button-logout"
349
        textStyle={{ color: Colors.light.danger }}
350
      />
351

352
      <ConfirmModal
353
        visible={logoutVisible}
354
        title="Sair da conta"
355
        message="Tem certeza que quer sair?"
356
        confirmLabel="Sair"
357
        cancelLabel="Cancelar"
358
        destructive
359
        onCancel={() => setLogoutVisible(false)}
360
        onConfirm={() => {
361
          setLogoutVisible(false);
362
          logout();
363
        }}
364
        testID="logout-confirm"
365
      />
366

367
      <ConfirmModal
368
        visible={exportVisible}
369
        title="Exportar meus dados"
370
        message={
371
          "Vamos preparar um arquivo com todos os seus dados e enviá-lo " +
372
          "para o e-mail abaixo. Pode levar alguns instantes."
373
        }
374
        detail={user?.email}
375
        confirmLabel="Exportar"
376
        cancelLabel="Cancelar"
377
        loading={exporting}
378
        onCancel={() => setExportVisible(false)}
379
        onConfirm={handleExportConfirm}
380
        testID="export-confirm"
381
      />
382
    </ScreenLayout>
383
  );
384
}
385

386
const styles = StyleSheet.create({
387
  cardIcon: {
388
    paddingVertical: 15,
389
    paddingHorizontal: 14,
390
    width: 48,
391
    borderRadius: BorderRadius.md,
392
  },
393
  rowLabel: {
394
    ...Typography.bodyLg,
395
    color: Colors.light.text,
396
  },
397
  rowSubtitle: {
398
    ...Typography.bodyMd,
399
    color: Colors.light.textSecondary,
400
  },
401
  divider: {
402
    height: StyleSheet.hairlineWidth,
403
    backgroundColor: "#D1D5DB",
404
    marginVertical: 16,
405
  },
406
});