diff --git a/messages/de.json b/messages/de.json index 110a13f..d984dc6 100644 --- a/messages/de.json +++ b/messages/de.json @@ -64,6 +64,8 @@ "namePlaceholder": "Name eingeben", "nameRequired": "Dieses Feld ist erforderlich.", "nameMaxLength": "Der Name darf maximal 30 Zeichen lang sein.", + "nickname": "Rufname / Spitzname (optional)", + "nicknamePlaceholder": "z. B. Rexi", "species": "Tierart", "selectSpecies": "Tierart auswählen", "speciesRequired": "Bitte wähle eine Tierart aus.", diff --git a/messages/en.json b/messages/en.json index 06c21bd..6e1377c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -64,6 +64,8 @@ "namePlaceholder": "Enter pet name", "nameRequired": "This field is required.", "nameMaxLength": "Pet name must be 30 characters or fewer.", + "nickname": "Nickname (optional)", + "nicknamePlaceholder": "e.g. Rexy", "species": "Species", "selectSpecies": "Select species", "speciesRequired": "Please select a species.", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5be9fe2..c26e58b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -147,6 +147,7 @@ model Pet { breed Breed? @relation(fields: [breedId], references: [id]) breedId String? name String + nickname String? @db.VarChar(30) // Rufname/Spitzname; shown after the real name, e.g. "Rex (Rexi)" — see src/lib/pet-display-name.ts bio String? adoptionStory String? avatarKey String? // Supabase Storage object key, NOT a full URL diff --git a/src/app/(app)/pets/[petId]/edit/page.tsx b/src/app/(app)/pets/[petId]/edit/page.tsx index 1d4b497..5af1d05 100644 --- a/src/app/(app)/pets/[petId]/edit/page.tsx +++ b/src/app/(app)/pets/[petId]/edit/page.tsx @@ -134,6 +134,7 @@ export default function EditPetPage() { initialAvatarKey={pet.avatarKey ?? null} initialValues={{ name: pet.name, + nickname: pet.nickname ?? null, speciesId: pet.speciesId, breedId: pet.breedId ?? null, bio: pet.bio ?? null, diff --git a/src/app/(app)/pets/[petId]/page.tsx b/src/app/(app)/pets/[petId]/page.tsx index 5b9b0de..adb9f43 100644 --- a/src/app/(app)/pets/[petId]/page.tsx +++ b/src/app/(app)/pets/[petId]/page.tsx @@ -1,4 +1,5 @@ import { notFound } from "next/navigation"; +import { formatPetName } from "@/lib/pet-display-name"; import Link from "next/link"; import { currentUser } from "@clerk/nextjs/server"; import { getTranslations } from "next-intl/server"; @@ -71,7 +72,7 @@ export default async function PetProfilePage({ params }: PageProps) { />

- {pet.name} + {formatPetName(pet)} {pet.isVerified && ( {/* ProfileActions: Follow + Message + options menu (client component) */} - + {pet.bio && ( <> diff --git a/src/app/p/[secret]/users/page.tsx b/src/app/p/[secret]/users/page.tsx index b8a6930..1dd09df 100644 --- a/src/app/p/[secret]/users/page.tsx +++ b/src/app/p/[secret]/users/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { formatPetName } from "@/lib/pet-display-name"; import { useRouter, useParams } from "next/navigation"; import { toast } from "sonner"; import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; @@ -122,7 +123,7 @@ export default function AdminUsersPage() { trpc.admin.setPetVerified.mutationOptions({ onSuccess: (pet) => { void qc.invalidateQueries({ queryKey: trpc.admin.listUsers.queryKey() }); - toast.success(pet.isVerified ? `${pet.name} verified.` : `${pet.name} un-verified.`); + toast.success(pet.isVerified ? `${formatPetName(pet)} verified.` : `${formatPetName(pet)} un-verified.`); }, onError: (err) => { toast.error(`Verification failed: ${err.message}`); @@ -274,7 +275,7 @@ export default function AdminUsersPage() { : "border-zinc-700 text-zinc-400 hover:border-zinc-600" }`} > - {pet.name} + {formatPetName(pet)} diff --git a/src/components/feed/MilestoneCard.tsx b/src/components/feed/MilestoneCard.tsx index 11eb462..1f50b71 100644 --- a/src/components/feed/MilestoneCard.tsx +++ b/src/components/feed/MilestoneCard.tsx @@ -3,6 +3,7 @@ import type { MilestoneType } from "@prisma/client"; import { MILESTONE_META } from "@/lib/milestone-meta"; import { getAvatarUrl } from "@/lib/avatar-url"; +import { formatPetName } from "@/lib/pet-display-name"; import { getMediaUrl } from "@/lib/media-url"; import { BlurImage } from "@/components/ui/blur-image"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -60,7 +61,7 @@ export function MilestoneCard({ post }: MilestoneCardProps) {

- {pet.name} + {formatPetName(pet)}

{formatDate(post.createdAt)}

diff --git a/src/components/feed/PostCard.tsx b/src/components/feed/PostCard.tsx index e062044..7290fa7 100644 --- a/src/components/feed/PostCard.tsx +++ b/src/components/feed/PostCard.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect, useCallback, memo } from "react"; +import { formatPetName } from "@/lib/pet-display-name"; import { MoreHorizontal, MessageCircle, Sparkles, BadgeCheck } from "lucide-react"; import { toast } from "sonner"; import { useMutation } from "@tanstack/react-query"; @@ -246,7 +247,7 @@ function PostCardImpl({ post, onDeleted, variant = "feed" }: PostCardProps) {

- {pet.name} + {formatPetName(pet)} {pet.isVerified && ( )} diff --git a/src/components/feed/PostDetailDialog.tsx b/src/components/feed/PostDetailDialog.tsx index 1a6c4ea..97f78f6 100644 --- a/src/components/feed/PostDetailDialog.tsx +++ b/src/components/feed/PostDetailDialog.tsx @@ -5,6 +5,7 @@ import { MoreHorizontal, Trash2, Loader2 } from "lucide-react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { useTRPC } from "@/trpc/client"; +import { formatPetName } from "@/lib/pet-display-name"; import { useActivePet } from "@/context/ActivePetContext"; import { Dialog, DialogContent } from "@/components/ui/dialog"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; @@ -193,7 +194,7 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD

-

{pet.name}

+

{formatPetName(pet)}

{formatRelativeTime(post.createdAt)}

@@ -560,7 +561,7 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD onOpenChange={setShowBlockDialog} blockerPetId={activePetId} blockedPetId={pet.id} - blockedPetName={pet.name} + blockedPetName={formatPetName(pet)} /> )} diff --git a/src/components/layout/MobileNav.tsx b/src/components/layout/MobileNav.tsx index 8d19067..2bb8254 100644 --- a/src/components/layout/MobileNav.tsx +++ b/src/components/layout/MobileNav.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { formatPetName } from "@/lib/pet-display-name"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { @@ -106,7 +107,7 @@ export function MobileNav({ pets }: MobileNavProps) { className="h-6 w-6 shrink-0" /> - {activePet.name} + {formatPetName(activePet)} {activePet.isVerified && ( )} @@ -126,7 +127,7 @@ export function MobileNav({ pets }: MobileNavProps) { className="h-6 w-6 shrink-0" /> - {pet.name} + {formatPetName(pet)} {pet.isVerified && ( )} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 97d1382..617bf7e 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect } from "react"; +import { formatPetName } from "@/lib/pet-display-name"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { @@ -148,7 +149,7 @@ export function Sidebar({ pets }: SidebarProps) { {!collapsed && ( <> - {activePet.name} + {formatPetName(activePet)} {activePet.isVerified && ( )} @@ -170,7 +171,7 @@ export function Sidebar({ pets }: SidebarProps) { className="h-6 w-6 shrink-0" /> - {pet.name} + {formatPetName(pet)} {pet.isVerified && ( )} diff --git a/src/components/messages/MessageThread.tsx b/src/components/messages/MessageThread.tsx index 197a9fb..3795619 100644 --- a/src/components/messages/MessageThread.tsx +++ b/src/components/messages/MessageThread.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect, useRef, useCallback } from "react"; +import { formatPetName } from "@/lib/pet-display-name"; import { useRouter } from "next/navigation"; import { ArrowLeft, Send, Loader2 } from "lucide-react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; @@ -128,7 +129,7 @@ export function MessageThread({ conversationId, otherPet }: MessageThreadProps) {otherPet.name[0]?.toUpperCase() ?? "?"} - {otherPet.name} + {formatPetName(otherPet)}
@@ -148,7 +149,7 @@ export function MessageThread({ conversationId, otherPet }: MessageThreadProps) {otherPet.name[0]?.toUpperCase() ?? "?"} -

{otherPet.name}

+

{formatPetName(otherPet)}

{t("empty")}

)} diff --git a/src/components/pet/PetForm.tsx b/src/components/pet/PetForm.tsx index 3f91e0e..1904602 100644 --- a/src/components/pet/PetForm.tsx +++ b/src/components/pet/PetForm.tsx @@ -38,6 +38,11 @@ const petFormSchema = z.object({ .string() .min(1, "This field is required.") .max(30, "Pet name must be 30 characters or fewer."), + nickname: z + .string() + .max(30, "Nickname must be 30 characters or fewer.") + .optional() + .nullable(), speciesId: z.string().min(1, "Please select a species."), breedId: z.string().nullable().optional(), bio: z @@ -137,6 +142,7 @@ export function PetForm({ resolver: zodResolver(petFormSchema), defaultValues: { name: initialValues?.name ?? "", + nickname: initialValues?.nickname ?? "", speciesId: initialValues?.speciesId ?? "", breedId: initialValues?.breedId ?? null, bio: initialValues?.bio ?? "", @@ -154,6 +160,7 @@ export function PetForm({ if (mode === "create") { createMutation.mutate({ name: data.name, + nickname: data.nickname || undefined, speciesId: data.speciesId, breedId: data.breedId ?? undefined, bio: data.bio ?? undefined, @@ -163,6 +170,7 @@ export function PetForm({ updateMutation.mutate({ petId: petId!, name: data.name, + nickname: data.nickname || null, breedId: data.breedId ?? null, bio: data.bio ?? null, adoptionStory: data.adoptionStory ?? null, @@ -211,6 +219,21 @@ export function PetForm({ )} + {/* Nickname / Rufname — optional, shown after the real name everywhere, e.g. "Rex (Rexi)" */} +
+ + + {errors.nickname && ( +

{errors.nickname.message}

+ )} +
+ {/* Species selector — defer render until list loaded so SelectValue resolves names */}
diff --git a/src/lib/mention-helpers.ts b/src/lib/mention-helpers.ts index 7eea178..4b79abc 100644 --- a/src/lib/mention-helpers.ts +++ b/src/lib/mention-helpers.ts @@ -31,7 +31,10 @@ export async function createMentionNotifications( const mentionedPets = await prisma.pet.findMany({ where: { - name: { in: names, mode: "insensitive" }, + OR: [ + { name: { in: names, mode: "insensitive" } }, + { nickname: { in: names, mode: "insensitive" } }, + ], id: { not: actorPetId }, }, select: { id: true }, diff --git a/src/lib/pet-display-name.ts b/src/lib/pet-display-name.ts new file mode 100644 index 0000000..2b2d6b0 --- /dev/null +++ b/src/lib/pet-display-name.ts @@ -0,0 +1,8 @@ +/** + * Formats a pet's display name: real name first, nickname/Rufname after + * in parentheses when set — e.g. "Rex (Rexi)". Same format everywhere, + * including the admin panel, per explicit product decision. + */ +export function formatPetName(pet: { name: string; nickname?: string | null }): string { + return pet.nickname ? `${pet.name} (${pet.nickname})` : pet.name; +} diff --git a/src/trpc/routers/admin.ts b/src/trpc/routers/admin.ts index 2801b63..0fb3fdd 100644 --- a/src/trpc/routers/admin.ts +++ b/src/trpc/routers/admin.ts @@ -291,7 +291,7 @@ export const adminRouter = router({ } : undefined, include: { - pet: { select: { id: true, name: true, avatarKey: true, ownerId: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true, ownerId: true } }, images: { orderBy: { position: "asc" }, take: 1 }, videoPost: { select: { muxPlaybackId: true } }, milestone: true, @@ -299,7 +299,7 @@ export const adminRouter = router({ include: { originalPost: { include: { - pet: { select: { id: true, name: true } }, + pet: { select: { id: true, name: true, nickname: true } }, images: { orderBy: { position: "asc" }, take: 1 }, milestone: true, }, @@ -557,7 +557,7 @@ export const adminRouter = router({ await assertAdmin(ctx.prisma, ctx.userId); return ctx.prisma.comment.findMany({ where: { postId: input.postId }, - include: { pet: { select: { id: true, name: true, avatarKey: true } } }, + include: { pet: { select: { id: true, name: true, nickname: true, avatarKey: true } } }, orderBy: { createdAt: "asc" }, }); }), @@ -632,7 +632,7 @@ export const adminRouter = router({ ...(input.role !== "all" && input.role !== "none" ? { adminRole: { role: input.role } } : {}), }, include: { - pets: { select: { id: true, name: true, avatarKey: true, isVerified: true }, take: 3 }, + pets: { select: { id: true, name: true, nickname: true, avatarKey: true, isVerified: true }, take: 3 }, ban: true, shadowban: true, adminRole: true, @@ -864,6 +864,7 @@ export const adminRouter = router({ select: { id: true, name: true, + nickname: true, avatarKey: true, isVerified: true, ownerId: true, @@ -1060,8 +1061,8 @@ export const adminRouter = router({ const convs = await ctx.prisma.conversation.findMany({ where: { OR: [{ petAId: teamPet.id }, { petBId: teamPet.id }] }, include: { - petA: { select: { id: true, name: true, avatarKey: true } }, - petB: { select: { id: true, name: true, avatarKey: true } }, + petA: { select: { id: true, name: true, nickname: true, avatarKey: true } }, + petB: { select: { id: true, name: true, nickname: true, avatarKey: true } }, messages: { orderBy: { createdAt: "desc" }, take: 1 }, _count: { select: { messages: { where: { senderPetId: { not: teamPet.id }, readAt: null } } }, @@ -1095,7 +1096,7 @@ export const adminRouter = router({ where: { conversationId: input.conversationId }, orderBy: { createdAt: "asc" }, take: 200, - include: { senderPet: { select: { id: true, name: true, avatarKey: true } } }, + include: { senderPet: { select: { id: true, name: true, nickname: true, avatarKey: true } } }, }); // Fire-and-forget — any admin opening the thread marks it read for the whole team. @@ -1198,7 +1199,7 @@ export const adminRouter = router({ const reports = await ctx.prisma.report.findMany({ include: { - reporter: { select: { id: true, name: true, avatarKey: true } }, + reporter: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, orderBy: { createdAt: "desc" }, take: 100, @@ -1219,7 +1220,7 @@ export const adminRouter = router({ createdAt: true, reactionCount: true, commentCount: true, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, images: { orderBy: { position: "asc" }, take: 4, @@ -1262,7 +1263,7 @@ export const adminRouter = router({ type: true, createdAt: true, hiddenAt: true, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, images: { orderBy: { position: "asc" }, select: { storageKey: true, position: true, blurhash: true }, @@ -1271,7 +1272,7 @@ export const adminRouter = router({ orderBy: { createdAt: "desc" }, take: 200, select: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }, }, @@ -1511,7 +1512,7 @@ export const adminRouter = router({ return ctx.prisma.adminRole.findMany({ include: { owner: { - include: { pets: { select: { name: true, avatarKey: true }, take: 1 } }, + include: { pets: { select: { name: true, nickname: true, avatarKey: true }, take: 1 } }, }, }, orderBy: { grantedAt: "desc" }, @@ -1594,7 +1595,7 @@ export const adminRouter = router({ const owners = moderatorIds.length > 0 ? await ctx.prisma.owner.findMany({ where: { id: { in: moderatorIds } }, - select: { id: true, pets: { select: { name: true }, take: 1 } }, + select: { id: true, pets: { select: { name: true, nickname: true }, take: 1 } }, }) : []; const nameByOwnerId = new Map(owners.map((o) => [o.id, o.pets[0]?.name ?? null])); @@ -1630,7 +1631,7 @@ export const adminRouter = router({ orderBy: { createdAt: "desc" }, take: input.limit, include: { - issuedBy: { select: { id: true, pets: { select: { name: true }, take: 1 } } }, + issuedBy: { select: { id: true, pets: { select: { name: true, nickname: true }, take: 1 } } }, }, }); }), diff --git a/src/trpc/routers/ads.ts b/src/trpc/routers/ads.ts index e6d7681..bcc1060 100644 --- a/src/trpc/routers/ads.ts +++ b/src/trpc/routers/ads.ts @@ -91,7 +91,7 @@ export const adsRouter = router({ return ctx.prisma.adComment.findMany({ where: { adId: input.adId }, include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, orderBy: { createdAt: "asc" }, take: 100, @@ -113,7 +113,7 @@ export const adsRouter = router({ ctx.prisma.adComment.create({ data: { adId: input.adId, petId: input.petId, body: input.body }, include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }), ctx.prisma.advertisement.update({ diff --git a/src/trpc/routers/comments.ts b/src/trpc/routers/comments.ts index 6d282e9..132ee93 100644 --- a/src/trpc/routers/comments.ts +++ b/src/trpc/routers/comments.ts @@ -27,7 +27,7 @@ export const commentsRouter = router({ body: input.body, }, include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }), ctx.prisma.post.update({ @@ -77,7 +77,7 @@ export const commentsRouter = router({ orderBy: { createdAt: "asc" }, take: input.limit + 1, include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }); diff --git a/src/trpc/routers/explore.ts b/src/trpc/routers/explore.ts index adc83b5..ec0c33d 100644 --- a/src/trpc/routers/explore.ts +++ b/src/trpc/routers/explore.ts @@ -79,6 +79,7 @@ export const exploreRouter = router({ select: { id: true, name: true, + nickname: true, avatarKey: true, isVerified: true, owner: { select: { sponsorSince: true, sponsorPeriodEnd: true } }, diff --git a/src/trpc/routers/feed.ts b/src/trpc/routers/feed.ts index 63bb957..6794ff4 100644 --- a/src/trpc/routers/feed.ts +++ b/src/trpc/routers/feed.ts @@ -31,6 +31,7 @@ const postInclude = { select: { id: true, name: true, + nickname: true, avatarKey: true, isVerified: true, owner: { select: { sponsorSince: true, sponsorPeriodEnd: true } }, @@ -47,6 +48,7 @@ const postInclude = { select: { id: true, name: true, + nickname: true, avatarKey: true, isVerified: true, owner: { select: { sponsorSince: true, sponsorPeriodEnd: true } }, diff --git a/src/trpc/routers/follows.ts b/src/trpc/routers/follows.ts index 2569fb5..123a76f 100644 --- a/src/trpc/routers/follows.ts +++ b/src/trpc/routers/follows.ts @@ -83,7 +83,7 @@ export const followsRouter = router({ include: { images: { orderBy: { position: "asc" } }, milestone: true, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }); @@ -176,6 +176,7 @@ export const followsRouter = router({ select: { id: true, name: true, + nickname: true, avatarKey: true, species: { select: { name: true, translations: { where: { locale: ctx.locale } } } }, }, @@ -220,7 +221,7 @@ export const followsRouter = router({ }, }, select: { - followee: { select: { id: true, name: true, avatarKey: true } }, + followee: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, orderBy: { createdAt: "desc" }, take: 100, @@ -253,6 +254,7 @@ export const followsRouter = router({ select: { id: true, name: true, + nickname: true, avatarKey: true, species: { select: { name: true, translations: { where: { locale: ctx.locale } } } }, }, diff --git a/src/trpc/routers/messages.ts b/src/trpc/routers/messages.ts index 4a2437e..ab44bd2 100644 --- a/src/trpc/routers/messages.ts +++ b/src/trpc/routers/messages.ts @@ -50,8 +50,8 @@ export const conversationsRouter = router({ const convs = await ctx.prisma.conversation.findMany({ where: { OR: [{ petAId: input.petId }, { petBId: input.petId }] }, include: { - petA: { select: { id: true, name: true, avatarKey: true } }, - petB: { select: { id: true, name: true, avatarKey: true } }, + petA: { select: { id: true, name: true, nickname: true, avatarKey: true } }, + petB: { select: { id: true, name: true, nickname: true, avatarKey: true } }, messages: { orderBy: { createdAt: "desc" }, take: 1, @@ -188,7 +188,7 @@ export const messagesRouter = router({ orderBy: { createdAt: "asc" }, take: input.limit + 1, include: { - senderPet: { select: { id: true, name: true, avatarKey: true } }, + senderPet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }); @@ -237,7 +237,7 @@ export const messagesRouter = router({ senderPetId: input.senderPetId, body: input.body, }, - include: { senderPet: { select: { id: true, name: true, avatarKey: true } } }, + include: { senderPet: { select: { id: true, name: true, nickname: true, avatarKey: true } } }, }), // Touch conversation updatedAt so inbox re-orders correctly ctx.prisma.conversation.update({ diff --git a/src/trpc/routers/milestones.ts b/src/trpc/routers/milestones.ts index 5e65a5a..15a17d0 100644 --- a/src/trpc/routers/milestones.ts +++ b/src/trpc/routers/milestones.ts @@ -66,7 +66,7 @@ export const milestonesRouter = router({ include: { images: { orderBy: { position: "asc" } }, milestone: true, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, }); @@ -97,7 +97,7 @@ export const milestonesRouter = router({ include: { milestone: true, images: { orderBy: { position: "asc" } }, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, orderBy: { createdAt: "desc" }, take: input.limit, diff --git a/src/trpc/routers/notifications.ts b/src/trpc/routers/notifications.ts index 5555574..b47b6d1 100644 --- a/src/trpc/routers/notifications.ts +++ b/src/trpc/routers/notifications.ts @@ -26,7 +26,7 @@ export const notificationsRouter = router({ orderBy: { createdAt: "desc" }, take: input.limit + 1, include: { - actorPet: { select: { id: true, name: true, avatarKey: true } }, + actorPet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, post: { select: { id: true, petId: true, images: { take: 1, orderBy: { position: "asc" } } } }, comment: { select: { id: true, body: true } }, }, diff --git a/src/trpc/routers/pets.ts b/src/trpc/routers/pets.ts index aeb52c7..b72eb10 100644 --- a/src/trpc/routers/pets.ts +++ b/src/trpc/routers/pets.ts @@ -17,6 +17,7 @@ export const petsRouter = router({ .input( z.object({ name: z.string().min(1).max(30), + nickname: z.string().max(30).optional(), speciesId: z.string().min(1), breedId: z.string().optional(), bio: z.string().max(150).optional(), @@ -48,6 +49,7 @@ export const petsRouter = router({ data: { ownerId: ctx.userId, name: input.name, + nickname: input.nickname ?? null, speciesId: input.speciesId, breedId: input.breedId ?? null, bio: input.bio ?? null, @@ -145,6 +147,7 @@ export const petsRouter = router({ z.object({ petId: z.string().min(1), name: z.string().max(30).optional(), + nickname: z.string().max(30).optional().nullable(), bio: z.string().max(150).optional().nullable(), adoptionStory: z.string().max(500).optional().nullable(), breedId: z.string().optional().nullable(), @@ -161,6 +164,7 @@ export const petsRouter = router({ // Only update fields that were explicitly provided const updateData: Record = {}; if (data.name !== undefined) updateData.name = data.name; + if (data.nickname !== undefined) updateData.nickname = data.nickname; if (data.bio !== undefined) updateData.bio = data.bio; if (data.adoptionStory !== undefined) updateData.adoptionStory = data.adoptionStory; diff --git a/src/trpc/routers/posts.ts b/src/trpc/routers/posts.ts index 15c1645..13cf101 100644 --- a/src/trpc/routers/posts.ts +++ b/src/trpc/routers/posts.ts @@ -129,7 +129,7 @@ export const postsRouter = router({ }, include: { images: { orderBy: { position: "asc" } }, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, milestone: true, }, }); @@ -197,7 +197,7 @@ export const postsRouter = router({ data: { caption: input.caption }, include: { images: { orderBy: { position: "asc" } }, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, milestone: true, }, }); @@ -249,14 +249,14 @@ export const postsRouter = router({ take: input.limit + 1, include: { images: { orderBy: { position: "asc" } }, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, milestone: true, videoPost: { select: { status: true, muxPlaybackId: true } }, repost: { include: { originalPost: { include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, images: { orderBy: { position: "asc" } }, milestone: true, }, @@ -291,14 +291,14 @@ export const postsRouter = router({ }, include: { images: { orderBy: { position: "asc" } }, - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, milestone: true, videoPost: { select: { status: true, muxPlaybackId: true } }, repost: { include: { originalPost: { include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, images: { orderBy: { position: "asc" } }, milestone: true, }, diff --git a/src/trpc/routers/search.ts b/src/trpc/routers/search.ts index cd9ece2..2eb6d2b 100644 --- a/src/trpc/routers/search.ts +++ b/src/trpc/routers/search.ts @@ -20,6 +20,7 @@ export const searchRouter = router({ where: { OR: [ { name: { contains: input.query, mode: "insensitive" } }, + { nickname: { contains: input.query, mode: "insensitive" } }, { breed: { name: { contains: input.query, mode: "insensitive" } } }, ], ...(input.cursor ? { id: { gt: input.cursor } } : {}), @@ -84,7 +85,7 @@ export const searchRouter = router({ include: { post: { include: { - pet: { select: { id: true, name: true, avatarKey: true } }, + pet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, images: { orderBy: { position: "asc" }, take: 1 }, }, }, diff --git a/src/trpc/routers/stories.ts b/src/trpc/routers/stories.ts index 85c8646..95d1726 100644 --- a/src/trpc/routers/stories.ts +++ b/src/trpc/routers/stories.ts @@ -172,7 +172,7 @@ export const storiesRouter = router({ return ctx.prisma.storyView.findMany({ where: { storyId: input.storyId }, include: { - viewerPet: { select: { id: true, name: true, avatarKey: true } }, + viewerPet: { select: { id: true, name: true, nickname: true, avatarKey: true } }, }, orderBy: { viewedAt: "desc" }, take: 200,