frontend/auth: add the terms and onboarding flows
Signing up now asks for the terms up front and links to the page, and a first run after activation gets a short skippable tour. Both stay reachable from settings
Pedro Lucas Porcellis porcellis@eletrotupi.com · 11 days ago
c7abb1f398f8a1bc181dc8f1933a67881fa6b9d5 Browse filesParents
6bdc28aChanged files
3
Additions
+247
Deletions
0
frontend/app/(tabs)/settings.tsx
1 1
import {
2 2
TouchableOpacity,
3 3
StyleSheet,
4 4
View,
5 5
Switch,
302 302
name="file-download"
303 303
size={24}
304 304
color={Colors.light.textSecondary}
305 305
/>
306 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>
307 332
</Card>
308 333
</Section>
309 334
310 335
<Button
311 336
title="Sair da Conta"
369 394
height: StyleSheet.hairlineWidth,
370 395
backgroundColor: "#D1D5DB",
371 396
marginVertical: 16,
372 397
},
373 398
});
/dev/null → frontend/app/onboarding.tsx
1 +
import React, { useState } from 'react';
2 +
import { View, StyleSheet } from 'react-native';
3 +
import { router } from 'expo-router';
4 +
import { SafeAreaView } from 'react-native-safe-area-context';
5 +
import { Text } from '@/components/ui/Text';
6 +
import { Button } from '@/components/ui';
7 +
import { useAuth } from '@/context/AuthContext';
8 +
import { useThemeColor } from '@/hooks/use-theme-color';
9 +
10 +
const PAGES = [
11 +
{
12 +
title: 'Bem vindo ao nexo',
13 +
body: 'Registre seu humor ao longo do dia, sempre depois que aconteceu. É um diário, não uma cobrança.',
14 +
},
15 +
{
16 +
title: 'No seu ritmo',
17 +
body: 'Anote quantos momentos quiser. Cada registro guarda o humor, a intensidade e o que aconteceu.',
18 +
},
19 +
{
20 +
title: 'Olhe para trás',
21 +
body: 'Com o tempo, o Histórico e a Jornada mostram padrões entre humor, sono, gatilhos e ações de cuidado.',
22 +
},
23 +
];
24 +
25 +
export default function OnboardingScreen() {
26 +
const [index, setIndex] = useState(0);
27 +
const [saving, setSaving] = useState(false);
28 +
const { completeOnboarding } = useAuth();
29 +
const textColor = useThemeColor({}, 'text');
30 +
const backgroundColor = useThemeColor({}, 'background');
31 +
const tintColor = useThemeColor({}, 'tint');
32 +
33 +
const page = PAGES[index];
34 +
const isLast = index === PAGES.length - 1;
35 +
36 +
const finish = async () => {
37 +
if (saving) return;
38 +
39 +
setSaving(true);
40 +
try {
41 +
await completeOnboarding();
42 +
} catch (error) {
43 +
console.error('Failed to complete onboarding:', error);
44 +
} finally {
45 +
setSaving(false);
46 +
router.replace('/(tabs)/new');
47 +
}
48 +
};
49 +
50 +
return (
51 +
<SafeAreaView style={[styles.container, { backgroundColor }]}>
52 +
<View style={styles.content}>
53 +
<Text style={[styles.title, { color: textColor }]}>{page.title}</Text>
54 +
<Text style={[styles.body, { color: textColor, opacity: 0.7 }]}>
55 +
{page.body}
56 +
</Text>
57 +
</View>
58 +
59 +
<View style={styles.dots}>
60 +
{PAGES.map((_, i) => (
61 +
<View
62 +
key={i}
63 +
style={[
64 +
styles.dot,
65 +
{
66 +
backgroundColor: i === index ? tintColor : textColor,
67 +
opacity: i === index ? 1 : 0.2,
68 +
},
69 +
]}
70 +
/>
71 +
))}
72 +
</View>
73 +
74 +
<View style={styles.actions}>
75 +
<Button
76 +
title={isLast ? 'Começar' : 'Próximo'}
77 +
loading={saving}
78 +
onPress={() => (isLast ? finish() : setIndex((i) => i + 1))}
79 +
/>
80 +
{!isLast && (
81 +
<Button
82 +
variant="outline"
83 +
title="Pular"
84 +
onPress={finish}
85 +
disabled={saving}
86 +
/>
87 +
)}
88 +
</View>
89 +
</SafeAreaView>
90 +
);
91 +
}
92 +
93 +
const styles = StyleSheet.create({
94 +
container: {
95 +
flex: 1,
96 +
},
97 +
content: {
98 +
flex: 1,
99 +
justifyContent: 'center',
100 +
paddingHorizontal: 24,
101 +
},
102 +
title: {
103 +
fontSize: 28,
104 +
fontWeight: 'bold',
105 +
marginBottom: 12,
106 +
},
107 +
body: {
108 +
fontSize: 16,
109 +
lineHeight: 24,
110 +
},
111 +
dots: {
112 +
flexDirection: 'row',
113 +
justifyContent: 'center',
114 +
gap: 8,
115 +
marginBottom: 24,
116 +
},
117 +
dot: {
118 +
width: 8,
119 +
height: 8,
120 +
borderRadius: 4,
121 +
},
122 +
actions: {
123 +
gap: 12,
124 +
paddingHorizontal: 24,
125 +
paddingBottom: 24,
126 +
},
127 +
});
/dev/null → frontend/app/terms.tsx
1 +
import React from 'react';
2 +
import { Linking, ScrollView, StyleSheet, TouchableOpacity } from 'react-native';
3 +
import { Text } from '@/components/ui/Text';
4 +
import { useThemeColor } from '@/hooks/use-theme-color';
5 +
6 +
const WEBSITE_URL = 'https://usenexo.xyz';
7 +
8 +
export default function TermsScreen() {
9 +
const textColor = useThemeColor({}, 'text');
10 +
const tintColor = useThemeColor({}, 'tint');
11 +
const backgroundColor = useThemeColor({}, 'background');
12 +
13 +
return (
14 +
<ScrollView
15 +
style={{ backgroundColor }}
16 +
contentContainerStyle={styles.content}
17 +
showsVerticalScrollIndicator={false}
18 +
>
19 +
<Text style={[styles.title, { color: textColor }]}>
20 +
Termos e Condições
21 +
</Text>
22 +
23 +
<Text style={[styles.heading, { color: textColor }]}>
24 +
Um app acadêmico
25 +
</Text>
26 +
27 +
<Text style={[styles.body, { color: textColor }]}>
28 +
O nexo é um projeto acadêmico, desenvolvido como trabalho de conclusão de
29 +
curso. Ele não é um produto comercial nem um dispositivo médico, e não
30 +
substitui acompanhamento profissional. Em caso de crise, procure ajuda
31 +
médica ou um serviço de emergência.
32 +
</Text>
33 +
34 +
<Text style={[styles.heading, { color: textColor }]}>
35 +
Seus dados
36 +
</Text>
37 +
38 +
<Text style={[styles.body, { color: textColor }]}>
39 +
Os registros que você faz aqui são seus. Usamos seus dados apenas para o
40 +
app funcionar: montar o seu histórico, gerar a sua jornada de indicadores
41 +
e enviar os lembretes que você pedir. Não vendemos nem compartilhamos seus
42 +
dados com terceiros.
43 +
</Text>
44 +
45 +
<Text style={[styles.body, { color: textColor }]}>
46 +
Você pode exportar tudo o que registrou a qualquer momento, e pode pedir a
47 +
remoção da sua conta e dos seus dados. O código-fonte é auditável e aberto.
48 +
</Text>
49 +
50 +
<Text style={[styles.heading, { color: textColor }]}>
51 +
Saiba mais
52 +
</Text>
53 +
54 +
<Text style={[styles.body, { color: textColor }]}>
55 +
Para saber mais sobre o projeto, visite:
56 +
</Text>
57 +
58 +
<TouchableOpacity style={styles.linkWrapper}
59 +
onPress={() => Linking.openURL(WEBSITE_URL)}>
60 +
<Text style={[styles.link, { color: tintColor }]}>{WEBSITE_URL}</Text>
61 +
</TouchableOpacity>
62 +
</ScrollView>
63 +
);
64 +
}
65 +
66 +
const styles = StyleSheet.create({
67 +
content: {
68 +
paddingHorizontal: 24,
69 +
paddingTop: 16,
70 +
paddingBottom: 40,
71 +
},
72 +
title: {
73 +
fontSize: 24,
74 +
fontWeight: 'bold',
75 +
marginBottom: 16,
76 +
},
77 +
heading: {
78 +
fontSize: 18,
79 +
fontWeight: 'bold',
80 +
marginTop: 8,
81 +
marginBottom: 8,
82 +
},
83 +
body: {
84 +
fontSize: 16,
85 +
lineHeight: 24,
86 +
marginBottom: 16,
87 +
},
88 +
linkWrapper: {
89 +
marginBottom: 50
90 +
},
91 +
link: {
92 +
fontSize: 16,
93 +
textDecorationLine: 'underline',
94 +
},
95 +
});