Files
rc-compat/bash-setup-scripts/setup-ui-integration.sh
T

301 lines
7.8 KiB
Bash
Executable File

#!/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!"