eletrotupi / tcc/ frontend/app/(tabs)/settings.tsx30f98b88ba3e0926a1a93ed3c7a9e2618d9ff920
13.5 KB | 467 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 pushEnabled = user?.pushEnabled ?? true;
32
  const dailyReminderTime = user?.dailyReminderTime ?? null;
33
  const reminderEnabled = dailyReminderTime !== null;
34
  const weeklyResumeEnabled = user?.weeklyResumeEnabled ?? false;
35
  const [lastReminderTime, setLastReminderTime] = useState("20:00");
36
  const reminderTime = dailyReminderTime ?? lastReminderTime;
37

38
  const patchUserMe = usePatchUserMe((updatedUser) => {
39
    updateAuthUser(updatedUser);
40
  });
41

42
  const handlePushToggle = (value: boolean) => {
43
    patchUserMe.mutate({ pushEnabled: value });
44
  };
45

46
  const handleReminderToggle = (value: boolean) => {
47
    if (dailyReminderTime) {
48
      setLastReminderTime(dailyReminderTime);
49
    }
50

51
    patchUserMe.mutate({ dailyReminderTime: value ? reminderTime : null });
52
  };
53

54
  const handleWeeklyResumeToggle = (value: boolean) => {
55
    patchUserMe.mutate({ weeklyResumeEnabled: value });
56
  };
57

58
  const handleTimeChange = (date: Date) => {
59
    const time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
60
    setLastReminderTime(time);
61
    patchUserMe.mutate({ dailyReminderTime: time });
62
  };
63

64
  const textColor = useThemeColor({}, "text");
65

66
  const handleLogout = () => {
67
    setLogoutVisible(true);
68
  };
69

70
  const handleProfilePress = () => {
71
    router.push("/profile");
72
  };
73

74
  const handleInvisibleMode = () => {};
75

76
  const handleDataExport = () => {
77
    setExportVisible(true);
78
  };
79

80
  const handleExportConfirm = async () => {
81
    setExporting(true);
82

83
    try {
84
      const { message } = await apiClient.exportData();
85

86
      setExportVisible(false);
87
      showToast(message, "success");
88
    } catch (error) {
89
      showToast(
90
        error instanceof Error
91
          ? error.message
92
          : "Não foi possível exportar seus dados",
93
        "error",
94
      );
95
    } finally {
96
      setExporting(false);
97
    }
98
  };
99

100
  return (
101
    <ScreenLayout
102
      userName={user?.firstName}
103
      userAvatar={user?.avatarURL}
104
      showNotificationBadge={true}
105
    >
106
      <Section>
107
        <SectionHeader title="Perfil" />
108

109
        <Card style={{ padding: Spacing.cardGap }} onPress={handleProfilePress}>
110
          <View
111
            style={{
112
              flexDirection: "row",
113
              alignItems: "center",
114
              justifyContent: "space-between",
115
            }}
116
          >
117
            <View
118
              style={{
119
                flexDirection: "column",
120
                alignItems: "center",
121
                justifyContent: "space-between",
122
              }}
123
            >
124
              <Text style={[styles.rowLabel, { width: '100%' }]}>Dados Pessoais</Text>
125
              <Text style={styles.rowSubtitle}>Nome, Email e senha</Text>
126
            </View>
127

128
            <Ionicons name="chevron-forward" size={24} color={textColor} />
129
          </View>
130
        </Card>
131
      </Section>
132

133
      <Section>
134
        <SectionHeader title="Notificações" />
135

136
        <Card style={{ padding: Spacing.cardGap }}>
137
          <View
138
            style={{
139
              flexDirection: "row",
140
              alignItems: "center",
141
              justifyContent: "space-between",
142
            }}
143
          >
144
            <View
145
              style={[
146
                styles.cardIcon,
147
                { backgroundColor: Colors.light.accentBlue, marginBottom: 5 },
148
              ]}
149
            >
150
              <Ionicons
151
                name="notifications-outline"
152
                size={20}
153
                color={Colors.light.black}
154
              />
155
            </View>
156

157
            <View
158
              style={{
159
                flexDirection: "column",
160
                alignItems: "flex-start",
161
                justifyContent: "space-between",
162
                marginLeft: 12,
163
                flex: 1,
164
              }}
165
            >
166
              <Text style={styles.rowLabel}>Notificações por push</Text>
167
              <Text style={styles.rowSubtitle}>
168
                Os lembretes continuam no app mesmo com o push desativado.
169
              </Text>
170
            </View>
171

172
            <Switch
173
              onValueChange={handlePushToggle}
174
              thumbColor={Colors.light.tint}
175
              trackColor={{
176
                false: Colors.light.disabled,
177
                true: Colors.light.gray,
178
              }}
179
              value={pushEnabled}
180
              disabled={patchUserMe.isPending}
181
            />
182
          </View>
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.accentIndigo, marginBottom: 5 },
197
              ]}
