Initial build: Great Bear microbrewery site

- TanStack Start (React 19, Tailwind 4) frontend recreated from Stitch design
- PocketBase backend with schema migration + idempotent content seed
- Single Docker image running both services (non-root, healthchecked)
- deploy.sh to build & push to registry.tanshu.com
- Ansible playbook (roles: app, caddy) deploying to www.greatbear.in
  with Caddy TLS entries for the site and the PocketBase admin
This commit is contained in:
tanshu committed 2026-09-03 08:51:47 +05:30
commit 7b070fde81
66 files changed
+8375

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
import * as React from 'react'
import type { MenuCategory, MenuItem } from '~/lib/types'
export function MenuBrowser({
categories,
items,
}: {
categories: MenuCategory[]
items: MenuItem[]
}) {
const [active, setActive] = React.useState<string>('all')
const visible = items.filter((item) => active === 'all' || item.category === active)
return (
<>
{/* Sticky Menu Navigation Filter */}
<section className="sticky top-20 z-40 bg-surface/90 py-space-md shadow-md backdrop-blur-md">
<div className="no-scrollbar mx-auto flex max-w-7xl items-center justify-start gap-space-sm overflow-x-auto px-grid-margin md:justify-center">
<FilterTab
key="all"
label="All Offerings"
active={active === 'all'}
onClick={() => setActive('all')}
/>
{categories.map((category) => (
<FilterTab
key={category.slug}
label={category.name}
active={active === category.slug}
onClick={() => setActive(category.slug)}
/>
))}
</div>
</section>
{/* Main Menu Grid */}
<section className="mx-auto w-full max-w-7xl px-grid-margin py-space-xl">
<div className="grid grid-cols-1 gap-grid-gutter md:grid-cols-2">
{visible.map((item) => (
<div
key={item.uid}
className="flex flex-col overflow-hidden rounded-xl bg-surface-container shadow-md transition-all duration-300 hover:shadow-xl"
>
<div
className="h-64 w-full bg-cover bg-center"
style={{ backgroundImage: `url('${item.image}')` }}
/>
<div className="flex flex-grow flex-col justify-between">
<div>
<div className="mb-space-xs flex items-center justify-between">
<h3 className="font-headline-md text-on-surface">{item.name}</h3>
<span className="font-headline-md text-primary">₹{item.price}</span>
</div>
<p className="mb-space-md text-body-md text-on-surface-variant">
{item.description}
</p>
</div>
<div className="-mx-space-lg -mb-space-lg flex items-center justify-between bg-surface-container-low p-space-md pt-space-md">
<div className="flex items-center gap-space-sm">
<span
className="material-symbols-outlined text-[20px] text-primary"
style={{ fontVariationSettings: "'FILL' 1" }}
>
sports_bar
</span>
<span className="text-label-md text-on-surface">
Perfect Pairing:{' '}
<strong className="text-primary">{item.pairing}</strong>
</span>
</div>
<span className="rounded bg-surface-container-highest px-space-sm py-space-xs text-label-md uppercase text-on-surface-variant">
{item.tag}
</span>
</div>
</div>
</div>
))}
</div>
</section>
</>
)
}
function FilterTab({
label,
active,
onClick,
}: {
label: string
active: boolean
onClick: () => void
}) {
return (
<button
type="button"
onClick={onClick}
className={
active
? 'whitespace-nowrap rounded-lg bg-primary-container px-space-md py-space-sm text-label-lg font-bold text-on-primary-container transition-all'
: 'whitespace-nowrap rounded-lg bg-surface-container px-space-md py-space-sm text-label-lg text-on-surface-variant transition-all hover:bg-surface-container-high'
}
>
{label}
</button>
)
}