Files
GearBox/src/client/components/SetupsView.tsx
Jean-Luc Makiola edc9793c2d feat: migrate setup visibility from boolean to three-tier system
Replace isPublic boolean with visibility enum (private/link/public) across
the full stack. Add shares table to schema for future share link support.
Update all services, routes, schemas, hooks, components, and tests.

Plan: 32-01 (Setup Sharing System - Schema Migration)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:55:46 +02:00

114 lines
3.6 KiB
TypeScript

import { useState } from "react";
import { useCreateSetup, useSetups } from "../hooks/useSetups";
import { SetupCard } from "./SetupCard";
export function SetupsView() {
const [newSetupName, setNewSetupName] = useState("");
const { data: setups, isLoading } = useSetups();
const createSetup = useCreateSetup();
function handleCreateSetup(e: React.FormEvent) {
e.preventDefault();
const name = newSetupName.trim();
if (!name) return;
createSetup.mutate({ name }, { onSuccess: () => setNewSetupName("") });
}
return (
<div>
{/* Create setup form */}
<form onSubmit={handleCreateSetup} className="flex gap-2 mb-6">
<input
type="text"
value={newSetupName}
onChange={(e) => setNewSetupName(e.target.value)}
placeholder="New setup name..."
className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent"
/>
<button
type="submit"
disabled={!newSetupName.trim() || createSetup.isPending}
className="px-4 py-2 bg-gray-700 hover:bg-gray-800 disabled:opacity-50 text-white text-sm font-medium rounded-lg transition-colors"
>
{createSetup.isPending ? "Creating..." : "Create"}
</button>
</form>
{/* Loading skeleton */}
{isLoading && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[1, 2].map((i) => (
<div
key={i}
className="h-24 bg-gray-200 rounded-xl animate-pulse"
/>
))}
</div>
)}
{/* Empty state */}
{!isLoading && (!setups || setups.length === 0) && (
<div className="py-16">
<div className="max-w-lg mx-auto text-center">
<h2 className="text-xl font-semibold text-gray-900 mb-8">
Build your perfect loadout
</h2>
<div className="space-y-6 text-left mb-10">
<div className="flex items-start gap-4">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 text-gray-700 font-bold text-sm shrink-0">
1
</div>
<div>
<p className="font-medium text-gray-900">Create a setup</p>
<p className="text-sm text-gray-500">
Name your loadout for a specific trip or activity
</p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 text-gray-700 font-bold text-sm shrink-0">
2
</div>
<div>
<p className="font-medium text-gray-900">Add items</p>
<p className="text-sm text-gray-500">
Pick gear from your collection to include in the setup
</p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 text-gray-700 font-bold text-sm shrink-0">
3
</div>
<div>
<p className="font-medium text-gray-900">Track weight</p>
<p className="text-sm text-gray-500">
See weight breakdown and optimize your pack
</p>
</div>
</div>
</div>
</div>
</div>
)}
{/* Setup grid */}
{!isLoading && setups && setups.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{setups.map((setup) => (
<SetupCard
key={setup.id}
id={setup.id}
name={setup.name}
visibility={setup.visibility}
itemCount={setup.itemCount}
totalWeight={setup.totalWeight}
totalCost={setup.totalCost}
/>
))}
</div>
)}
</div>
);
}