198
            >
199
              <Ionicons
200
                name="alarm-outline"
201
                size={20}
202
                color={Colors.light.black}
203
              />
204
            </View>
205

206
            <View
207
              style={{
208
                flexDirection: "column",
209
                alignItems: "flex-start",
210
                justifyContent: "space-between",
211
                marginLeft: 12,
212
                flex: 1,
213
              }}
214
            >
215
              <Text style={styles.rowLabel}>Lembrete diário de humor</Text>
216
              <Text style={styles.rowSubtitle}>
217
                {reminderEnabled ? `Diário às ${reminderTime}` : "Desativado"}
218
              </Text>
219
            </View>
220

221
            <Switch
222
              onValueChange={handleReminderToggle}
223
              thumbColor={Colors.light.tint}
224
              trackColor={{
225
                false: Colors.light.disabled,
226
                true: Colors.light.gray,
227
              }}
228
              value={reminderEnabled}
229
              disabled={patchUserMe.isPending}
230
            />
231
          </View>
232

233
          {reminderEnabled && (
234
            <TimePicker
235
              value={(() => {
236
                const [h, m] = reminderTime.split(":").map(Number);
237
                const d = new Date();
238
                d.setHours(h, m, 0, 0);
239
                return d;
240
              })()}
241
              onChange={handleTimeChange}
242
            />
243
          )}
244

245
          <View style={styles.divider} />
246

247
          <View
248
            style={{
249
              flexDirection: "row",
250
              alignItems: "center",
251
              justifyContent: "space-between",
252
            }}
253
          >
254
            <View
255
              style={[
256
                styles.cardIcon,
257
                { backgroundColor: Colors.light.accentPurple },
258
              ]}
259
            >
260
              <Ionicons name="sparkles" size={20} color={Colors.light.black} />
261
            </View>
262

263
            <View
264
              style={{
265
                flexDirection: "column",
266
                alignItems: "flex-start",
267
                justifyContent: "space-between",
268
                marginLeft: 12,
269
                flex: 1,
270
              }}
271
            >
272
              <Text style={styles.rowLabel}>Resumo semanal</Text>
273
              <Text style={styles.rowSubtitle}>
274
                Mostra um resumo no topo da Jornada, a partir dos seus dados.
275
                Desativar não afeta os indicadores semanais.
276
              </Text>
277
            </View>
278

279
            <Switch
280
              onValueChange={handleWeeklyResumeToggle}
281
              value={weeklyResumeEnabled}
282
              thumbColor={Colors.light.tint}
283
              trackColor={{
284
                false: Colors.light.disabled,
285
                true: Colors.light.gray,
286
              }}
287
              disabled={patchUserMe.isPending}
288
            />
289
          </View>
290
        </Card>
291
      </Section>
292

293
      <Section>
294
        <SectionHeader title="Relatório" />
295
        <Card
296
          style={{ padding: Spacing.cardGap }}
297
          onPress={() => router.push("/report")}
298
        >
299
          <View
300
            style={{
301
              flexDirection: "row",
302
              alignItems: "center",
303
              justifyContent: "space-between",
304
            }}
305
          >
306
            <View style={{ flexDirection: "row", alignItems: "center" }}>
307
              <MaterialIcons
308
                name="picture-as-pdf"
309
                size={24}
310
                color={Colors.light.textSecondary}
311
              />
312
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
313
                Gerar Relatório
314
              </Text>
315
            </View>
316
            <Ionicons name="chevron-forward" size={24} color={textColor} />
317
          </View>
318
        </Card>
319
      </Section>
320

