fix(mobile): Tierliste nach Foto-Upload neu laden, Abschluss zeigt Foto

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 08:21:36 +02:00
1 parent d81f080077
commit abbfc56a7a
4 files changed
+32 -11

No files matched your search

+12 -2
View File
@@ -57,6 +57,7 @@ export default function OnboardingPetScreen() {
const [adoptionStory, setAdoptionStory] = useState("");
const [createdPet, setCreatedPet] = useState<{ id: string; name: string } | null>(null);
const [createError, setCreateError] = useState<string | null>(null);
const [photoUri, setPhotoUri] = useState<string | undefined>(undefined);
const pendingLegal = useQuery({ ...trpc.legal.getPendingUpdate.queryOptions(), staleTime: Infinity });
const consentStatus = useQuery({ ...trpc.legal.getUploadConsentStatus.queryOptions(), staleTime: Infinity });
@@ -188,7 +189,16 @@ export default function OnboardingPetScreen() {
/>
);
} else if (current === "photo" && createdPet) {
content = <AvatarStep petId={createdPet.id} petName={createdPet.name} onDone={next} />;
content = (
<AvatarStep
petId={createdPet.id}
petName={createdPet.name}
onDone={(uri) => {
setPhotoUri(uri);
next();
}}
/>
);
} else if (current === "spiders") {
content = <ArachnophobiaOptIn onContinue={next} />;
} else if (current === "source") {
@@ -200,7 +210,7 @@ export default function OnboardingPetScreen() {
<AutoAdvance onAdvance={next} />
);
} else {
content = <DoneStep petName={petName} onFinish={leave} />;
content = <DoneStep petName={petName} photoUri={photoUri} onFinish={leave} />;
}
return (
+3
View File
@@ -218,3 +218,6 @@ Neu 7:
- [x] Wunsch 09.10. (Obsidian „Mobile - UI Rework“): „Freunde einladen“ in den Einstellungen sieht nach AI-Slop aus; Onboarding dynamischer machen statt statischer Seiten. Nachtrag beim Test: Die Detailseite „Freunde einladen“ passte nicht mehr zum neuen Design.
→ **Umgesetzt (09.10.):** Neue `InviteCard` (`src/components/referrals/`) in den Einstellungen und oben auf `/invite`: Rang mit Abzeichen (ohne Rang gestrichelt „Dein erster Rang wartet“), markierbarer Code (Kopieren-Knopf bräuchte natives Modul), animierter Fortschrittsbalken, „Link teilen“. `/invite` neu: Ränge als Zeitleiste mit „Als Nächstes“, Status-Etiketten bei eingeladenen Tieren. Onboarding (`app/onboarding/pet.tsx` + `src/components/onboarding/flow/`): Willkommen mit 3 Regel-Karten + beiden Bestätigungen auf einer Seite → Name (Buchstabe live) → Tierart als Chips + Rasse → Beschreibung/Geschichte (legt Tier an) → Foto → Spinnen-Schalter (entfällt bei Spinne) → Herkunft (nur erstes Tier) → Abschluss mit Pfoten-Konfetti; Fortschrittsbalken, Zurück (nicht nach dem Anlegen), Android-Zurück-Geste geht einen Schritt zurück. Fund: Neue Nutzer sahen auf „Bevor es losgeht“ alle 7 Änderungsnotizen der Rechtstexte statt der Regeln — Regeln jetzt als feste App-Texte, `legal.acknowledge` unverändert (Web-Onboarding hat dasselbe Problem noch). Typecheck + Lint sauber, live im Android-Emulator (Dunkelmodus) getestet: Karte, `/invite`, kompletter Onboarding-Durchlauf mit Testtier „Testi“ (danach über „Tier löschen“ entfernt, DB geprüft). Einmal reagierte „Profil anlegen“ erst beim zweiten Tippen, nicht reproduziert. Hellmodus und iOS nicht auf Gerät geprüft. Per OTA ausgeliefert 09.10. (Runtime 1.0.6, Android + iOS).
- [x] Gefunden 09.10. (Video-Aufnahmen): Nach dem Onboarding zeigte „Meine Tiere“ das neue Tier nur mit Anfangsbuchstaben, obwohl das Foto hochgeladen war (bis zum App-Neustart).
→ **Behoben (09.10.):** `AvatarStep` lädt nach dem Upload die Tierliste neu (`pets.list` invalidiert). Außerdem zeigt der Abschluss-Screen des Onboardings jetzt das hochgeladene Foto statt des Buchstabens. Typecheck + Lint sauber, live im Emulator geprüft (Liste zeigt Foto sofort, Abschluss mit Foto).
@@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from "react";
import { View, Text, Image as RNImage, Pressable, ActivityIndicator, StyleSheet, Alert } from "react-native";
import * as ImagePicker from "expo-image-picker";
import { useTranslation } from "react-i18next";
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ImagePlus, Camera } from "lucide-react-native";
import { useTRPC } from "../../api/trpc";
import { uploadPickedImage } from "../../lib/upload";
@@ -14,7 +14,8 @@ import { useTheme } from "../../context/ThemeContext";
type Props = {
petId: string;
petName: string;
onDone: () => void;
/** Called with the local photo URI once uploaded, or without one when skipped. */
onDone: (uploadedPhotoUri?: string) => void;
};
/**
@@ -34,6 +35,8 @@ export function AvatarStep({ petId, petName, onDone }: Props) {
const [error, setError] = useState<string | null>(null);
const getPresignedUrlMutation = useMutation(trpc.media.getPresignedUrl.mutationOptions());
const queryClient = useQueryClient();
const [uploadedUri, setUploadedUri] = useState<string | undefined>(undefined);
const confirmUploadMutation = useMutation(trpc.media.confirmUpload.mutationOptions());
const pickAndUpload = useCallback(
@@ -47,11 +50,14 @@ export function AvatarStep({ petId, petName, onDone }: Props) {
try {
const { key } = await uploadPickedImage(picked, (file) => getPresignedUrlMutation.mutateAsync({ petId, ...file }));
await confirmUploadMutation.mutateAsync({ petId, key });
setUploadedUri(picked.uri);
// Lists still showed the letter avatar after onboarding until the app restarted.
queryClient.invalidateQueries({ queryKey: trpc.pets.list.queryOptions().queryKey });
} catch {
setError(t("onboarding.uploadError"));
}
},
[petId, getPresignedUrlMutation, confirmUploadMutation]
[petId, getPresignedUrlMutation, confirmUploadMutation, queryClient, trpc, t]
);
const pickFromGallery = useCallback(async () => {
@@ -67,7 +73,7 @@ export function AvatarStep({ petId, petName, onDone }: Props) {
return;
}
await pickAndUpload(() => ImagePicker.launchCameraAsync({ quality: 0.8 }));
}, [pickAndUpload]);
}, [pickAndUpload, t]);
const isUploading = getPresignedUrlMutation.isPending || confirmUploadMutation.isPending;
@@ -78,7 +84,7 @@ export function AvatarStep({ petId, petName, onDone }: Props) {
subtitle={t("onboarding.photoSubtitle", { name: petName })}
footer={
// Ohne „Überspringen“-Link (Apple 5.1.1(iv)): „Weiter“ führt auch ohne Foto weiter.
<Button title={t("common.continue")} onPress={onDone} disabled={isUploading} style={styles.cta} />
<Button title={t("common.continue")} onPress={() => onDone(uploadedUri)} disabled={isUploading} style={styles.cta} />
}
>
<View style={styles.photoRing}>
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { Animated, Easing, StyleSheet, Text, View } from "react-native";
import { Animated, Easing, Image, StyleSheet, Text, View } from "react-native";
import { useTranslation } from "react-i18next";
import { PawPrint } from "lucide-react-native";
import { useTheme } from "../../../context/ThemeContext";
@@ -11,10 +11,10 @@ import { fontFamily, getTypography, type ColorTokens } from "../../../theme/toke
const PAW_COUNT = 16;
const BURST_MS = 1300;
type Props = { petName: string; onFinish: () => void };
type Props = { petName: string; photoUri?: string; onFinish: () => void };
/** Closing moment after onboarding: the pet's badge pops in, paws burst around it. */
export function DoneStep({ petName, onFinish }: Props) {
export function DoneStep({ petName, photoUri, onFinish }: Props) {
const { t } = useTranslation();
const { colors } = useTheme();
const styles = useMemo(() => makeStyles(colors), [colors]);
@@ -40,7 +40,7 @@ export function DoneStep({ petName, onFinish }: Props) {
<View>
{isReducedMotion ? null : <PawBurst color={colors.marigoldOrange} />}
<Animated.View style={[styles.badge, { opacity: pop, transform: [{ scale }] }]}>
<Text style={styles.letter}>{letter}</Text>
{photoUri ? <Image source={{ uri: photoUri }} style={styles.photo} alt={petName} /> : <Text style={styles.letter}>{letter}</Text>}
</Animated.View>
</View>
<Reveal index={2}>
@@ -107,8 +107,10 @@ function makeStyles(colors: ColorTokens) {
justifyContent: "center",
borderWidth: 8,
borderColor: colors.marigoldTint,
overflow: "hidden",
},
letter: { fontFamily: fontFamily.extraBold, fontSize: 48, color: "#ffffff" },
photo: { width: "100%", height: "100%" },
title: {
fontFamily: fontFamily.extraBold,
fontSize: 28,