Files
petfeed/src/components/profile/ProfileTabs.tsx
T
adminandClaude Sonnet 4.6 6542c06e02 fix: auto-set first pet on login, resize detail view, fix Clerk types
- Sidebar + MobileNav: auto-call setActivePet(pets[0]) when activePetId
  is null so Follow/reaction buttons work immediately after onboarding
- PostCard detail mode: reduce image max-h from 70dvh to 50dvh so the
  footer (paw + comment buttons) stays visible within the dialog
- ProfileTabs dialog: reduce max-h from 90dvh to 85dvh
- Auth pages: remove layout.logoPlacement and colorText (not in Clerk
  Variables type, caused Docker build failure)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 05:58:57 +02:00

138 lines
5.2 KiB
TypeScript

"use client";
import { useState, useCallback } from "react";
import { Layers2 } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { useTRPC } from "@/trpc/client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { MilestoneCard } from "@/components/feed/MilestoneCard";
import { PostCard } from "@/components/feed/PostCard";
import { getMediaUrl } from "@/lib/media-url";
import type { PostCardPost } from "@/components/feed/PostCard";
interface ProfileTabsProps {
petId: string;
}
/**
* ProfileTabs — Posts (3-col photo grid) + Milestones (chronological list) (MILE-02).
*
* Posts tab: photo posts fetched via posts.byPetId; carousel posts get a Layers2 overlay.
* Milestones tab: milestone posts via milestones.listByPet rendered as MilestoneCard.
* Active tab has orange-500 underline (shadcn Tabs default with --primary: orange-500).
*/
export function ProfileTabs({ petId }: ProfileTabsProps) {
const trpc = useTRPC();
const [_activeTab, setActiveTab] = useState("posts");
const [selectedPost, setSelectedPost] = useState<PostCardPost | null>(null);
const { data: postsData } = useQuery(
trpc.posts.byPetId.queryOptions({ petId, limit: 30 })
);
const { data: milestonesData } = useQuery(
trpc.milestones.listByPet.queryOptions({ petId, limit: 30 })
);
const posts = postsData?.posts ?? [];
const milestones = (milestonesData ?? []) as PostCardPost[];
const handleTabChange = useCallback((tab: string) => {
setActiveTab(tab);
}, []);
return (
<>
<Tabs defaultValue="posts" onValueChange={handleTabChange}>
<TabsList className="w-full rounded-none border-b bg-transparent h-auto p-0">
<TabsTrigger
value="posts"
className="flex-1 rounded-none border-b-2 border-transparent data-[state=active]:border-orange-500 data-[state=active]:text-foreground data-[state=active]:shadow-none pb-2"
>
Posts
</TabsTrigger>
<TabsTrigger
value="milestones"
className="flex-1 rounded-none border-b-2 border-transparent data-[state=active]:border-orange-500 data-[state=active]:text-foreground data-[state=active]:shadow-none pb-2"
>
Milestones
</TabsTrigger>
</TabsList>
{/* Posts tab — 3-column photo grid, tap to open full PostCard */}
<TabsContent value="posts" className="mt-0">
{posts.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-10">
No posts yet
</p>
) : (
<div className="grid grid-cols-3 gap-0.5 mt-0.5">
{posts.map((post) => {
const firstImage = post.images?.[0];
const isCarousel = (post.images?.length ?? 0) > 1;
const mediaUrl = firstImage ? getMediaUrl(firstImage.storageKey) : null;
return (
<button
key={post.id}
type="button"
onClick={() => setSelectedPost(post)}
className="relative aspect-square overflow-hidden bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-500"
aria-label={post.caption ?? "View post"}
>
{mediaUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={mediaUrl}
alt={post.caption ?? "Post"}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-muted" />
)}
{isCarousel && (
<div className="absolute top-1.5 right-1.5">
<Layers2 className="h-4 w-4 text-white drop-shadow" aria-hidden="true" />
</div>
)}
</button>
);
})}
</div>
)}
</TabsContent>
{/* Milestones tab — chronological MilestoneCard list */}
<TabsContent value="milestones" className="mt-4 space-y-4">
{milestones.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-10">
No milestones yet
</p>
) : (
milestones.map((post) => (
<MilestoneCard key={post.id} post={post} />
))
)}
</TabsContent>
</Tabs>
{/* Post detail dialog — centered overlay, no X button (3-dots in PostCard header handles options) */}
<Dialog open={!!selectedPost} onOpenChange={(open) => { if (!open) setSelectedPost(null); }}>
<DialogContent
showCloseButton={false}
className="max-w-[560px] w-full p-0 overflow-y-auto max-h-[85dvh] rounded-xl"
>
{selectedPost && (
<PostCard
post={selectedPost}
variant="detail"
onDeleted={() => setSelectedPost(null)}
/>
)}
</DialogContent>
</Dialog>
</>
);
}