111 lines
4.0 KiB
TypeScript
111 lines
4.0 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 { MilestoneCard } from "@/components/feed/MilestoneCard";
|
|
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 { 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 */}
|
|
<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 (
|
|
<div key={post.id} className="relative aspect-square overflow-hidden bg-muted">
|
|
{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>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</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>
|
|
);
|
|
}
|