260 lines
8.4 KiB
Bash
Executable File
260 lines
8.4 KiB
Bash
Executable File
#!/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"
|