diff --git a/messages/de.json b/messages/de.json index 952a1b3..6de5b44 100644 --- a/messages/de.json +++ b/messages/de.json @@ -126,7 +126,11 @@ "addPhotoDesc": "Gib deinem Tier ein Gesicht auf PawFeed. Du kannst das jederzeit ändern.", "done": "Fertig", "skipForNow": "Jetzt überspringen", - "loading": "Wird geladen" + "loading": "Wird geladen", + "caretakerSwitch": "Ich betreue ein Tier mit", + "caretakerTitle": "Ein Tier mitbetreuen", + "caretakerDesc": "Gib den Einladungscode ein, den du vom Haupt-Besitzer bekommen hast. Sobald er deine Anfrage freigibt, kannst du loslegen.", + "caretakerBack": "Doch ein eigenes Tier anlegen" }, "Language": { "switch": "Sprache", @@ -490,7 +494,8 @@ "typeUnknown": "hat eine neue Mitteilung für dich.", "typePetDeceased": "ist über die Regenbogenbrücke gegangen. In liebevoller Erinnerung 🤍", "typePetLost": "wird vermisst. Vielleicht kannst du helfen – tippe für alle Infos.", - "typePetFound": "ist wieder sicher zu Hause. Danke fürs Mithelfen! 🎉" + "typePetFound": "ist wieder sicher zu Hause. Danke fürs Mithelfen! 🎉", + "typeCaretakerRequest": "Jemand möchte dein Tier mitbetreuen – bitte prüfe die Anfrage." }, "PhotoPostForm": { "captionPlaceholder": "Bildunterschrift schreiben…", @@ -646,7 +651,8 @@ "title": "Meine Tiere", "addPet": "Tier anlegen", "emptyTitle": "Füge dein erstes Tier hinzu", - "emptyDesc": "Gib deinem Tier ein eigenes Profil auf PawFeed." + "emptyDesc": "Gib deinem Tier ein eigenes Profil auf PawFeed.", + "caretakerHeading": "Ich betreue ein Tier mit" }, "PetProfile": { "editProfile": "Profil bearbeiten", @@ -936,5 +942,40 @@ "bannerTitle": "{name} wird vermisst", "bannerText": "Zuletzt gesehen: {place} · {date}", "bannerLink": "Alle Infos & Seite teilen" + }, + "Caretaker": { + "heading": "Mitbetreuer", + "intro": "Kümmert sich noch jemand um {name}? Lade eine zweite Person ein – sie nutzt ihr eigenes Konto und kann als {name} posten, Nachrichten beantworten und die Gesundheitsdaten sehen.", + "createInvite": "Mitbetreuer einladen", + "inviteHint": "Gib diesen Code an die Person weiter. Sie gibt ihn in der App unter „Meine Tiere“ oder bei der Anmeldung ein. Danach entscheidest du, ob du die Anfrage freigibst.", + "copy": "Code kopieren", + "copyLink": "Link kopieren", + "copied": "Kopiert", + "validUntil": "Gültig bis {date}", + "newCode": "Neuen Code erzeugen", + "revokeInvite": "Einladung zurückziehen", + "requestFrom": "Anfrage zur Mitbetreuung von {who}", + "requestHint": "Gibst du frei, kann diese Person als {name} handeln. Löschen, Privatsphäre und Gedenkmodus bleiben bei dir.", + "approve": "Freigeben", + "reject": "Ablehnen", + "approved": "Freigegeben – ihr kümmert euch jetzt gemeinsam.", + "activeWith": "{who} betreut {name} mit dir.", + "remove": "Mitbetreuer entfernen", + "removeConfirm": "Soll die Person {name} nicht mehr mitbetreuen? Ihre bisherigen Beiträge als {name} bleiben erhalten.", + "youCoCare": "Du betreust {name} mit.", + "caretakerLimits": "Löschen, Privatsphäre und Gedenkmodus verwaltet der Haupt-Besitzer.", + "leave": "Mitbetreuung beenden", + "leaveConfirm": "Möchtest du {name} nicht mehr mitbetreuen? Du kannst später wieder eingeladen werden.", + "left": "Du betreust {name} nicht mehr mit.", + "cancel": "Abbrechen", + "unknownAccount": "einem anderen Konto", + "failed": "Das hat leider nicht geklappt.", + "codeLabel": "Einladungscode", + "codeHint": "Den Code bekommst du vom Haupt-Besitzer des Tiers. Er ist 7 Tage gültig.", + "redeem": "Einlösen", + "requestSent": "Anfrage gesendet – sobald sie freigegeben ist, findest du {name} unter „Meine Tiere“.", + "waiting": "Deine Anfrage für {name} wartet auf Freigabe.", + "checkAgain": "Erneut prüfen", + "withdraw": "Zurückziehen" } } diff --git a/messages/en.json b/messages/en.json index e6de15c..9dcea03 100644 --- a/messages/en.json +++ b/messages/en.json @@ -126,7 +126,11 @@ "addPhotoDesc": "Give your pet a face on PawFeed. You can always change this later.", "done": "Done", "skipForNow": "Skip for now", - "loading": "Loading" + "loading": "Loading", + "caretakerSwitch": "I co-care for a pet", + "caretakerTitle": "Co-care for a pet", + "caretakerDesc": "Enter the invitation code you got from the main owner. As soon as they approve your request, you're all set.", + "caretakerBack": "Create my own pet instead" }, "Language": { "switch": "Language", @@ -490,7 +494,8 @@ "typeUnknown": "has a new notification for you.", "typePetDeceased": "has crossed the rainbow bridge. In loving memory 🤍", "typePetLost": "is missing. Maybe you can help – tap for all details.", - "typePetFound": "is safely back home. Thanks for helping! 🎉" + "typePetFound": "is safely back home. Thanks for helping! 🎉", + "typeCaretakerRequest": "Someone would like to co-care for your pet – please review the request." }, "PhotoPostForm": { "captionPlaceholder": "Write a caption…", @@ -646,7 +651,8 @@ "title": "My Pets", "addPet": "Add Pet", "emptyTitle": "Add your first pet", - "emptyDesc": "Give your pet their own profile on PawFeed." + "emptyDesc": "Give your pet their own profile on PawFeed.", + "caretakerHeading": "I co-care for a pet" }, "PetProfile": { "editProfile": "Edit profile", @@ -936,5 +942,40 @@ "bannerTitle": "{name} is missing", "bannerText": "Last seen: {place} · {date}", "bannerLink": "All details & share page" + }, + "Caretaker": { + "heading": "Co-caretaker", + "intro": "Does someone else look after {name} too? Invite a second person – they use their own account and can post as {name}, answer messages and see the health records.", + "createInvite": "Invite a co-caretaker", + "inviteHint": "Share this code with that person. They enter it under “My pets” or during sign-up. You then decide whether to approve the request.", + "copy": "Copy code", + "copyLink": "Copy link", + "copied": "Copied", + "validUntil": "Valid until {date}", + "newCode": "Create new code", + "revokeInvite": "Withdraw invitation", + "requestFrom": "Co-care request from {who}", + "requestHint": "If you approve, this person can act as {name}. Deleting, privacy and memorial mode stay with you.", + "approve": "Approve", + "reject": "Decline", + "approved": "Approved – you now care for this pet together.", + "activeWith": "{who} co-cares for {name} with you.", + "remove": "Remove co-caretaker", + "removeConfirm": "Should this person stop co-caring for {name}? Their posts as {name} stay.", + "youCoCare": "You co-care for {name}.", + "caretakerLimits": "Deleting, privacy and memorial mode are managed by the main owner.", + "leave": "Stop co-caring", + "leaveConfirm": "Do you want to stop co-caring for {name}? You can be invited again later.", + "left": "You no longer co-care for {name}.", + "cancel": "Cancel", + "unknownAccount": "another account", + "failed": "Sorry, that didn't work.", + "codeLabel": "Invitation code", + "codeHint": "You get the code from the pet's main owner. It is valid for 7 days.", + "redeem": "Redeem", + "requestSent": "Request sent – once approved, you'll find {name} under “My pets”.", + "waiting": "Your request for {name} is waiting for approval.", + "checkAgain": "Check again", + "withdraw": "Withdraw" } } diff --git a/mobile/app/(app)/(tabs)/notifications.tsx b/mobile/app/(app)/(tabs)/notifications.tsx index 40a159a..cc1b1ff 100644 --- a/mobile/app/(app)/(tabs)/notifications.tsx +++ b/mobile/app/(app)/(tabs)/notifications.tsx @@ -38,6 +38,7 @@ const SELF_MESSAGE_KEYS: Partial> = { BIRTHDAY: "notifications.birthday", ADOPTION_DAY: "notifications.adoptionDay", REFERRAL_RANK_UP: "notifications.referralRankUp", + CARETAKER_REQUEST: "notifications.caretakerRequest", }; // Suffix only — the actor's name is rendered separately as its own tappable diff --git a/mobile/app/(app)/(tabs)/pets.tsx b/mobile/app/(app)/(tabs)/pets.tsx index 0f15286..42a6dac 100644 --- a/mobile/app/(app)/(tabs)/pets.tsx +++ b/mobile/app/(app)/(tabs)/pets.tsx @@ -11,6 +11,7 @@ import { Card } from "../../../src/components/Card"; import { formatPetName } from "../../../src/lib/format"; import { getTypography, type ColorTokens, type Typography } from "../../../src/theme/tokens"; import type { Pet } from "../../../src/api/types"; +import { CaretakerRedeem } from "../../../src/components/caretaker/CaretakerRedeem"; /** * List of the owner's own pets (pets.list()). Tapping a row opens that @@ -60,6 +61,15 @@ export default function PetsScreen() { {t("pets.empty")} } + // Mitbetreuer: Einladungscode eines anderen Kontos einlösen + ListFooterComponent={ + + {t("caretaker.redeemHeading")} + + + } + automaticallyAdjustKeyboardInsets + keyboardShouldPersistTaps="handled" /> ); } @@ -112,6 +122,8 @@ function makeStyles(colors: ColorTokens) { return StyleSheet.create({ list: { padding: 16, flexGrow: 1, backgroundColor: colors.porchWhite }, separator: { height: 12 }, + caretaker: { marginTop: 32, gap: 8 }, + caretakerHeading: { ...typography.title, fontSize: 16 }, center: { flex: 1, alignItems: "center", justifyContent: "center", backgroundColor: colors.porchWhite }, row: { flexDirection: "row", alignItems: "center", gap: 12 }, rowText: { flex: 1, gap: 2 }, diff --git a/mobile/app/(app)/pets/[petId]/edit.tsx b/mobile/app/(app)/pets/[petId]/edit.tsx index e44e11d..48e35ff 100644 --- a/mobile/app/(app)/pets/[petId]/edit.tsx +++ b/mobile/app/(app)/pets/[petId]/edit.tsx @@ -14,6 +14,7 @@ import { BreedPicker } from "../../../../src/components/pet/BreedPicker"; import { PrivacySettings } from "../../../../src/components/pet/PrivacySettings"; import { MemorialSettings } from "../../../../src/components/pet/MemorialSettings"; import { LostReportSettings } from "../../../../src/components/lost/LostReportSettings"; +import { CaretakerSettings } from "../../../../src/components/caretaker/CaretakerSettings"; import { radii, getTypography, type ColorTokens, type Typography } from "../../../../src/theme/tokens"; import { displayDateToIso, isoToDisplayDate } from "../../../../src/lib/date-format"; import { useBottomSafePadding } from "../../../../src/hooks/useBottomSafePadding"; @@ -121,6 +122,9 @@ export default function EditPetScreen() { ); } + // Ältere Server ohne Mitbetreuer liefern keine Rolle → wie bisher Haupt-Besitzer. + const isMainOwner = pet.role !== "CARETAKER"; + return ( // FlatList, not ScrollView — kept from the original nested-ScrollView // fix (see onboarding/pet.tsx's comment); OptionPicker's dropdown now @@ -175,19 +179,28 @@ export default function EditPetScreen() { - -

