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

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

34
  const notificationsEnabled = user?.notificationsEnabled ?? true;
35
  const dailyReminderTime = user?.dailyReminderTime ?? "20:00";
36

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

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

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

50
  const textColor = useThemeColor({}, "text");
51

52
  const handleLogout = () => {
53
    setLogoutVisible(true);
54
  };
55

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

60
  const handleInvisibleMode = () => {};
61

62
  const handleDataExport = () => {
63
    setExportVisible(true);
64
  };
65

66
  const handleExportConfirm = async () => {
67
    setExporting(true);
68

69
    try {
70
      const { message } = await apiClient.exportData();
71

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

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

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

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

119
      <Section>
120
        <SectionHeader title="Notificações" />
121

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

143
            <View
144
              style={{
145
                flexDirection: "column",
146
                alignItems: "flex-start",
147
                justifyContent: "space-between",
148
                marginLeft: 12,
149
                flex: 1,
150
              }}
151
            >
152
              <Text style={styles.cardTitle}>Lembrete diário de humor</Text>
153
              <Text>{notificationsEnabled ? `Diário às ${dailyReminderTime}` : "Desativado"}</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.cardTitle}>Indicadores semanais</Text>
208
              <Text>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={{ marginLeft: 8, ...styles.cardTitle }}>
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
272
                style={{
273
                  marginLeft: 8,
274
                  color: Colors.light.text,
275
                  fontSize: 14,
276
                  fontWeight: "500" as const,
277
                }}
278
              >
279
                Privacidade
280
              </Text>
281
            </View>
282

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

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

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

303
              <Text
304
                style={{
305
                  marginLeft: 8,
306
                  color: Colors.light.text,
307
                  fontSize: 14,
308
                  fontWeight: "500" as const,
309
                }}
310
              >
311
                Exportar Meus Dados
312
              </Text>
313
            </View>
314

315
            <MaterialIcons
316
              name="file-download"
317
              size={24}
318
              color={Colors.light.textSecondary}
319
            />
320
          </TouchableOpacity>
321
        </Card>
322
      </Section>
323

324
      <Button
325
        title="Sair da Conta"
326
        onPress={handleLogout}
327
        variant="outline"
328
        style={{ borderColor: Colors.light.danger }}
329
        testID="button-logout"
330
        textStyle={{ color: Colors.light.danger }}
331
      />
332

333
      <ConfirmModal
334
        visible={logoutVisible}
335
        title="Sair da conta"
336
        message="Tem certeza que quer sair?"
337
        confirmLabel="Sair"
338
        cancelLabel="Cancelar"
339
        destructive
340
        onCancel={() => setLogoutVisible(false)}
341
        onConfirm={() => {
342
          setLogoutVisible(false);
343
          logout();
344
        }}
345
        testID="logout-confirm"
346
      />
347

348
      <ConfirmModal
349
        visible={exportVisible}
350
        title="Exportar meus dados"
351
        message={
352
          "Vamos preparar um arquivo com todos os seus dados e enviá-lo " +
353
          "para o e-mail abaixo. Pode levar alguns instantes."
354
        }
355
        detail={user?.email}
356
        confirmLabel="Exportar"
357
        cancelLabel="Cancelar"
358
        loading={exporting}
359
        onCancel={() => setExportVisible(false)}
360
        onConfirm={handleExportConfirm}
361
        testID="export-confirm"
362
      />
363
    </ScreenLayout>
364
  );
365
}
366

367
const styles = StyleSheet.create({
368
  container: {
369
    flex: 1,
370
    marginHorizontal: 16,
371
    paddingTop: 10,
372
  },
373
  item: {
374
    paddingTop: 20,
375
    paddingBottom: 20,
376
  },
377
  header: {
378
    fontSize: 22,
379
    fontWeight: "bold",
380
  },
381
  cardIcon: {
382
    paddingVertical: 15,
383
    paddingHorizontal: 14,
384
    width: 48,
385
    borderRadius: BorderRadius.md,
386
  },
387
  cardTitle: {
388
    fontSize: 16,
389
    fontWeight: "bold",
390
  },
391
  cardSubtitle: {
392
    fontSize: 12,
393
    color: Colors.light.textSecondary,
394
  },
395
  logout: {
396
    color: "#FF2C2C",
397
  },
398
  divider: {
399
    height: StyleSheet.hairlineWidth,
400
    backgroundColor: "#D1D5DB",
401
    marginVertical: 16,
402
  },
403
});