321
      <Section>
322
        <SectionHeader title="Privacidade" />
323

324
        <Card style={{ padding: Spacing.cardGap }}>
325
          <TouchableOpacity
326
            style={{
327
              flexDirection: "row",
328
              alignItems: "center",
329
              justifyContent: "space-between",
330
            }}
331
            onPress={handleInvisibleMode}
332
          >
333
            <View style={{ flexDirection: "row", alignItems: "center" }}>
334
              <MaterialIcons
335
                name="lock"
336
                size={24}
337
                color={Colors.light.textSecondary}
338
              />
339

340
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
341
                Privacidade
342
              </Text>
343
            </View>
344

345
            <Ionicons name="chevron-forward" size={24} color={textColor} />
346
          </TouchableOpacity>
347

348
          <View style={styles.divider} />
349

350
          <TouchableOpacity
351
            style={{
352
              flexDirection: "row",
353
              alignItems: "center",
354
              justifyContent: "space-between",
355
            }}
356
            onPress={handleDataExport}
357
          >
358
            <View style={{ flexDirection: "row", alignItems: "center" }}>
359
              <MaterialIcons
360
                name="storage"
361
                size={24}
362
                color={Colors.light.textSecondary}
363
              />
364

365
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
366
                Exportar Meus Dados
367
              </Text>
368
            </View>
369

370
            <MaterialIcons
371
              name="file-download"
372
              size={24}
373
              color={Colors.light.textSecondary}
374
            />
375
          </TouchableOpacity>
376

377
          <View style={styles.divider} />
378

379
          <TouchableOpacity
380
            style={{
381
              flexDirection: "row",
382
              alignItems: "center",
383
              justifyContent: "space-between",
384
            }}
385
            onPress={() => router.push("/terms")}
386
          >
387
            <View style={{ flexDirection: "row", alignItems: "center" }}>
388
              <MaterialIcons
389
                name="description"
390
                size={24}
391
                color={Colors.light.textSecondary}
392
              />
393

394
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
395
                Termos e Condições
396
              </Text>
397
            </View>
398

399
            <Ionicons name="chevron-forward" size={24} color={textColor} />
400
          </TouchableOpacity>
401
        </Card>
402
      </Section>
403

404
      <Button
405
        title="Sair da Conta"
406
        onPress={handleLogout}
407
        variant="outline"
408
        style={{ borderColor: Colors.light.danger }}
409
        testID="button-logout"
410
        textStyle={{ color: Colors.light.danger }}
411
      />
412

413
      <ConfirmModal
414
        visible={logoutVisible}
415
        title="Sair da conta"
416
        message="Tem certeza que quer sair?"
417
        confirmLabel="Sair"
418
        cancelLabel="Cancelar"
419
        destructive
420
        onCancel={() => setLogoutVisible(false)}
421
        onConfirm={() => {
422
          setLogoutVisible(false);
423
          logout();
424
        }}
425
        testID="logout-confirm"
426
      />
427

428
      <ConfirmModal
429
        visible={exportVisible}
430
        title="Exportar meus dados"
431
        message={
432
          "Vamos preparar um arquivo com todos os seus dados e enviá-lo " +
433
          "para o e-mail abaixo. Pode levar alguns instantes."
434
        }
435
        detail={user?.email}
436
        confirmLabel="Exportar"
437
        cancelLabel="Cancelar"
438
        loading={exporting}
439
        onCancel={() => setExportVisible(false)}
440
        onConfirm={handleExportConfirm}
441
        testID="export-confirm"
442
      />
443
    </ScreenLayout>
444
  );
445
}
446

447
const styles = StyleSheet.create({
448
  cardIcon: {
449
    paddingVertical: 15,
450
    paddingHorizontal: 14,
451
    width: 48,
452
    borderRadius: BorderRadius.md,
453
  },
454
  rowLabel: {
455
    ...Typography.bodyLg,
456
    color: Colors.light.text,
457
  },
458
  rowSubtitle: {
459
    ...Typography.bodyMd,
460
    color: Colors.light.textSecondary,
461
  },
462
  divider: {
463
    height: StyleSheet.hairlineWidth,
464
    backgroundColor: "#D1D5DB",
465
    marginVertical: 16,
466
  },
467
});