eletrotupi / tcc/ frontend/app/(tabs)/settings.tsx92fe16c5883a76b1919f4c7dcb888c00e2912fc3
13.5 KB | 466 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
    >
105
      <Section>
106
        <SectionHeader title="Perfil" />
107

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

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

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

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

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

171
            <Switch
172
              onValueChange={handlePushToggle}
173
              thumbColor={Colors.light.tint}
174
              trackColor={{
175
                false: Colors.light.disabled,
176
                true: Colors.light.gray,
177
              }}
178
              value={pushEnabled}
179
              disabled={patchUserMe.isPending}
180
            />
181
          </View>
182

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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