Fix bouquet builder: bypass Online filter via dedicated API endpoint

Items in the Square "Build" category don't need the "Online" category
to appear in the bouquet builder. A new /api/bouquet-items endpoint
calls getSquareCatalog({ filterCategory: 'build' }) which skips the
Online filter and returns only Build-category items directly from Square.

BouquetPicker now fetches from /api/bouquet-items instead of /api/catalog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chris 2026-06-18 13:09:13 -04:00
parent 0e1d8e5af4
commit 5e1db823cb
3 changed files with 51 additions and 21 deletions

View File

@ -0,0 +1,23 @@
import { NextResponse } from 'next/server'
import { getSquareCatalog } from '@/lib/square'
export const dynamic = 'force-dynamic'
/**
* GET /api/bouquet-items
* Returns catalog items in the Square "Build" category, bypassing the
* Online-category filter so bouquet components don't need to be listed
* items in the main storefront.
*/
export async function GET() {
try {
const items = await getSquareCatalog({ filterCategory: 'build' })
return NextResponse.json({ items }, {
headers: { 'Cache-Control': 'public, max-age=30, stale-while-revalidate=60' },
})
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.error('[bouquet-items] error:', msg)
return NextResponse.json({ items: [] }, { status: 500 })
}
}

View File

@ -63,22 +63,13 @@ export default function BouquetPicker({ product, onClose }: Props) {
useEffect(() => {
Promise.all([
fetch(BASE + '/api/catalog').then((r) => r.ok ? r.json() : { items: [] }),
fetch(BASE + '/api/bouquet-items').then((r) => r.ok ? r.json() : { items: [] }),
fetch(BASE + '/colors.json').then((r) => r.ok ? r.json() : []),
]).then(([{ items }, families]: [{ items: CatalogItem[] }, ColorFamily[]]) => {
// Log all unique category slugs so we can verify "build" is present
const allSlugs = Array.from(new Set((items as CatalogItem[]).flatMap((i) => i.categories ?? [i.category]))).sort()
console.log('[BouquetPicker] category slugs in catalog:', allSlugs)
setDebugSlugs(allSlugs)
const inBuild = (item: CatalogItem) =>
(item.categories ?? []).includes('build') || item.category === 'build'
// Everything in the Build category — non-showColors items get quantity pickers,
// showColors items (e.g. the 11" latex) get the color picker treatment
const mylars = items.filter((i) => inBuild(i) && !i.showColors)
const latex = items.filter((i) => inBuild(i) && i.showColors)
console.log(`[BouquetPicker] found ${mylars.length} mylar items, ${latex.length} latex items in "build" category`)
// /api/bouquet-items already filters by the Build category server-side
const mylars = (items as CatalogItem[]).filter((i) => !i.showColors)
const latex = (items as CatalogItem[]).filter((i) => i.showColors)
setDebugSlugs([`${(items as CatalogItem[]).length} items fetched (${mylars.length} mylar, ${latex.length} latex)`])
setBuildItems([...mylars, ...latex])
setColorFamilies(families)

View File

@ -26,7 +26,12 @@ function getCatalogClient() {
return new Client({ accessToken: token, environment: env })
}
export async function getSquareCatalog(): Promise<CatalogItem[]> {
/**
* When filterCategory is supplied, the Online-category filter is skipped and only
* items belonging to the named category (case-insensitive) are returned.
*/
export async function getSquareCatalog(options: { filterCategory?: string } = {}): Promise<CatalogItem[]> {
const { filterCategory } = options
const client = getCatalogClient()
// Fetch all pages (Square paginates listCatalog)
@ -88,14 +93,25 @@ export async function getSquareCatalog(): Promise<CatalogItem[]> {
if (c.id && c.categoryData?.name) categoryNameMap.set(c.id, c.categoryData.name)
})
// Resolve the target category ID when filtering by a specific category name
const filterCategoryId = filterCategory
? objects.find(
(o) => o.type === 'CATEGORY' && o.categoryData?.name?.toLowerCase() === filterCategory.toLowerCase()
)?.id
: undefined
const items = objects
.filter((o) => o.type === 'ITEM')
.filter((o) =>
// If an "online" category exists in Square, only show items tagged with it.
// If the category doesn't exist in this account, show all items.
!onlineCategoryId ||
(o.itemData?.categories ?? []).some((c: { id?: string }) => c.id === onlineCategoryId)
)
.filter((o) => {
const cats: { id?: string }[] = o.itemData?.categories ?? []
// When a specific category is requested, skip the Online filter entirely
// and instead only keep items that belong to that category.
if (filterCategoryId) {
return cats.some((c) => c.id === filterCategoryId)
}
// Normal storefront path: apply the Online filter.
return !onlineCategoryId || cats.some((c) => c.id === onlineCategoryId)
})
.map((item) => {
const data = item.itemData!