diff --git a/estore/src/app/api/catalog/route.ts b/estore/src/app/api/catalog/route.ts index 73bb345..9b95fa9 100644 --- a/estore/src/app/api/catalog/route.ts +++ b/estore/src/app/api/catalog/route.ts @@ -70,11 +70,15 @@ function applyOverrides(items: CatalogItem[]): CatalogItem[] { }) } +const CACHE = 'public, max-age=20, stale-while-revalidate=40' + export async function GET() { try { const { items: rawItems } = await getCatalog() const items = applyOverrides(rawItems) - return NextResponse.json({ items, source: 'square' }) + return NextResponse.json({ items, source: 'square' }, { + headers: { 'Cache-Control': CACHE }, + }) } catch (err) { console.error('[catalog] error:', err) return NextResponse.json({ items: [], source: 'error' }, { status: 500 }) diff --git a/estore/src/app/api/categories-display/route.ts b/estore/src/app/api/categories-display/route.ts index 17f29e4..36f95b4 100644 --- a/estore/src/app/api/categories-display/route.ts +++ b/estore/src/app/api/categories-display/route.ts @@ -2,5 +2,7 @@ import { NextResponse } from 'next/server' import { getCategoryDisplayConfig } from '@/lib/categories-display' export function GET() { - return NextResponse.json(getCategoryDisplayConfig()) + return NextResponse.json(getCategoryDisplayConfig(), { + headers: { 'Cache-Control': 'public, max-age=300, stale-while-revalidate=600' }, + }) } diff --git a/estore/src/app/api/inventory/route.ts b/estore/src/app/api/inventory/route.ts index 42e92c2..2c0ff17 100644 --- a/estore/src/app/api/inventory/route.ts +++ b/estore/src/app/api/inventory/route.ts @@ -21,7 +21,9 @@ export async function GET() { const counts: Record = {} countsMap.forEach((qty, id) => { counts[id] = qty }) - return NextResponse.json({ counts }) + return NextResponse.json({ counts }, { + headers: { 'Cache-Control': 'public, max-age=10, stale-while-revalidate=20' }, + }) } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) console.error('[inventory] error:', msg) diff --git a/estore/src/app/api/occasions/route.ts b/estore/src/app/api/occasions/route.ts index 1187683..120105d 100644 --- a/estore/src/app/api/occasions/route.ts +++ b/estore/src/app/api/occasions/route.ts @@ -8,5 +8,7 @@ export async function GET() { const payload = active.map(({ key, label, emoji, blurb, squareCategorySlug }) => ({ key, label, emoji, blurb, squareCategorySlug, })) - return NextResponse.json({ occasions: payload }) + return NextResponse.json({ occasions: payload }, { + headers: { 'Cache-Control': 'public, max-age=300, stale-while-revalidate=600' }, + }) } diff --git a/estore/src/app/globals.css b/estore/src/app/globals.css index 7469cf2..d56679b 100644 --- a/estore/src/app/globals.css +++ b/estore/src/app/globals.css @@ -191,7 +191,8 @@ color: #334854; width: 100%; white-space: normal; - word-break: break-word; + overflow-wrap: normal; + word-break: normal; } .color-family-heading { diff --git a/estore/src/app/layout.tsx b/estore/src/app/layout.tsx index f8ace10..911d7c6 100644 --- a/estore/src/app/layout.tsx +++ b/estore/src/app/layout.tsx @@ -24,7 +24,10 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( + {/* Preconnect to Square product image CDN */} + {/* Google Fonts — same as beachpartyballoons.com */} + { if (!drawerOpen) return - const prev = document.body.style.overflow + const scrollY = window.scrollY + const prevOverflow = document.body.style.overflow + const prevPosition = document.body.style.position + const prevTop = document.body.style.top + const prevWidth = document.body.style.width document.body.style.overflow = 'hidden' - return () => { document.body.style.overflow = prev } + document.body.style.position = 'fixed' + document.body.style.top = `-${scrollY}px` + document.body.style.width = '100%' + return () => { + document.body.style.overflow = prevOverflow + document.body.style.position = prevPosition + document.body.style.top = prevTop + document.body.style.width = prevWidth + window.scrollTo(0, scrollY) + } }, [drawerOpen]) const [editingEntry, setEditingEntry] = useState(null) diff --git a/estore/src/components/ProductCard.tsx b/estore/src/components/ProductCard.tsx index d6f8b0d..99bd666 100644 --- a/estore/src/components/ProductCard.tsx +++ b/estore/src/components/ProductCard.tsx @@ -51,7 +51,7 @@ export default function ProductCard({ item }: Props) {
{item.imageUrl ? ( // eslint-disable-next-line @next/next/no-img-element - {item.name} + {item.name} ) : (
🎈
)} diff --git a/estore/src/lib/useLockBodyScroll.ts b/estore/src/lib/useLockBodyScroll.ts index 7fb4c28..ab51294 100644 --- a/estore/src/lib/useLockBodyScroll.ts +++ b/estore/src/lib/useLockBodyScroll.ts @@ -1,10 +1,27 @@ import { useEffect } from 'react' -/** Locks body scroll while the calling component is mounted. */ +/** Locks body scroll while the calling component is mounted. + * Uses position:fixed + scroll-position restore so iOS Safari + * rubber-band scrolling is also prevented. */ export function useLockBodyScroll() { useEffect(() => { - const prev = document.body.style.overflow + const scrollY = window.scrollY + const prevOverflow = document.body.style.overflow + const prevPosition = document.body.style.position + const prevTop = document.body.style.top + const prevWidth = document.body.style.width + document.body.style.overflow = 'hidden' - return () => { document.body.style.overflow = prev } + document.body.style.position = 'fixed' + document.body.style.top = `-${scrollY}px` + document.body.style.width = '100%' + + return () => { + document.body.style.overflow = prevOverflow + document.body.style.position = prevPosition + document.body.style.top = prevTop + document.body.style.width = prevWidth + window.scrollTo(0, scrollY) + } }, []) }