added in admin pages to add models and parts
This commit is contained in:
+259
@@ -0,0 +1,259 @@
|
||||
#!/bin/zsh
|
||||
|
||||
echo "🛠 Setting up Compatibility Explorer (Option A)..."
|
||||
|
||||
COMPAT_DIR="src/app/compatibility"
|
||||
mkdir -p "$COMPAT_DIR"
|
||||
|
||||
cat > "$COMPAT_DIR/page.js" << 'EOF'
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { CATEGORIES } from "@/lib/data/categories";
|
||||
|
||||
function ModeCard({ title, emoji, description, children }) {
|
||||
return (
|
||||
<div className="border rounded-xl p-6 shadow-sm bg-white flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-2xl">{emoji}</div>
|
||||
<div>
|
||||
<h2 className="font-semibold text-lg">{title}</h2>
|
||||
<p className="text-sm text-gray-500">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col gap-3">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CompatibilityPage() {
|
||||
const [models, setModels] = useState([]);
|
||||
const [parts, setParts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [modelForParts, setModelForParts] = useState("");
|
||||
const [partForModels, setPartForModels] = useState("");
|
||||
const [modelA, setModelA] = useState("");
|
||||
const [modelB, setModelB] = useState("");
|
||||
const [categoryModel, setCategoryModel] = useState("");
|
||||
const [category, setCategory] = useState("");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
try {
|
||||
const [modelsRes, partsRes] = await Promise.all([
|
||||
fetch("/api/models"),
|
||||
fetch("/api/parts"),
|
||||
]);
|
||||
|
||||
const modelsJson = modelsRes.ok ? await modelsRes.json() : [];
|
||||
const partsJson = partsRes.ok ? await partsRes.json() : [];
|
||||
|
||||
setModels(modelsJson || []);
|
||||
setParts(partsJson || []);
|
||||
} catch (e) {
|
||||
console.error("Failed to load models/parts", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
}, []);
|
||||
|
||||
function handleGoToModelCompatibility() {
|
||||
if (!modelForParts) return;
|
||||
router.push(`/model/${modelForParts}`);
|
||||
}
|
||||
|
||||
function handleGoToPartCompatibility() {
|
||||
if (!partForModels) return;
|
||||
router.push(`/part/${partForModels}`);
|
||||
}
|
||||
|
||||
function handleExploreCategory() {
|
||||
if (!categoryModel || !category) return;
|
||||
router.push(`/category/${categoryModel}/${category}`);
|
||||
}
|
||||
|
||||
function handleCompareModels() {
|
||||
if (!modelA || !modelB) return;
|
||||
// Stubbed for now to avoid broken routes
|
||||
alert("Model comparison view coming soon! (Hotshot vs Supershot, etc.)");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
{/* Breadcrumb-ish header */}
|
||||
<nav className="text-sm text-gray-500">
|
||||
<Link href="/" className="hover:underline text-blue-600">
|
||||
Home
|
||||
</Link>{" "}
|
||||
<span>›</span>{" "}
|
||||
<span className="font-semibold text-gray-700">Compatibility Explorer</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Title */}
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold mb-2">Compatibility Explorer</h1>
|
||||
<p className="text-gray-600 max-w-2xl">
|
||||
Discover cross-compatibility between RC models and parts. Use one of the
|
||||
tools below to explore fitment, upgrades, and shared components.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{loading && (
|
||||
<p className="text-gray-500">Loading models and parts…</p>
|
||||
)}
|
||||
|
||||
{/* Mode Selector Grid */}
|
||||
{!loading && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Mode 1: Model → Compatible Parts */}
|
||||
<ModeCard
|
||||
title="Model → Compatible Parts"
|
||||
emoji="🔧"
|
||||
description="Find every part that fits a specific RC model."
|
||||
>
|
||||
<select
|
||||
className="border rounded-lg px-3 py-2"
|
||||
value={modelForParts}
|
||||
onChange={(e) => setModelForParts(e.target.value)}
|
||||
>
|
||||
<option value="">Select a model…</option>
|
||||
{models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name} ({m.brand})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={handleGoToModelCompatibility}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
|
||||
disabled={!modelForParts}
|
||||
>
|
||||
Show Compatible Parts
|
||||
</button>
|
||||
</ModeCard>
|
||||
|
||||
{/* Mode 2: Part → Compatible Models */}
|
||||
<ModeCard
|
||||
title="Part → Compatible Models"
|
||||
emoji="🧩"
|
||||
description="See all RC models that this part works with."
|
||||
>
|
||||
<select
|
||||
className="border rounded-lg px-3 py-2"
|
||||
value={partForModels}
|
||||
onChange={(e) => setPartForModels(e.target.value)}
|
||||
>
|
||||
<option value="">Select a part…</option>
|
||||
{parts.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name} ({p.id})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={handleGoToPartCompatibility}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
|
||||
disabled={!partForModels}
|
||||
>
|
||||
Show Compatible Models
|
||||
</button>
|
||||
</ModeCard>
|
||||
|
||||
{/* Mode 3: Compare Two Models (stubbed) */}
|
||||
<ModeCard
|
||||
title="Compare Two Models"
|
||||
emoji="🔍"
|
||||
description="See shared and unique parts between two models. (Coming soon)"
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<select
|
||||
className="border rounded-lg px-3 py-2"
|
||||
value={modelA}
|
||||
onChange={(e) => setModelA(e.target.value)}
|
||||
>
|
||||
<option value="">Select Model A…</option>
|
||||
{models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name} ({m.brand})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded-lg px-3 py-2"
|
||||
value={modelB}
|
||||
onChange={(e) => setModelB(e.target.value)}
|
||||
>
|
||||
<option value="">Select Model B…</option>
|
||||
{models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name} ({m.brand})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCompareModels}
|
||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-lg cursor-not-allowed"
|
||||
disabled
|
||||
>
|
||||
Compare (Coming Soon)
|
||||
</button>
|
||||
</ModeCard>
|
||||
|
||||
{/* Mode 4: Explore Categories */}
|
||||
<ModeCard
|
||||
title="Explore by Category"
|
||||
emoji="📂"
|
||||
description="Browse shocks, drivetrain, chassis, and more for a model."
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<select
|
||||
className="border rounded-lg px-3 py-2"
|
||||
value={categoryModel}
|
||||
onChange={(e) => setCategoryModel(e.target.value)}
|
||||
>
|
||||
<option value="">Select a model…</option>
|
||||
{models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name} ({m.brand})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
className="border rounded-lg px-3 py-2"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
>
|
||||
<option value="">Select a category…</option>
|
||||
{CATEGORIES.map((cat) => (
|
||||
<option key={cat} value={cat}>
|
||||
{cat}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleExploreCategory}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
|
||||
disabled={!categoryModel || !category}
|
||||
>
|
||||
Explore Category
|
||||
</button>
|
||||
</ModeCard>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "🎉 Compatibility Explorer (Option A) page created at src/app/compatibility/page.js"
|
||||
Reference in New Issue
Block a user