FloCafe
FloCafe는 구독 없이 무료로 주문·테이블·주방 디스플레이·로컬 데이터를 관리하는 로컬 퍼스트 레스토랑 POS입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기מבצעי בירה — 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