- {dmPolicy === "FOLLOWERS_ONLY" ? t("dmFollowersOnlyHint") : t("dmEveryoneHint")} -

- + {/* Privatsphäre, Gedenkmodus, Löschen: nur der Haupt-Besitzer (Mitbetreuer sieht sie nicht) */} + {isMainOwner && ( + <> + +
+

{t("privacyHeading")}

+
+ +
+ + +
+

+ {dmPolicy === "FOLLOWERS_ONLY" + ? t("dmFollowersOnlyHint") + : t("dmEveryoneHint")} +

+
-
- -
- - +
+ +
+ + +
+

+ {followerVisibility === "FOLLOWERS_ONLY" + ? t("followerFollowersOnlyHint") + : followerVisibility === "NOBODY" + ? t("followerNobodyHint") + : t("followerEveryoneHint")} +

+
-

- {followerVisibility === "FOLLOWERS_ONLY" - ? t("followerFollowersOnlyHint") - : followerVisibility === "NOBODY" - ? t("followerNobodyHint") - : t("followerEveryoneHint")} -

-
-
+ + )} {/* Verification */} @@ -258,11 +304,14 @@ export default function EditPetPage() {

{t("pendingStatus")}

) : (
- {verificationRequest?.status === "REJECTED" && verificationRequest.reviewNote && ( -

- {t("rejectedPrefix", { reason: verificationRequest.reviewNote })} -

- )} + {verificationRequest?.status === "REJECTED" && + verificationRequest.reviewNote && ( +

+ {t("rejectedPrefix", { + reason: verificationRequest.reviewNote, + })} +

+ )}

{t("criteriaHint")}