added in admin pages to add models and parts
This commit is contained in:
Executable
+300
@@ -0,0 +1,300 @@
|
||||
#!/bin/zsh
|
||||
|
||||
echo "🎨 Setting up UI integration (Next.js App Router)..."
|
||||
|
||||
BASE="src/app"
|
||||
COMP="src/app/components"
|
||||
UTIL="src/lib/ui"
|
||||
|
||||
mkdir -p "$COMP"
|
||||
mkdir -p "$UTIL"
|
||||
|
||||
#############################################
|
||||
# API CLIENT (simple fetch helpers)
|
||||
#############################################
|
||||
|
||||
cat > "$UTIL/api.js" << 'EOF'
|
||||
export async function fetchJSON(url) {
|
||||
const res = await fetch(url, { cache: "no-store" });
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export function getModel(id) {
|
||||
return fetchJSON(`/api/models/${id}`);
|
||||
}
|
||||
|
||||
export function getPart(id) {
|
||||
return fetchJSON(`/api/parts/${id}`);
|
||||
}
|
||||
|
||||
export function getCompatibleParts(modelId) {
|
||||
return fetchJSON(`/api/compatibility/model/${modelId}`);
|
||||
}
|
||||
|
||||
export function getCompatibleModels(partId) {
|
||||
return fetchJSON(`/api/compatibility/part/${partId}`);
|
||||
}
|
||||
EOF
|
||||
|
||||
#############################################
|
||||
# COMPONENTS
|
||||
#############################################
|
||||
|
||||
# ModelCard.js
|
||||
cat > "$COMP/ModelCard.js" << 'EOF'
|
||||
export default function ModelCard({ model }) {
|
||||
return (
|
||||
<div className="border rounded p-4 shadow">
|
||||
<h2 className="font-bold text-lg">{model.name}</h2>
|
||||
<p>{model.brand} • {model.scale}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
# PartCard.js
|
||||
cat > "$COMP/PartCard.js" << 'EOF'
|
||||
export default function PartCard({ part }) {
|
||||
return (
|
||||
<div className="border rounded p-4 shadow">
|
||||
<h2 className="font-bold text-lg">{part.name}</h2>
|
||||
<p className="opacity-70">ID: {part.id}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
# SearchBar.js
|
||||
cat > "$COMP/SearchBar.js" << 'EOF'
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export default function SearchBar({ onSearch }) {
|
||||
const [text, setText] = useState("");
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="border p-2 rounded w-full"
|
||||
placeholder="Search models or parts..."
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="px-4 py-2 bg-primary text-white rounded"
|
||||
onClick={() => onSearch(text)}
|
||||
>
|
||||
Go
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
#############################################
|
||||
# UI: Model Page Integration
|
||||
#############################################
|
||||
|
||||
cat > "$BASE/model/[modelId]/page.js" << 'EOF'
|
||||
import { getModel, getCompatibleParts } from "@/src/lib/ui/api";
|
||||
import PartCard from "@/src/app/components/PartCard";
|
||||
|
||||
export default async function ModelPage({ params }) {
|
||||
const model = await getModel(params.modelId);
|
||||
const parts = await getCompatibleParts(params.modelId);
|
||||
|
||||
if (!model) return <p>Model not found</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-3xl font-bold">{model.name}</h1>
|
||||
<p className="opacity-70">{model.brand} • {model.scale}</p>
|
||||
|
||||
<h2 className="text-xl font-bold mt-6">Compatible Parts</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{Array.isArray(parts) && parts.length > 0 ? (
|
||||
parts.map(item => <PartCard key={item.part.id} part={item.part} />)
|
||||
) : (
|
||||
<p>No compatible parts.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
#############################################
|
||||
# UI: Part Page Integration
|
||||
#############################################
|
||||
|
||||
cat > "$BASE/part/[partId]/page.js" << 'EOF'
|
||||
import { getPart, getCompatibleModels } from "@/src/lib/ui/api";
|
||||
import ModelCard from "@/src/app/components/ModelCard";
|
||||
|
||||
export default async function PartDetailPage({ params }) {
|
||||
const part = await getPart(params.partId);
|
||||
const models = await getCompatibleModels(params.partId);
|
||||
|
||||
if (!part) return <p>Part not found</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-3xl font-bold">{part.name}</h1>
|
||||
<p className="opacity-70">ID: {part.id}</p>
|
||||
|
||||
<h2 className="text-xl font-bold mt-6">Compatible Models</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{Array.isArray(models) && models.length > 0 ? (
|
||||
models.map(item => (
|
||||
<ModelCard key={item.model.id} model={item.model} />
|
||||
))
|
||||
) : (
|
||||
<p>No compatible models.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
#############################################
|
||||
# UI: Category Page Integration
|
||||
#############################################
|
||||
|
||||
cat > "$BASE/category/[modelId]/[categoryName]/page.js" << 'EOF'
|
||||
import { getModel, getCompatibleParts } from "@/src/lib/ui/api";
|
||||
import PartCard from "@/src/app/components/PartCard";
|
||||
|
||||
export default async function CategoryPage({ params }) {
|
||||
const model = await getModel(params.modelId);
|
||||
const allParts = await getCompatibleParts(params.modelId);
|
||||
|
||||
if (!model) return <p>Model not found</p>;
|
||||
|
||||
const parts = allParts
|
||||
? allParts.filter(p => p.part.category === params.categoryName)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">
|
||||
{params.categoryName} for {model.name}
|
||||
</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
||||
{parts.length ? (
|
||||
parts.map(p => (
|
||||
<PartCard key={p.part.id} part={p.part} />
|
||||
))
|
||||
) : (
|
||||
<p>No compatible parts in this category.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
#############################################
|
||||
# UI: Compatibility Explorer Page
|
||||
#############################################
|
||||
|
||||
cat > "$BASE/compatibility/page.js" << 'EOF'
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { fetchJSON } from "@/src/lib/ui/api";
|
||||
import ModelCard from "../components/ModelCard";
|
||||
import PartCard from "../components/PartCard";
|
||||
|
||||
export default function CompatibilityPage() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [result, setResult] = useState([]);
|
||||
|
||||
async function compare() {
|
||||
const modelData = await fetchJSON(`/api/compatibility/model/${query}`);
|
||||
const partData = await fetchJSON(`/api/compatibility/part/${query}`);
|
||||
|
||||
setResult(modelData || partData || []);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-3xl font-bold">Compatibility Explorer</h1>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="border p-2 rounded w-full"
|
||||
placeholder="Enter modelId or partId"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="px-4 py-2 bg-primary text-white rounded"
|
||||
onClick={compare}
|
||||
>
|
||||
Compare
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
{result.map(item =>
|
||||
item.model ? (
|
||||
<ModelCard key={item.model.id} model={item.model} />
|
||||
) : (
|
||||
<PartCard key={item.part.id} part={item.part} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
#############################################
|
||||
# UI: Home Page Search Integration
|
||||
#############################################
|
||||
|
||||
cat > "$BASE/page.js" << 'EOF'
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import SearchBar from "./components/SearchBar";
|
||||
import { searchModels, searchParts } from "@/src/lib/search/search";
|
||||
|
||||
export default function HomePage() {
|
||||
const [results, setResults] = useState([]);
|
||||
|
||||
async function handleSearch(query) {
|
||||
const models = searchModels(query);
|
||||
const parts = searchParts(query);
|
||||
setResults([...models, ...parts]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-4xl font-bold">RC Compatibility Explorer</h1>
|
||||
|
||||
<SearchBar onSearch={handleSearch} />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{results.map(item =>
|
||||
item.categories ? (
|
||||
<div key={item.id} className="border p-4 rounded">
|
||||
<strong>{item.name}</strong> — Model
|
||||
</div>
|
||||
) : (
|
||||
<div key={item.id} className="border p-4 rounded">
|
||||
<strong>{item.name}</strong> — Part
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "🎉 UI integration created successfully!"
|
||||
Reference in New Issue
Block a user