eletrotupi / tcc/ frontend/app/(tabs)/settings.tsxc7abb1f398f8a1bc181dc8f1933a67881fa6b9d5
11.5 KB | 398 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 [allowWeeklyInsights, setAllowWeeklyInsights] = useState(false);
27
  const [logoutVisible, setLogoutVisible] = useState(false);
28
  const [exportVisible, setExportVisible] = useState(false);
29
  const [exporting, setExporting] = useState(false);
30
  const { showToast } = useToast();
31

32
  const notificationsEnabled = user?.notificationsEnabled ?? true;
33
  const dailyReminderTime = user?.dailyReminderTime ?? "20:00";
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 handleTimeChange = (date: Date) => {
44
    const time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
45
    patchUserMe.mutate({ dailyReminderTime: time });
46
  };
47

48
  const textColor = useThemeColor({}, "text");
49

50
  const handleLogout = () => {
51
    setLogoutVisible(true);
52
  };
53

54
  const handleProfilePress = () => {
55
    router.push("/profile");
56
  };
57

58
  const handleInvisibleMode = () => {};
59

60
  const handleDataExport = () => {
61
    setExportVisible(true);
62
  };
63

64
  const handleExportConfirm = async () => {
65
    setExporting(true);
66

67
    try {
68
      const { message } = await apiClient.exportData();
69

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

84
  return (
85
    <ScreenLayout
86
      userName={user?.firstName}
87
      userAvatar={user?.avatarURL}
88
      showNotificationBadge={true}
89
    >
90
      <Section>
91
        <SectionHeader title="Perfil" />
92

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

112
            <Ionicons name="chevron-forward" size={24} color={textColor} />
113
          </View>
114
        </Card>
115
      </Section>
116

117
      <Section>
118
        <SectionHeader title="Notificações" />
119

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

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

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

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

180
          <View style={styles.divider} />
181

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

198
            <View
199
              style={{
200
                flexDirection: "column",
201
                alignItems: "flex-start",
202
                justifyContent: "space-between",
203
                marginLeft: 12,
204
                flex: 1,
205
              }}
206
            >
207
              <Text style={styles.rowLabel}>Indicadores semanais</Text>
208
              <Text style={styles.rowSubtitle}>Relatórios de progressos</Text>
209
            </View>
210

211
            <Switch
212
              onValueChange={setAllowWeeklyInsights}
213
              value={allowWeeklyInsights}
214
              thumbColor={Colors.light.tint}
215
              trackColor={{
216
                false: Colors.light.disabled,
217
                true: Colors.light.gray,
218
              }}
219
            />
220
          </View>
221
        </Card>
222
      </Section>
223

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

252
      <Section>
253
        <SectionHeader title="Privacidade" />
254

255
        <Card style={{ padding: Spacing.cardGap }}>
256
          <TouchableOpacity
257
            style={{
258
              flexDirection: "row",
259
              alignItems: "center",
260
              justifyContent: "space-between",
261
            }}
262
            onPress={handleInvisibleMode}
263
          >
264
            <View style={{ flexDirection: "row", alignItems: "center" }}>
265
              <MaterialIcons
266
                name="lock"
267
                size={24}
268
                color={Colors.light.textSecondary}
269
              />
270

271
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
272
                Privacidade
273
              </Text>
274
            </View>
275

276
            <Ionicons name="chevron-forward" size={24} color={textColor} />
277
          </TouchableOpacity>
278

279
          <View style={styles.divider} />
280

281
          <TouchableOpacity
282
            style={{
283
              flexDirection: "row",
284
              alignItems: "center",
285
              justifyContent: "space-between",
286
            }}
287
            onPress={handleDataExport}
288
          >
289
            <View style={{ flexDirection: "row", alignItems: "center" }}>
290
              <MaterialIcons
291
                name="storage"
292
                size={24}
293
                color={Colors.light.textSecondary}
294
              />
295

296
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
297
                Exportar Meus Dados
298
              </Text>
299
            </View>
300

301
            <MaterialIcons
302
              name="file-download"
303
              size={24}
304
              color={Colors.light.textSecondary}
305
            />
306
          </TouchableOpacity>
307

308
          <View style={styles.divider} />
309

310
          <TouchableOpacity
311
            style={{
312
              flexDirection: "row",
313
              alignItems: "center",
314
              justifyContent: "space-between",
315
            }}
316
            onPress={() => router.push("/terms")}
317
          >
318
            <View style={{ flexDirection: "row", alignItems: "center" }}>
319
              <MaterialIcons
320
                name="description"
321
                size={24}
322
                color={Colors.light.textSecondary}
323
              />
324

325
              <Text style={[styles.rowLabel, { marginLeft: 8 }]}>
326
                Termos e Condições
327
              </Text>
328
            </View>
329

330
            <Ionicons name="chevron-forward" size={24} color={textColor} />
331
          </TouchableOpacity>
332
        </Card>
333
      </Section>
334

335
      <Button
336
        title="Sair da Conta"
337
        onPress={handleLogout}
338
        variant="outline"
339
        style={{ borderColor: Colors.light.danger }}
340
        testID="button-logout"
341
        textStyle={{ color: Colors.light.danger }}
342
      />
343

344
      <ConfirmModal
345
        visible={logoutVisible}
346
        title="Sair da conta"
347
        message="Tem certeza que quer sair?"
348
        confirmLabel="Sair"
349
        cancelLabel="Cancelar"
350
        destructive
351
        onCancel={() => setLogoutVisible(false)}
352
        onConfirm={() => {
353
          setLogoutVisible(false);
354
          logout();
355
        }}
356
        testID="logout-confirm"
357
      />
358

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

378
const styles = StyleSheet.create({
379
  cardIcon: {
380
    paddingVertical: 15,
381
    paddingHorizontal: 14,
382
    width: 48,
383
    borderRadius: BorderRadius.md,
384
  },
385
  rowLabel: {
386
    ...Typography.bodyLg,
387
    color: Colors.light.text,
388
  },
389
  rowSubtitle: {
390
    ...Typography.bodyMd,
391
    color: Colors.light.textSecondary,
392
  },
393
  divider: {
394
    height: StyleSheet.hairlineWidth,
395
    backgroundColor: "#D1D5DB",
396
    marginVertical: 16,
397
  },
398
});