eletrotupi / tcc/ frontend/app/(tabs)/settings.tsxbee79a3ab499c11539d5799b6c9cb1fc0d8ba1e0
10.7 KB | 375 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, Spacing, Typography } 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.rowLabel}>Dados Pessoais</Text>
111
              <Text style={styles.rowSubtitle}>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.rowLabel}>Lembrete diário de humor</Text>
153
              <Text style={styles.rowSubtitle}>
154
                {notificationsEnabled ? `Diário às ${dailyReminderTime}` : "Desativado"}
155
              </Text>
156
            </View>
157

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

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

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

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

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

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

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

254
      <Section>
255
        <SectionHeader title="Privacidade" />
256

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

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

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

281
          <View style={styles.divider} />
282

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

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

303
            <MaterialIcons
304
              name="file-download"
305
              size={24}
306
              color={Colors.light.textSecondary}
307
            />
308
          </TouchableOpacity>
309
        </Card>
310
      </Section>
311

312
      <Button
313
        title="Sair da Conta"
314
        onPress={handleLogout}
315
        variant="outline"
316
        style={{ borderColor: Colors.light.danger }}
317
        testID="button-logout"
318
        textStyle={{ color: Colors.light.danger }}
319
      />
320

321
      <ConfirmModal
322
        visible={logoutVisible}
323
        title="Sair da conta"
324
        message="Tem certeza que quer sair?"
325
        confirmLabel="Sair"
326
        cancelLabel="Cancelar"
327
        destructive
328
        onCancel={() => setLogoutVisible(false)}
329
        onConfirm={() => {
330
          setLogoutVisible(false);
331
          logout();
332
        }}
333
        testID="logout-confirm"
334
      />
335

336
      <ConfirmModal
337
        visible={exportVisible}
338
        title="Exportar meus dados"
339
        message={
340
          "Vamos preparar um arquivo com todos os seus dados e enviá-lo " +
341
          "para o e-mail abaixo. Pode levar alguns instantes."
342
        }
343
        detail={user?.email}
344
        confirmLabel="Exportar"
345
        cancelLabel="Cancelar"
346
        loading={exporting}
347
        onCancel={() => setExportVisible(false)}
348
        onConfirm={handleExportConfirm}
349
        testID="export-confirm"
350
      />
351
    </ScreenLayout>
352
  );
353
}
354

355
const styles = StyleSheet.create({
356
  cardIcon: {
357
    paddingVertical: 15,
358
    paddingHorizontal: 14,
359
    width: 48,
360
    borderRadius: BorderRadius.md,
361
  },
362
  rowLabel: {
363
    ...Typography.bodyLg,
364
    color: Colors.light.text,
365
  },
366
  rowSubtitle: {
367
    ...Typography.bodyMd,
368
    color: Colors.light.textSecondary,
369
  },
370
  divider: {
371
    height: StyleSheet.hairlineWidth,
372
    backgroundColor: "#D1D5DB",
373
    marginVertical: 16,
374
  },
375
});