feat: add UI screens design
This commit is contained in:
@@ -1,104 +1,382 @@
|
||||
import { View, Text, StyleSheet, Alert } from "react-native";
|
||||
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 { useRouter } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Redirect, useRouter } from "expo-router";
|
||||
|
||||
import { COLORS } from "../constants/colors";
|
||||
import PrimaryButton from "../components/PrimaryButton";
|
||||
|
||||
import { useApp } from "../context/AppContext";
|
||||
import { resetDemo } from "../services/tracking.service";
|
||||
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, refreshStatus } = useApp();
|
||||
const router = useRouter();
|
||||
const { user, logout } = useApp();
|
||||
const router = useRouter();
|
||||
const [myAddress, setMyAddress] = useState<MyAddress | null>(null);
|
||||
|
||||
const handleResetDemo = async () => {
|
||||
try {
|
||||
await resetDemo();
|
||||
await refreshStatus();
|
||||
Alert.alert("Demo reiniciado", "Estado y notificaciones borradas.");
|
||||
} catch (err) {
|
||||
Alert.alert(
|
||||
"Error",
|
||||
err instanceof Error ? err.message : "No se pudo reiniciar",
|
||||
);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
void getMyAddress().then(setMyAddress).catch(() => {});
|
||||
}, [user]);
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>No has iniciado sesión</Text>
|
||||
<PrimaryButton
|
||||
title="Iniciar sesión"
|
||||
onPress={() => router.push("/login")}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
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",
|
||||
});
|
||||
Alert.alert("Demo reiniciada", "Las notificaciones se borraron.");
|
||||
} catch (err) {
|
||||
Alert.alert(
|
||||
"No se pudo reiniciar",
|
||||
err instanceof Error ? err.message : "Error",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
router.replace("/login");
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>Hola, {user.name}</Text>
|
||||
<Text style={styles.email}>{user.email}</Text>
|
||||
|
||||
<View style={{ height: 24 }} />
|
||||
|
||||
<PrimaryButton
|
||||
title="Mi domicilio"
|
||||
onPress={() => router.push("/addresses")}
|
||||
/>
|
||||
|
||||
<View style={{ height: 12 }} />
|
||||
|
||||
<PrimaryButton
|
||||
title="Buzón de retroalimentación"
|
||||
onPress={() => router.push("/feedback")}
|
||||
/>
|
||||
|
||||
<View style={{ height: 12 }} />
|
||||
|
||||
<PrimaryButton
|
||||
title="Reiniciar demo"
|
||||
onPress={handleResetDemo}
|
||||
/>
|
||||
|
||||
<View style={{ height: 12 }} />
|
||||
|
||||
<PrimaryButton title="Cerrar sesión" onPress={handleLogout} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
const handleHelp = () => {
|
||||
Alert.alert(
|
||||
"Ayuda",
|
||||
"EcoRuta 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({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: COLORS.background,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
padding: 24,
|
||||
justifyContent: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
marginBottom: 6,
|
||||
},
|
||||
email: {
|
||||
fontSize: 14,
|
||||
color: "#6B7280",
|
||||
textAlign: "center",
|
||||
marginBottom: 16,
|
||||
},
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user