refactor(storefront): remove supabase shim and restore customer storefronts
The legacy src/lib/supabase.ts shim returned { data: null } for every
query, so 15+ pages were silently rendering empty state — including the
two customer-facing storefronts (tuxedo, indian-river-direct) and
several v1 admin pages. Replace every caller with canonical access:
- New src/actions/storefront.ts server-action module: brand lookup,
public stops, active products, stop-by-slug, wholesale settings,
portal config. Uses the shared pg Pool and SECURITY DEFINER RPCs.
- src/actions/brand-settings.ts: getBrandSettingsPublic inlined the
brands + brand_settings LEFT JOIN wholesale_settings query (the
RPC it called did not exist; try/catch was masking the failure).
- src/actions/ai/preferences.ts: switched get/saveAIPreferences to
pool.query.
- src/actions/square-sync-ui.ts: new getSquareQueueCount action for
SquareSyncWidget (replaces shim count).
- src/app/api/{tuxedo,indian-river-direct}/schedule-pdf/route.ts:
use pool.query.
- next.config.ts: redirects /admin/{products/:id,reports,taxes,
settings/{shipping,integrations,billing}} → their v2 / settings
equivalents, then deleted those pages.
- Deleted src/lib/supabase.ts (no remaining imports).
Tests: 174/175 (unchanged from baseline; pre-existing getAdminUser
mock issue is tracked in Step 5).
This commit is contained in:
@@ -1,158 +0,0 @@
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import ProductEditForm from "@/components/admin/ProductEditForm";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { PageHeader, AdminBadge } from "@/components/admin/design-system";
|
||||
import { Package as PackageIcon } from "lucide-react";
|
||||
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
|
||||
import { redirect } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
type ProductDetailPageProps = {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
interface Product {
|
||||
id: string;
|
||||
brand_id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
price: number;
|
||||
active: boolean;
|
||||
type: string | null;
|
||||
image_url: string | null;
|
||||
is_taxable: boolean;
|
||||
pickup_type: string | null;
|
||||
brands?: { name: string; slug: string };
|
||||
}
|
||||
|
||||
interface Brand {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export default async function ProductDetailPage({ params }: ProductDetailPageProps) {
|
||||
const { id } = await params;
|
||||
|
||||
const [{ data: product, error }, { data: brands }] = await Promise.all([
|
||||
supabase.from("products").select("*, brands(name, slug)").eq("id", id).single() as unknown as { data: Product | null; error: { message: string } | null },
|
||||
supabase.from("brands").select("id, name, slug") as unknown as { data: Brand[] | null },
|
||||
]);
|
||||
|
||||
const adminUser = await getAdminUser();
|
||||
|
||||
if (!adminUser) return <AdminAccessDenied />;
|
||||
|
||||
if (!adminUser.can_manage_products) redirect("/admin/pickup");
|
||||
|
||||
if (adminUser?.brand_id && product?.brand_id !== adminUser.brand_id) {
|
||||
return <AdminAccessDenied />;
|
||||
}
|
||||
|
||||
if (error || !product) {
|
||||
return (
|
||||
<main className="min-h-screen bg-[var(--admin-bg)] px-4 sm:px-6 md:px-8 py-6 sm:py-8">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<h1 className="text-3xl font-bold text-[var(--admin-danger)]">Product not found</h1>
|
||||
<pre className="mt-4 rounded-xl bg-[var(--admin-card-bg)] border border-[var(--admin-border)] p-4 text-sm text-[var(--admin-text-muted)]">
|
||||
{error?.message ?? "Product not found"}
|
||||
</pre>
|
||||
<Link
|
||||
href="/admin/products"
|
||||
className="mt-4 inline-block text-sm text-[var(--admin-text-muted)] hover:text-[var(--admin-text-primary)]"
|
||||
>
|
||||
← Back to Products
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-[var(--admin-bg)] px-4 sm:px-6 md:px-8 py-6 sm:py-8">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<div className="ha-eyebrow mb-2">Operations</div>
|
||||
<PageHeader
|
||||
icon={<PackageIcon className="h-5 w-5" strokeWidth={1.75} />}
|
||||
title="Edit product"
|
||||
subtitle="Update product details, pricing, and availability."
|
||||
actions={
|
||||
<AdminBadge tone={product.active ? "success" : "neutral"} dot>
|
||||
{product.active ? "Active" : "Inactive"}
|
||||
</AdminBadge>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mb-6">
|
||||
<Link
|
||||
href="/admin/products"
|
||||
className="text-sm text-[var(--admin-text-muted)] hover:text-[var(--admin-text-primary)] transition-colors"
|
||||
>
|
||||
← Back to Products
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl bg-[var(--admin-card-bg)] p-8 shadow-sm border border-[var(--admin-border)]">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-[var(--admin-text-muted)]">
|
||||
{product.brands?.name}
|
||||
</p>
|
||||
<h1 className="mt-2 text-3xl font-bold text-[var(--admin-text-primary)]">
|
||||
{product.name}
|
||||
</h1>
|
||||
<p className="mt-2 text-lg text-[var(--admin-text-secondary)]">
|
||||
{product.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--admin-text-muted)]">Price</p>
|
||||
<p
|
||||
className="mt-1 text-2xl font-bold text-[var(--admin-text-primary)]"
|
||||
style={{ fontFamily: "var(--font-fragment-mono)", fontVariantNumeric: "tabular-nums" }}
|
||||
>
|
||||
${Number(product.price).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--admin-text-muted)]">Type</p>
|
||||
<p className="mt-1 text-lg font-semibold text-[var(--admin-text-primary)]">
|
||||
{product.type}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-2xl bg-[var(--admin-card-bg)] p-8 shadow-sm border border-[var(--admin-border)]">
|
||||
<h2 className="text-2xl font-bold text-[var(--admin-text-primary)]">Edit Product</h2>
|
||||
<p className="mt-1 text-[var(--admin-text-muted)]">
|
||||
Update product details, pricing, and availability.
|
||||
</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<ProductEditForm
|
||||
product={{
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
description: product.description ?? "",
|
||||
price: Number(product.price),
|
||||
type: product.type ?? "pickup",
|
||||
active: product.active,
|
||||
brand_id: product.brand_id,
|
||||
image_url: product.image_url ?? "",
|
||||
is_taxable: product.is_taxable,
|
||||
pickup_type: product.pickup_type ?? "pickup",
|
||||
}}
|
||||
brands={brands ?? []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user