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.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证מבצעי בירה — Hebrew RTL PWA for supermarket beer deals
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗A mobile-first Hebrew PWA that answers one question in seconds: which beer is on sale, how much, and where.
Send one link. The person opens it, picks their name and four beers, and is looking at deals. No sign-up, no password, no app store.
src/middleware.ts creates an anonymous Supabase session on the first
request. That is the whole trick behind "open the link and use it": every
visitor has a real auth.uid() from the first page load, so Row Level Security
can own every rule — your profile, your favourites, your submission — without
anyone ever seeing a login form. Admins sign in with an email and password at
/admin/login, which replaces the anonymous session with a named one.
Not one rule that matters lives in React. The pages hide buttons as a courtesy;
the rules are in supabase/migrations/0003_rls.sql and in the SECURITY DEFINER functions in 0002_functions.sql:
| the rule | what actually enforces it |
|---|---|
| a submitted deal is never published automatically | the deals_submit policy accepts an INSERT only when status = 'pending' |
| a report never removes a deal | nothing in the schema connects deal_reports to deals.status — the absence is the feature |
| users cannot approve or edit deals | there is no UPDATE policy for them; approve_deal() calls require_admin() |
| only a super admin manages admins | public.admins has no INSERT policy at all; it is writable only through grant_admin() / revoke_admin() |
| the discount is never typed by hand | deals.discount_percent is a generated column |
tests/security.test.ts asserts each of those against the real migration files
on an in-memory Postgres, from inside a non-superuser session — because RLS does
not apply to a superuser, and a test that forgets that proves nothing.
Prices are whole agorot in an integer column. The shekel conversion happens
on the string, because parseFloat("19.99") * 100 is 1998.9999999999998.
See src/lib/money.ts — and note that a comma is read as a decimal separator,
since 24,90 typed into a phone must not become ₪2,490.
Six-packs, cases and singles are three separate worlds. parseCategory() falls
back to one category rather than to "all", and getDealsForBeer() takes the
category as a required argument. A mixed list sorted by price would always put
a ₪8.90 single above a ₪24.90 six-pack and make the better deal look worse.
The Primary Beer — favourite #1, derived by a database trigger — sets four CSS variables and nothing else. Four colours cannot move a button, which is how the layout stays identical across every theme. The colours are ours, chosen to evoke each beer; no logos, no brand palettes, no trade dress.
Only the Primary Beer, never favourites #2–#4. A deal must clear both
admin-configured thresholds (minimum discount and maximum price), and a
per-person-per-beer cooldown stops a Thursday-evening run of approvals from
becoming five buzzes. deals.notified_at means a deal is announced once, even
if it is later edited and re-approved.
npm run dev # local dev server
npm run verify # typecheck + lint + tests + production build
npm test # security, migrations, release rule, components (no credentials)
npm run e2e # Playwright on a local fake Supabase, at 390x844 and 430x932
npm run changelog # regenerate CHANGELOG.md from src/content/releases.json
npm run sql:build # write supabase/RUN_THIS_IN_SUPABASE.sql (generated, not in git)
npm run icons # regenerate the PWA icons
npm run vapid # generate a Web Push key pair (once, ever)
Every meaningful change ships a release entry (version bump + src/content/releases.json
npm run changelog); a test fails if it does not. The rule, and the other
non-negotiables, are in CLAUDE.md. CI (.github/workflows/ci.yml) runs
everything above on every pull request.src/
middleware.ts anonymous session + token refresh
app/ screens; /admin is the moderation side; /whats-new is the release history
components/ offer lines and strips, hero, listing search, settings editors, onboarding
components/ui/ the design system's parts: Tape, Price, Chip, TabBar, icons, progress bar
design/ colour maths and the beer themes (data, with guaranteed contrast)
content/releases.json the release record: what's-new screen + CHANGELOG.md come from it
lib/
money.ts agorot, and the string-based conversion
offers.ts reads of the offers_feed / regular_prices_feed views
offer-logic.ts grouping, sorting, headline pick (pure, tested)
search.ts Hebrew-aware search
categories.ts the three legacy pack categories (deal submissions)
session.ts who is asking, in one round trip
queries.ts catalogue and settings reads
actions/ server actions — each one calls a checked RPC
push/ who gets woken up, and the message they see
supabase/migrations/ the schema, the write API, and RLS
supabase/seed/ sample data for dev/staging ONLY (production refuses to show it)
tests/ security rules against the real migrations; components; release rule
tests/e2e/ Playwright specs + a fake Supabase (real migrations on PGlite)
docs/ setup, collaborators, INFRASTRUCTURE.md, LESSONS.md, the 2026-09-29 product audit
DESIGN.md, PRODUCT.md the design system ("The Tape") and who the product is for