FloCafe
FloCafe is the free, local-first restaurant POS in the FloPOS ecosystem. Run orders, tables, kitchen display, and local data without a software subscription.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Premium NFC ve QR destekli dijital kartvizit platformu.
Built with Next.js 16 (App Router), React 19, Tailwind CSS v4, shadcn/ui, and Supabase.
| Tool | Version |
|---|---|
| Node | 20.9+ (.nvmrc pins 24) |
| npm | 10+ |
| Docker | Required only for the local Supabase stack |
nvm use
npm install
cp .env.example .env.local
Start the local Supabase stack (Postgres, Auth, Storage, Studio) and apply every migration plus the seed data:
npm run db:start
npm run db:reset
db:start prints the local API URL and keys. Copy them into .env.local, then:
npm run gen:types # regenerate lib/db/database.types.ts from the live schema
npm run dev
The app runs at http://localhost:3000. Supabase Studio is at http://localhost:54323, and outbound mail is captured at http://localhost:54324 rather than being sent.
The seed creates one confirmed account: [email protected] / Dijivizit123!.
The local Auth container reads Google credentials from the environment:
# supabase/.env (gitignored)
SUPABASE_AUTH_GOOGLE_CLIENT_ID=...
SUPABASE_AUTH_GOOGLE_SECRET=...
Register http://127.0.0.1:54321/auth/v1/callback as an authorized redirect URI
in the Google Cloud console.
| Script | Purpose |
|---|---|
npm run dev | Development server |
npm run build | Production build |
npm run typecheck | tsc --noEmit |
npm run lint | ESLint, warnings treated as failures |
npm run format | Prettier write |
npm test | Vitest unit tests |
npm run db:start | Start the local Supabase stack |
npm run db:reset | Drop, re-migrate, and re-seed the local database |
npm run db:diff | Diff local schema changes into a new migration |
npm run db:push | Apply migrations to the linked hosted project |
npm run gen:types | Regenerate database types from the local schema |
app/
(marketing)/ Public landing page
(auth)/ Login, register, and the auth server actions
(dashboard)/ Authenticated area, guarded by requireUser()
p/[publicId]/ Public digital card — the shareable URL
auth/callback/ OAuth (PKCE) code exchange
auth/confirm/ Email link verification
middleware.ts Session refresh and coarse redirects (Edge runtime)
components/ui/ shadcn/ui primitives
components/auth/ Auth forms
lib/
supabase/ Browser, server, and proxy clients
dal/ Data Access Layer — the authorization boundary
validations/ Zod schemas shared by client and server
i18n/ next-intl configuration and Intl helpers
env/ Zod-validated environment variables
messages/tr.json All user-facing copy
supabase/migrations/ Schema as version-controlled SQL
Every profile gets an 11-character base62 public_id (~65.5 bits of entropy)
generated in Postgres from gen_random_bytes with rejection sampling to avoid
modulo bias. Cards live at /p/<public_id> and this is what NFC tags and QR
codes encode.
This only works if the ids cannot be enumerated, which drives two decisions:
select ... where is_published RLS policy would expose
GET /rest/v1/profiles?is_published=eq.true through PostgREST and let anyone
with the publishable key list every card. Instead anon reads exclusively
through public.get_public_card(p_public_id), a security definer function
that takes an exact id and returns at most one row./p/[publicId] sets
robots: { index: false, follow: false }.middleware.ts refreshes the Supabase session and handles redirects, but Server
Actions are POSTs to the route that hosts them, so a matcher change can silently
remove proxy coverage. Every entry point that touches private data calls
requireUser() from lib/dal/auth.ts, which verifies the JWT signature via
getClaims(). getSession() is never used for authorization — it reads a
client-controlled cookie without revalidating it.
Turkish only in V1, served without a locale segment in the URL. Every
user-facing string lives in messages/tr.json; none are hardcoded in
components, and validation errors carry message keys rather than prose.
To add a locale: add messages/<locale>.json, list it in
lib/i18n/config.ts, switch lib/i18n/request.ts to a dynamic import, and move
the route groups under app/[locale]/.
The UI follows a restrained, typographic aesthetic in the vein of Apple, Linear,
Notion and Stripe: whitespace and type carry hierarchy, the palette is
neutral-forward, and motion is limited to short ease-out transitions that honour
prefers-reduced-motion. No gradients, illustrations, or marketing sections.
The binding version of these constraints, including what is explicitly
forbidden, lives in .cursor/rules/design-language.mdc so coding agents apply it
automatically. --brand is a deliberately low-chroma accent reserved for
identity moments; it is not a surface colour.
This project targets Next.js 16, which differs from older guides in ways that matter here:
middleware.ts to proxy.ts (Node.js only). This app keeps
middleware.ts on purpose so it runs on the Edge runtime that Cloudflare
Workers support natively (see the comment in middleware.ts).params, searchParams, cookies(), and headers() are async-only.dev and build; next lint is removed.images.domains is removed in favour of images.remotePatterns.images.dangerouslyAllowLocalIP, which is
enabled in development so the local Supabase storage endpoint works.