d892b3f64f
Deploy to route.crispygoat.com / deploy (push) Failing after 9s
Phase 1 — Pattern library - Add src/lib/api.ts (typed PostgREST client, anon-key) - Add src/lib/svc-fetch.ts (service-role fetch + PostgREST client) - Add src/lib/db-types.ts (Database generic, RowOf helper) - Add src/lib/auth-admin.ts (better-auth admin wrappers: createUser, listUsers, removeUser, requestPasswordReset, validateSession) Phase 2 — Server action migration - Migrate all 67 server actions off @supabase/supabase-js to svcApi/svcRpc - Replace service.auth.admin.* with authAdmin.* (better-auth) - Replace publicApi.auth.* with authAdmin.* (better-auth) - Add typed single() null guards + nullable column fallbacks Phase 3 — Delete mock data + debug routes - Remove if(useMockData) branches from 7 files (-226 lines) - Delete src/lib/mock-data.ts (no longer referenced) - Delete 7 debug API routes (debug-admin-users, debug-auth, debug-cookie, debug-env, debug-get-admin-users, debug-hello, debug-me) Phase 4 — Hard cut - Delete src/lib/supabase.ts (orphan — no importers) - Delete src/app/api/supabase + src/app/api/supabase-test routes - Remove @supabase/ssr + @supabase/supabase-js from package.json - Rename env vars: NEXT_PUBLIC_SUPABASE_URL → NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY → NEXT_PUBLIC_API_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY → POSTGREST_SERVICE_KEY Phase 5 — Verify - npx tsc --noEmit: 0 errors - npm run lint: 0 new errors from migration (104 pre-existing errors unrelated) - npm run build: same failure mode as main worktree (PostgREST-dependent prerender), no regression Net: 158 files changed, +1640 / -1903 lines (-263 net)
99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
import StopTableClient from "@/components/admin/StopTableClient";
|
|
import StopsHeaderActions from "@/components/admin/StopsHeaderActions";
|
|
import { api } from "@/lib/api";
|
|
import { getAdminUser } from "@/lib/admin-permissions";
|
|
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
|
|
import { PageHeader } from "@/components/admin/design-system";
|
|
import { redirect } from "next/navigation";
|
|
|
|
const StopIcon = () => (
|
|
<svg className="h-5 w-5 sm:h-6 sm:w-6 text-current" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/>
|
|
<circle cx="12" cy="10" r="3"/>
|
|
</svg>
|
|
);
|
|
|
|
export default async function AdminStopsPage() {
|
|
const adminUser = await getAdminUser();
|
|
|
|
if (!adminUser) return <AdminAccessDenied />;
|
|
|
|
if (!adminUser.can_manage_stops) {
|
|
redirect("/admin/pickup");
|
|
}
|
|
|
|
let query = api
|
|
.from("stops")
|
|
.select(`
|
|
id,
|
|
city,
|
|
state,
|
|
date,
|
|
time,
|
|
location,
|
|
active,
|
|
deleted_at,
|
|
brand_id,
|
|
address,
|
|
zip,
|
|
cutoff_time,
|
|
status,
|
|
brands (
|
|
name
|
|
)
|
|
`)
|
|
.is("deleted_at", null)
|
|
.order("date", { ascending: true });
|
|
|
|
if (adminUser?.brand_id) {
|
|
query = query.eq("brand_id", adminUser.brand_id);
|
|
}
|
|
|
|
const { data: stops, error } = (await query) as any;
|
|
|
|
if (error) {
|
|
return (
|
|
<main className="min-h-screen bg-[var(--admin-bg)]">
|
|
<div className="px-4 sm:px-6 md:px-8 py-6 sm:py-8">
|
|
<div className="max-w-6xl mx-auto">
|
|
<nav className="flex items-center gap-2 text-xs sm:text-sm mb-6">
|
|
<a href="/admin" className="text-[var(--admin-text-muted)] hover:text-[var(--admin-text-secondary)]">Admin</a>
|
|
<span className="text-[var(--admin-text-muted)]">/</span>
|
|
<span className="text-[var(--admin-text-primary)] font-medium">Stops & Routes</span>
|
|
</nav>
|
|
<h1 className="text-2xl sm:text-3xl font-black text-red-600 tracking-tight">
|
|
Error loading stops
|
|
</h1>
|
|
<pre className="mt-4 rounded-xl bg-white border border-[var(--admin-border)] p-4 text-sm text-[var(--admin-text-secondary)]">
|
|
{error.message}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<main className="min-h-screen bg-[var(--admin-bg)]">
|
|
<div className="px-4 sm:px-6 md:px-8 py-6 sm:py-8">
|
|
<PageHeader
|
|
breadcrumb={[
|
|
{ label: "Admin", href: "/admin" },
|
|
{ label: "Stops & Routes" }
|
|
]}
|
|
icon={<StopIcon />}
|
|
title="Stops & Routes"
|
|
subtitle={adminUser?.brand_id ? "Managing stops for your brand." : "Manage routes, pickup locations, dates, and cutoff times."}
|
|
actions={<StopsHeaderActions brandId={adminUser?.brand_id ?? ""} />}
|
|
/>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="px-4 sm:px-6 md:px-8 py-4 sm:py-6">
|
|
<div className="overflow-hidden rounded-2xl border border-[var(--admin-border)] bg-white shadow-sm">
|
|
<StopTableClient stops={stops ?? []} />
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
} |