diff --git a/mobile/app/(app)/_layout.tsx b/mobile/app/(app)/_layout.tsx index 47d9cca..6bcb795 100644 --- a/mobile/app/(app)/_layout.tsx +++ b/mobile/app/(app)/_layout.tsx @@ -49,6 +49,7 @@ export default function AppLayout() { + diff --git a/mobile/app/(app)/lost/[petId].tsx b/mobile/app/(app)/lost/[petId].tsx new file mode 100644 index 0000000..e0e2e47 --- /dev/null +++ b/mobile/app/(app)/lost/[petId].tsx @@ -0,0 +1,242 @@ +import { useMemo } from "react"; +import { View, Text, ScrollView, Pressable, Image, ActivityIndicator, Linking, Share, StyleSheet } from "react-native"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { useTranslation } from "react-i18next"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { Clock, Info, Mail, MapPin, MessageCircle, Phone, Share2, Siren } from "lucide-react-native"; +import { useTRPC } from "../../../src/api/trpc"; +import { useActivePet } from "../../../src/context/ActivePetContext"; +import { useTheme } from "../../../src/context/ThemeContext"; +import { Button } from "../../../src/components/Button"; +import { env } from "../../../src/lib/env"; +import { getMediaUrl, getAvatarUrl } from "../../../src/lib/media-url"; +import { isoToDisplayDate } from "../../../src/lib/date-format"; +import { useBottomSafePadding } from "../../../src/hooks/useBottomSafePadding"; +import { radii, getTypography, type ColorTokens, type Typography } from "../../../src/theme/tokens"; + +const RED = "#dc2626"; + +function pad(n: number): string { + return String(n).padStart(2, "0"); +} + +/** „04.10.2026, 14:30“ in Ortszeit des Geräts. */ +function formatDateTime(value: Date | string): string { + const d = new Date(value); + const iso = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; + return `${isoToDisplayDate(iso)}, ${pad(d.getHours())}:${pad(d.getMinutes())}`; +} + +/** + * Vermisst-Ansicht in der App — dieselben Angaben wie die öffentliche Seite + * /vermisst/, ohne die App zu verlassen. Ziel des Profil-Banners und des + * Antippens einer PET_LOST-Mitteilung (src/lib/notification-route.ts). + */ +export default function LostPetScreen() { + const { petId } = useLocalSearchParams<{ petId: string }>(); + const { t } = useTranslation(); + const trpc = useTRPC(); + const router = useRouter(); + const { activePetId, pets } = useActivePet(); + const { colors } = useTheme(); + const styles = useMemo(() => makeStyles(colors), [colors]); + const bottomPadding = useBottomSafePadding(); + + const { data: report, isLoading } = useQuery(trpc.lostReports.byPet.queryOptions({ petId })); + const openConversation = useMutation( + trpc.conversations.getOrCreate.mutationOptions({ + onSuccess: (conversation) => router.push(`/messages/${conversation.id}`), + }) + ); + + if (isLoading) { + return ( + + + + ); + } + + if (!report) { + return ( + + {t("lostView.notActiveTitle")} + {t("lostView.notActiveText")} + + ))} + + + + {isLoading ? ( + + ) : !data?.reports.length ? ( +
+ + Keine Meldungen in dieser Ansicht. +
+ ) : ( +
    + {data.reports.map((r) => { + const isActive = r.foundAt == null; + return ( +
  • +
    + + + {r.pet.name.slice(0, 2).toUpperCase()} + +
    + + {r.pet.name} + +

    gemeldet {DATE_FORMAT.format(new Date(r.createdAt))}

    +
    + {isActive ? ( + Aktiv + ) : ( + Wieder da · {DATE_FORMAT.format(new Date(r.foundAt!))} + )} +
    + +
    +

    + + {r.lastSeenPlace} · {DATE_FORMAT.format(new Date(r.lastSeenAt))} +

    + {r.description &&

    {r.description}

    } +
    + +
    + + + {r.notifiedCount} benachrichtigt + + {r.contactPhone && ( + + + Telefon + + )} + {r.contactEmail && ( + + + E-Mail + + )} + {r.allowIndexing ? "Suchmaschinen erlaubt" : "Nur per Link"} +
    + +
    + + Öffentliche Seite + +
    + {isActive && ( + + )} + +
    +
    +
  • + ); + })} +
+ )} + + !open && setTarget(null)}> + + + + {target?.mode === "DELETE" ? `Meldung zu ${target?.name} löschen?` : `Meldung zu ${target?.name} schließen?`} + + + {target?.mode === "DELETE" + ? "Für Missbrauch oder Fake-Meldungen. Die öffentliche Seite ist danach nicht mehr erreichbar. Es geht keine Mitteilung raus." + : "Für vergessene Meldungen. Das Abzeichen verschwindet, die öffentliche Seite zeigt „wieder zu Hause“. Es geht keine Entwarnung raus."} + + +