Files
hackathon-opti-1a67c9077937…/frontend/src/app/profile.tsx
2026-05-23 07:41:56 -06:00

384 lines
9.5 KiB
TypeScript

import { useEffect, useState } from "react";
import {
View,
Text,
StyleSheet,
ScrollView,
Image,
Pressable,
Alert,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Ionicons } from "@expo/vector-icons";
import { Redirect, useRouter } from "expo-router";
import { COLORS } from "../constants/colors";
import { useApp } from "../context/AppContext";
import { getMyAddress, type MyAddress } from "../services/addresses.service";
import { apiFetch } from "../lib/api";
type Row = {
key: string;
title: string;
subtitle: string;
icon: keyof typeof Ionicons.glyphMap;
color: string;
onPress: () => void;
danger?: boolean;
};
export default function ProfileScreen() {
const { user, logout } = useApp();
const router = useRouter();
const [myAddress, setMyAddress] = useState<MyAddress | null>(null);
useEffect(() => {
if (!user) return;
void getMyAddress().then(setMyAddress).catch(() => {});
}, [user]);
if (!user) return <Redirect href="/login" />;
const handleLogout = () => {
logout();
router.replace("/login");
};
const handleReset = async () => {
try {
await apiFetch<{ message: string }>("/api/tracking/reset-demo", {
method: "POST",
body: JSON.stringify({}),
});
Alert.alert("Demo reiniciada", "Las notificaciones se borraron.");
} catch (err) {
Alert.alert(
"No se pudo reiniciar",
err instanceof Error ? err.message : "Error",
);
}
};
const handleHelp = () => {
Alert.alert(
"Ayuda",
"OptiRuta te avisa cuando el camión recolector está cerca.\n\n" +
"• Tu dirección define la ruta que verás.\n" +
"• Las alertas se actualizan cada 30 segundos.\n" +
"• Usa el buzón si el camión no pasó.",
);
};
const rows: Row[] = [
{
key: "address",
title: "Mi domicilio",
subtitle: "Ver y administrar tus domicilios registrados",
icon: "home-outline",
color: "#0E8A61",
onPress: () => router.push("/addresses"),
},
{
key: "feedback",
title: "Buzón de retroalimentación",
subtitle: "Sugerencias, comentarios y reportes",
icon: "chatbubble-outline",
color: "#0E8A61",
onPress: () => router.push("/feedback"),
},
{
key: "reset",
title: "Reiniciar demo",
subtitle: "Restablecer la simulación y los datos",
icon: "refresh-outline",
color: "#0E8A61",
onPress: handleReset,
},
{
key: "help",
title: "Ayuda",
subtitle: "Preguntas frecuentes y soporte",
icon: "help-circle-outline",
color: "#0E8A61",
onPress: handleHelp,
},
{
key: "logout",
title: "Cerrar sesión",
subtitle: "Salir de tu cuenta",
icon: "log-out-outline",
color: "#EF4444",
onPress: handleLogout,
danger: true,
},
];
const initial = (user.name?.[0] ?? user.email[0]).toUpperCase();
return (
<SafeAreaView
style={{ flex: 1, backgroundColor: COLORS.background }}
edges={["bottom"]}
>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 130 }}
>
{/* Header verde con avatar */}
<View style={styles.heroWrap}>
<View style={styles.heroBackground} />
<View style={styles.avatarOuter}>
<View style={styles.avatarInner}>
<Text style={styles.avatarText}>{initial}</Text>
</View>
</View>
</View>
<View style={styles.identityBox}>
<Text style={styles.name}>
Hola, {user.name} <Text style={{ color: "#22C55E" }}>🌿</Text>
</Text>
<View style={styles.emailRow}>
<Ionicons name="mail-outline" size={14} color="#6B7280" />
<Text style={styles.email}>{user.email}</Text>
</View>
</View>
{/* Zona + ruta */}
<Pressable
style={styles.zoneRow}
onPress={() => router.push("/addresses")}
>
<View style={styles.zoneCol}>
<Ionicons name="location-outline" size={22} color="#0E8A61" />
<View style={{ marginLeft: 8, flex: 1 }}>
<Text style={styles.zoneLabel}>Tu zona</Text>
<Text style={styles.zoneValue} numberOfLines={2}>
{myAddress?.colonia ?? "Sin asignar"}
</Text>
</View>
</View>
<View style={styles.zoneDivider} />
<View style={styles.zoneCol}>
<Ionicons name="bus-outline" size={22} color="#0E8A61" />
<View style={{ marginLeft: 8, flex: 1 }}>
<Text style={styles.zoneLabel}>Ruta asignada</Text>
<Text style={styles.zoneValue} numberOfLines={1}>
{myAddress?.routeId ?? "—"}
</Text>
</View>
</View>
<Ionicons
name="chevron-forward"
size={18}
color="#9CA3AF"
style={{ marginLeft: 4 }}
/>
</Pressable>
{/* Lista de opciones */}
<View style={{ paddingHorizontal: 16, marginTop: 4 }}>
{rows.map((r) => (
<Pressable key={r.key} style={styles.rowCard} onPress={r.onPress}>
<View
style={[
styles.rowIcon,
{ backgroundColor: `${r.color}15` },
]}
>
<Ionicons name={r.icon} size={22} color={r.color} />
</View>
<View style={{ flex: 1 }}>
<Text
style={[
styles.rowTitle,
r.danger && { color: "#EF4444" },
]}
>
{r.title}
</Text>
<Text style={styles.rowSubtitle}>{r.subtitle}</Text>
</View>
<Ionicons name="chevron-forward" size={18} color="#9CA3AF" />
</Pressable>
))}
</View>
{/* Tu impacto cuenta */}
<View style={styles.impactCard}>
<View style={styles.impactLeaf}>
<Image
source={require("../../assets/illustrations/impact-leaf.png")}
style={{ width: 40, height: 40 }}
resizeMode="contain"
/>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.impactTitle}>Tu impacto cuenta</Text>
<Text style={styles.impactBody}>
Siguiendo los horarios y separando correctamente, haces tu ciudad
más limpia.
</Text>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
heroWrap: {
height: 140,
backgroundColor: "#D1FAE5",
overflow: "visible",
marginBottom: 60,
},
heroBackground: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "#D1FAE5",
},
avatarOuter: {
position: "absolute",
bottom: -50,
alignSelf: "center",
width: 112,
height: 112,
borderRadius: 56,
backgroundColor: "#FFFFFF",
justifyContent: "center",
alignItems: "center",
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 6,
},
avatarInner: {
width: 100,
height: 100,
borderRadius: 50,
backgroundColor: "#0E8A61",
justifyContent: "center",
alignItems: "center",
},
avatarText: {
color: "#FFFFFF",
fontSize: 38,
fontWeight: "800",
},
identityBox: {
alignItems: "center",
marginBottom: 18,
},
name: {
fontSize: 22,
fontWeight: "800",
color: "#0F172A",
},
emailRow: {
flexDirection: "row",
alignItems: "center",
marginTop: 6,
},
email: {
fontSize: 13,
color: "#6B7280",
marginLeft: 6,
},
zoneRow: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#ECFDF5",
borderRadius: 16,
padding: 14,
marginHorizontal: 16,
marginBottom: 16,
},
zoneCol: {
flexDirection: "row",
alignItems: "center",
flex: 1,
},
zoneDivider: {
width: 1,
height: 36,
backgroundColor: "#A7F3D0",
marginHorizontal: 8,
},
zoneLabel: {
fontSize: 11,
color: "#065F46",
fontWeight: "700",
},
zoneValue: {
fontSize: 13,
color: "#0F172A",
fontWeight: "600",
marginTop: 2,
},
rowCard: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#FFFFFF",
borderRadius: 14,
padding: 14,
marginBottom: 10,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 6,
elevation: 2,
},
rowIcon: {
width: 42,
height: 42,
borderRadius: 21,
justifyContent: "center",
alignItems: "center",
marginRight: 12,
},
rowTitle: {
fontSize: 15,
fontWeight: "700",
color: "#0F172A",
},
rowSubtitle: {
fontSize: 12,
color: "#6B7280",
marginTop: 2,
},
impactCard: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#ECFDF5",
borderRadius: 16,
padding: 14,
marginHorizontal: 16,
marginTop: 8,
},
impactLeaf: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: "#FFFFFF",
justifyContent: "center",
alignItems: "center",
marginRight: 12,
},
impactTitle: {
fontSize: 14,
fontWeight: "800",
color: "#065F46",
marginBottom: 4,
},
impactBody: {
fontSize: 12,
color: "#065F46",
lineHeight: 16,
},
});