196 lines
7.3 KiB
TypeScript
196 lines
7.3 KiB
TypeScript
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import type { Metadata } from "next";
|
|
import { getPlantWithDiseases } from "@/lib/api/diseases-db";
|
|
import { getPlantDescription } from "@/lib/display-helpers";
|
|
import BetaNotice from "@/components/BetaNotice";
|
|
import DiseaseCards from "./DiseaseCards";
|
|
import PlantViewTracker from "@/components/PlantViewTracker";
|
|
import FlagPlantImage from "@/components/FlagPlantImage";
|
|
|
|
interface Props {
|
|
params: Promise<{ plantId: string }>;
|
|
}
|
|
|
|
export async function generateStaticParams() {
|
|
const { getDb } = await import("@/lib/db/index");
|
|
const { plants } = await import("@/lib/db/schema");
|
|
const db = getDb();
|
|
const rows = await db.select({ id: plants.id }).from(plants);
|
|
return rows.map((p: { id: string }) => ({
|
|
plantId: p.id,
|
|
}));
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const { plantId } = await params;
|
|
const result = await getPlantWithDiseases(plantId);
|
|
|
|
if (!result) {
|
|
return { title: "Plant Not Found" };
|
|
}
|
|
|
|
return {
|
|
title: `${result.plant.commonName} — Diseases & Care`,
|
|
description: `Learn about ${result.plant.commonName} (${result.plant.scientificName}) diseases, symptoms, causes, and treatments. ${result.diseases.length} diseases documented.`,
|
|
};
|
|
}
|
|
|
|
// ─── Plant Detail Page ───
|
|
|
|
export default async function PlantDetailPage({ params }: Props) {
|
|
const { plantId } = await params;
|
|
const result = await getPlantWithDiseases(plantId);
|
|
|
|
if (!result) {
|
|
notFound();
|
|
}
|
|
|
|
const { plant, diseases } = result;
|
|
const description = getPlantDescription(
|
|
plant.commonName,
|
|
plant.scientificName,
|
|
plant.category,
|
|
plant.family,
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<PlantViewTracker plantId={plantId} />
|
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
|
|
{/* Breadcrumb */}
|
|
<nav className="mb-6 text-sm" aria-label="Breadcrumb">
|
|
<ol className="flex items-center gap-2 text-zinc-500 dark:text-zinc-400">
|
|
<li>
|
|
<Link
|
|
href="/"
|
|
className="hover:text-leaf-green-600 dark:hover:text-leaf-green-400 transition-colors"
|
|
>
|
|
Home
|
|
</Link>
|
|
</li>
|
|
<li aria-hidden="true">/</li>
|
|
<li>
|
|
<Link
|
|
href="/browse"
|
|
className="hover:text-leaf-green-600 dark:hover:text-leaf-green-400 transition-colors"
|
|
>
|
|
Browse
|
|
</Link>
|
|
</li>
|
|
<li aria-hidden="true">/</li>
|
|
<li className="text-zinc-800 dark:text-zinc-200 font-medium">{plant.commonName}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<BetaNotice variant="card" className="mb-6" />
|
|
|
|
{/* Plant hero */}
|
|
<div className="flex flex-col sm:flex-row sm:items-start gap-6 mb-10">
|
|
{/* Plant image */}
|
|
<div className="relative h-32 w-32 sm:h-40 sm:w-40 shrink-0 rounded-2xl overflow-hidden bg-gradient-to-br from-leaf-green-50 to-leaf-green-100 dark:from-leaf-green-950 dark:to-leaf-green-900">
|
|
{plant.imageUrl ? (
|
|
<Image
|
|
src={plant.imageUrl}
|
|
alt={plant.commonName}
|
|
fill
|
|
className="object-cover"
|
|
sizes="(min-width: 640px) 16rem, 8rem"
|
|
unoptimized
|
|
/>
|
|
) : (
|
|
<div className="flex items-center justify-center w-full h-full">
|
|
<svg
|
|
className="w-12 h-12 text-leaf-green-300 dark:text-leaf-green-700"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth={1.5}
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M12 3c-1.5 2-4 4-4 7a4 4 0 0 0 8 0c0-3-2.5-5-4-7Z"
|
|
/>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 21v-9" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
<FlagPlantImage plantId={plantId} />
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<h1 className="text-3xl sm:text-4xl font-bold text-zinc-900 dark:text-zinc-100">
|
|
{plant.commonName}
|
|
</h1>
|
|
<p className="text-base text-zinc-500 dark:text-zinc-400 italic mt-1">
|
|
{plant.scientificName}
|
|
</p>
|
|
<p className="text-sm text-zinc-500 dark:text-zinc-400 mt-1">
|
|
Family: <span className="font-medium">{plant.family}</span>
|
|
{" · "}
|
|
Category: <span className="font-medium capitalize">{plant.category}</span>
|
|
</p>
|
|
<p className="mt-3 text-sm text-zinc-600 dark:text-zinc-300 leading-relaxed">
|
|
{description}
|
|
</p>
|
|
<div className="mt-3 flex items-start gap-2 text-sm text-zinc-500 dark:text-zinc-400">
|
|
<span aria-hidden="true">💚</span>
|
|
<span>{plant.careSummary}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Identify disease CTA */}
|
|
<div className="mb-10 rounded-xl bg-gradient-to-r from-leaf-green-50 to-soil-brown-50 dark:from-leaf-green-950 dark:to-soil-brown-950 border border-leaf-green-200 dark:border-leaf-green-800 p-5 sm:p-6">
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-base font-semibold text-zinc-900 ">
|
|
🧐 Spot a problem on your {plant.commonName.toLowerCase()}?
|
|
</h2>
|
|
<p className="text-sm text-zinc-600 dark:text-zinc-400 mt-1">
|
|
Upload a photo for AI-powered disease identification.
|
|
</p>
|
|
</div>
|
|
<Link
|
|
href="/upload"
|
|
className="inline-flex items-center gap-2 shrink-0 rounded-lg bg-leaf-green-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-leaf-green-700 focus:outline-none focus:ring-2 focus:ring-leaf-green-500 focus:ring-offset-2"
|
|
>
|
|
📸 Identify a Disease
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Disease list */}
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-zinc-900 dark:text-zinc-100 mb-2">
|
|
Known Diseases
|
|
</h2>
|
|
<p className="text-sm text-zinc-500 dark:text-zinc-400 mb-6">
|
|
{diseases.length === 0
|
|
? "No diseases currently documented for this plant."
|
|
: `${diseases.length} ${
|
|
diseases.length === 1 ? "disease" : "diseases"
|
|
} documented for ${plant.commonName}.`}
|
|
</p>
|
|
|
|
{diseases.length > 0 ? (
|
|
<DiseaseCards diseases={diseases} />
|
|
) : (
|
|
<div className="rounded-xl border border-dashed border-zinc-300 dark:border-zinc-700 p-10 text-center">
|
|
<span className="text-4xl block mb-3" aria-hidden="true">
|
|
🌿
|
|
</span>
|
|
<p className="text-zinc-500 dark:text-zinc-400 text-sm">
|
|
Disease data for {plant.commonName} is being researched and will be added soon.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|