Salt la conținut
Înapoi la proiectele selectate
Demo live · Stripe în mod test
Sigla KROMA

KROMA

O cafenea cu precomandă construită de la cap la coadă — o vitrină live cu plată Stripe și un backstage real: tablou de bucătărie, zi de lucru și cifre.

Imaginea principală a vitrinei KROMA

O cafenea de specialitate fictivă, construită ca un sistem complet cu precomandă, nu ca un simplu mockup. Clienții parcurg un meniu zilnic live, personalizează o băutură, plătesc prin Stripe și urmăresc comanda; personalul lucrează aceeași comandă pe un tablou de bucătărie, rulează o zi de lucru care se încheie cu numărarea casei și citește cifrele cafenelei — în timp ce prețurile, stocul, starea plății și permisiunile sunt stabilite în Postgres, nu preluate pe încredere din browser.

// Stratul de date

Logică în primul rând în Postgres

Prețurile, stocul, permisiunile și rapoartele trăiesc în funcții SQL protejate prin Row Level Security.

// Plăți

Plata înaintea comenzii

O comandă cu cardul este creată abia după ce Stripe confirmă plata; eșecurile sunt rambursate.

// Zona personalului

Tablou de bucătărie Realtime

Supabase Realtime pe comenzi, cu interogare periodică la 30 de secunde ca rezervă.

// Verificare

Testat pe două niveluri

node:test pentru logica aplicației, plus teste SQL care rulează într-o tranzacție anulată la final.

// prezentare

Ce este KROMA

KROMA este o cafenea de specialitate fictivă, construită ca o aplicație web completă, gândită pentru producție. Un client parcurge un meniu zilnic live, personalizează o băutură, plătește cu cardul prin Stripe sau la bar și urmărește comanda; personalul lucrează aceeași comandă pe un tablou de bucătărie și gestionează stocul, meniul, rambursările și numărarea casei de la sfârșitul zilei din aceeași aplicație. Este un proiect de portofoliu — cafeneaua este fictivă, iar plățile rulează în modul de test Stripe — dar fluxul din spatele ei este real.

Prețul, stocul și permisiunile fiecărei comenzi sunt stabilite în Postgres, astfel încât vitrina, tabloul de bucătărie și raportul zilei citesc aceeași înregistrare de comandă.

// 01. Context strategic

De ce este construit KROMA astfel

01 // Problema

Un buton de plată nu înseamnă un magazin

Demo-urile de precomandă se opresc adesea la un buton Comandă: un meniu static și nimic în spatele lui. Ce desparte un sistem funcțional de un mockup este tot ceea ce aceste demo-uri omit — stocul care se termină la mijlocul dimineții, starea plății care trebuie verificată pe server și o a doua interfață pentru cei care pregătesc de fapt comanda.

Dinamica-cheie: Două interfețe, o singură comandă
02 // Soluția

O singură înregistrare de comandă, două interfețe

KROMA este construit ca un sistem cu două fețe. Pagina de comandă a clientului și tabloul de bucătărie al personalului citesc și avansează aceeași înregistrare de comandă. Funcțiile Postgres stabilesc prețul fiecărei comenzi, scad stocul și decid cine are voie să facă ce; aplicația Next.js le apelează.

Dinamica-cheie: Regulile stau lângă date
03 // Rezultatul

Un magazin care își conduce singur ziua

Un job zilnic Vercel Cron deschide ziua de lucru, iar personalul o închide numărând sertarul casei față de un raport generat. Demo-ul rămâne sigur chiar și în fața unor străini: plățile cu cardul rulează în modul de test Stripe, iar PIN-ul personalului este o credențială reală cu drept de scriere, care nu este publicată.

Dinamica-cheie: Zi deschisă prin cron, închidere cu numărare
// principiu arhitectural
„Browserul propune; Postgres decide. Clientul trimite ID-uri de produse, cantități și nume de modificatori — fiecare preț, modificare de stoc și verificare de permisiuni este rezolvată în baza de date.”
— Principiu arhitectural
// 02. Tur al produsului

Vedeți KROMA în acțiune

O prezentare a vitrinei pentru clienți și a zonei personalului — ce face fiecare parte și cum se simte utilizarea ei

Partea clientului

Ce întâlnește un invitat sau un membru, de la răsfoirea meniului până la ridicarea comenzii

01Vitrină

Vitrină cu meniu live

Un meniu zilnic live, prezentat ca o listă editorială, nu ca o grilă de carduri. Stocul și prețurile vin direct din Postgres, așa că un lot epuizat apare ca terminat în decurs de un minut.

Captură de ecran a vitrinei KROMA, cu lista editorială a meniului și panoul de previzualizare fix
  • Meniu zilnic live: Produsele, prețurile și stocul pe produs sunt citite din Postgres și se revalidează la fiecare 30 de secunde. Dacă interogarea nu returnează nimic, vitrina revine la un instantaneu local menu.json.
  • Imagine principală WebGL cu hartă de adâncime: Un fragment shader deplasează fotografia principală de-a lungul unei hărți de adâncime în tonuri de gri, pe măsură ce se mișcă cursorul. Revine la o imagine statică fără WebGL, la eșecul încărcării sau cu mișcare redusă.
  • Listă editorială de meniu: Rânduri pe toată lățimea, separate prin linii fine, cu un panou de previzualizare fix pe desktop, care schimbă fotografia la trecerea cursorului sau la focalizarea cu tastatura.
  • Filtrare pe categorii: Navigare cu butoane-pastilă, a căror umplere activă alunecă între categorii în loc să sară.
  • Personalizarea produsului: Un panou lateral glisant pentru modificatori și variante, cu prețul recalculat pe măsură ce alegeți.
  • Stări de stoc: Un produs epuizat apare ca „Terminat pentru azi”; dacă au rămas cinci sau mai puține, apare „Mai sunt doar N”.
Next.js 16SupabaseFramer MotionWebGLRevalidare la 30 s
02Plată și urmărire

Comandați, plătiți, urmăriți

Plătiți cu cardul prin Stripe Checkout sau alegeți plata la bar, apoi urmăriți comanda pe o pagină care poate fi distribuită. O comandă cu cardul există abia după ce Stripe confirmă plata.

  • Invitat sau cont: Comanda funcționează și fără cont. Coșul unui client autentificat este stocat în Postgres, iar coșul de invitat i se alătură la autentificare.
  • Stripe Checkout: Plățile cu cardul rulează în modul de test Stripe. Sesiunea este calculată pe baza unei oferte generate pe server și expiră după 30 de minute.
  • Plata la bar: O comandă la tejghea este înregistrată ca în așteptare; barista notează numerarul sau cardul atunci când o marchează ca plătită.
  • Verificare alimentară: Dietele și alergenii salvați semnalează în coș produsele în conflict, înainte de plată.
  • Urmărirea comenzii: O pagină /order/[token] care poate fi distribuită arată cum comanda trece de la plătită la în preparare și apoi la gata, reîmprospătându-se la fiecare 15 secunde. Înainte de a ajunge la bar, clientul o poate anula, iar plata online este rambursată.
  • Alertă de comandă gata: O notificare Web Push se trimite când comanda este gata, cu e-mail ca rezervă atunci când push-ul nu este disponibil.
  • Chitanțe: O pagină de chitanță care poate fi tipărită, cu cod de bare și linie de TVA, plus o chitanță trimisă pe e-mail prin Resend, atunci când există o adresă.
Stripe CheckoutStripe WebhooksWeb Push (VAPID)ResendServer Actions
03Conturi

Cont și card cu ștampile

Autentificarea prin e-mail sau Google deblochează un card cu ștampile, o „comandă obișnuită” dintr-o singură atingere, istoricul comenzilor și preferințele alimentare salvate.

  • Autentificare: E-mail și parolă, cu e-mail de confirmare și resetarea parolei, sau Google OAuth.
  • Card cu ștampile: Doar categoriile de băuturi primesc ștampilă. Un cont nou începe cu 2 ștampile, iar un card plin are 10. Soldul este derivat din istoricul comenzilor, nu stocat, așa că o comandă anulată sau rambursată își ia ștampilele cu ea.
  • Produs gratuit: Un card plin se răscumpără la plată pentru o unitate dintr-un produs.
  • Comanda obișnuită: Produsul comandat cel mai des de client, cu modificatorii din ultima comandă, gata de comandat din nou.
  • Preferințe: Un nume afișat, plus un nume separat pentru bar, strigat la pass, diete (vegană, vegetariană, pescetariană, fără gluten) și alergeni de evitat.
  • Istoricul comenzilor: Comenzi anterioare, cu buton Comandă din nou.
Supabase AuthGoogle OAuthRow Level SecuritySold derivat

Partea personalului

Backstage-ul de la /dashboard, în spatele unui cont real și al unui PIN din 4 cifre

PIN din 4 cifre

Autentificare a personalului pe două niveluri

iPad-ul de la bar păstrează o sesiune de lungă durată pentru ca tabloul să poată fi afișat; o persoană deblochează dreptul de a scrie cu un PIN din 4 cifre. PIN-ul este verificat numai în Postgres, cu pgcrypto, cinci introduceri greșite blochează acea persoană pentru 15 minute, iar fiecare deblocare este înregistrată.

Captură de ecran a tastaturii PIN pentru personal KROMA, pe suprafața întunecată a afișajului de bucătărie
04Afișaj bucătărie

Tabloul comenzilor

Un tablou întunecat, cu contrast ridicat, construit pentru linia de lucru. Comenzile trec prin patru culoare — La pass, Se prepară, Gata la bar, Ridicată — iar pagina clientului urmărește mișcarea.

Captură de ecran a tabloului de bucătărie KROMA, cu comenzile distribuite pe cele patru culoare și indicatoarele lor de vechime
  • Coadă live: Supabase Realtime pe tabelul de comenzi reîncarcă tabloul. Un indicator de conexiune arată starea live, reconectare sau offline, o interogare la 30 de secunde acoperă întreruperile, iar scrierile sunt dezactivate cât timp tabloul e offline.
  • Indicator de vechime: Fiecare comandă afișează un cronometru care rulează și trece de la proaspătă la caldă după 5 minute și la întârziată după 10.
  • Sunet pentru comandă nouă: Se aude un sunet când apare o comandă nouă pe tablou, după ce dispozitivul a fost armat printr-o atingere.
  • Avansare și anulare: O atingere avansează o comandă. Mutarea ei cu un culoar înapoi este permisă oricui în 90 de secunde; după aceea, doar unui manager sau proprietar.
  • Note, anulări și rambursări: Oricine se află în tură poate adăuga o notă. Managerii și proprietarii pot anula sau rambursa, iar rambursarea Stripe a unei comenzi online se emite din aplicație.
  • Reduceri și cadouri: Managerii și proprietarii pot aplica un procent, o sumă fixă sau un cadou integral, cu un motiv obligatoriu.
  • Instalabil: Tabloul se instalează ca PWA pe tot ecranul, în orientare peisaj.
Supabase RealtimePostgres RPC-uriPWAFramer Motion
05Ziua de lucru

Deschidere, vânzare, închidere

Vânzarea este organizată pe zile. Cineva deschide magazinul cu numărătoarea stocului de bază, un magazin închis refuză comenzile, iar un manager sau proprietar îl închide numărând sertarul casei față de un raport generat.

  • Deschiderea zilei: Deschiderea readuce stocul fiecărui produs la nivelul de bază sau la cantitățile introduse pe ecranul de deschidere. Orice rol poate deschide, iar două dispozitive care apasă Deschide produc o singură deschidere.
  • Magazin închis: Cât timp nicio zi nu este deschisă, plasarea unei comenzi generează „Brutăria este închisă.”
  • Numere de tichet: Fiecare zi își numerotează tichetele începând de la #001.
  • Mod de plată: La tejghea, barista notează dacă o comandă a fost plătită în numerar sau cu cardul.
  • Numărarea casei: Ecranul de închidere primește o numărătoare pe valori nominale și arată diferența față de numerarul așteptat: fondul de casă plus încasările în numerar minus rambursările în numerar.
  • Protecție la închidere: O zi nu se poate închide cât timp mai sunt comenzi la pass; eroarea listează numerele lor de tichet.
  • Raportul zilei: Comenzi, încasări, net și TVA; totaluri în numerar, cu cardul și online; sume cu reducere, anulate, rambursate și aruncate; și ce a rămas în stoc.
  • TVA: Prețurile rămân cu TVA inclus. TVA-ul este extras pe categorie (11% pentru tot ce se vinde astăzi) și salvat ca instantaneu pe fiecare linie de comandă.
Postgres RPC-uriZile de lucruTVAVercel Cron
06Analiză

Cifre și registru

O vedere asupra magazinului pentru manageri și proprietari, agregată în Postgres și nu în browser, astfel încât verificarea permisiunilor se află acolo unde se adună banii.

  • Încasări: Încasările pe o fereastră de date aleasă, filtrabile pe categorie.
  • În spatele barului: Pe persoană: comenzi începute, anulate și rambursate, cu duratele luate din aceleași rânduri de comandă.
  • Registru: Jurnalul de audit, filtrat și paginat: deblocări, mutări de comenzi, reduceri, limitele turelor, deschiderea și închiderea zilei și modificările de meniu.
  • Poartă de permisiuni: Fiecare agregare recitește rolul apelantului în baza de date și refuză pe oricine se află sub nivelul de manager.
Postgres RPC-uriJurnal de auditRestricții pe roluri
Administrare meniu

Editarea meniului live

Managerii și proprietarii editează meniul din panoul de control: reordonează produsele, deschid fișa produsului cu editorul de modificatori, gestionează categoriile și șterg produse. Fiecare scriere trece printr-o funcție cu permisiuni verificate, iar comenzile anterioare își păstrează numele și prețul produsului.

// 03. Arhitectură și topologie

Arhitectură full-stack serverless

O aplicație Next.js pe Vercel în fața unui proiect Supabase, cu Stripe, cron și push integrate prin rute de server

topology.ascii
    [ Browser · Customer & Staff PWAs ]                           [ Vercel Cron ]
      │                           │                                     │
      │                           │ HTTPS                               │ Bearer secret
      │                           ▼                                   ▼
      │                    ┌────────────────────────────────────────────────────────────────┐
      │                    │                Next.js 16 · React 19  (Vercel)                 │
      │                    ├────────────────────────────────────────────────────────────────┤
      │                    │ • Server Components & Server Actions                           │
      │                    │ • Route Handlers: Stripe webhook · cron · auth callbacks       │
      │                    │ • Signed cookie that unlocks staff writes                      │
      │                    └───────────┬─────────────────────┬───────────────────┬──────────┘
      │                                │ RPC                 │ API               │ Push · email
      │                                ▼                   ▼                  ▼
      │                     ┌──────────────────────┐  ┌───────────────┐  ┌───────────────────┐
      └─ RPC · Realtime ──►│ Supabase             │  │ Stripe        │  │ Notifications     │
                            ├──────────────────────┤  ├───────────────┤  ├───────────────────┤
                            │ • Postgres + RLS     │  │ • Checkout    │  │ • Web Push (VAPID)│
                            │ • Security-definer   │  │ • Refunds     │  │ • Resend email    │
                            │   RPCs               │  │ • Webhooks    │  └───────────────────┘
                            │ • Auth               │  └───────────────┘
                            │ • Realtime           │
                            └──────────────────────┘

Matricea deciziilor tehnice și a justificărilor

Unde se află fiecare responsabilitate și de ce acolo

Strat și domeniuAlegerea tehnologicăCapabilități-cheie și justificare
Frontend
Framework de aplicație
Next.js 16 (App Router) + React 19

Componente de server, acțiuni de server, o singură graniță de client

Vitrina este o componentă de server care preia meniul și îl revalidează la fiecare 30 de secunde; o singură graniță de client păstrează starea filtrelor și a coșului. Mutațiile sunt acțiuni de server, iar rutele (route handlers) acoperă webhook-ul Stripe, jobul cron și callback-urile de autentificare.
Persistență
Bază de date și logică
Supabase Postgres

Reguli de business ca RPC-uri security-definer, RLS pe datele clienților

Prețurile, stocul, numerele de tichet, permisiunile, rambursările și rapoartele trăiesc în funcții SQL gestionate ca migrări versionate, astfel încât vitrina, tabloul și jobul cron trec prin aceleași reguli.
Autentificare
Identitate
Supabase Auth + tabelul staff

E-mail/Google pentru clienți; sesiune de stație plus PIN pentru personal

Clienții se autentifică prin Supabase Auth. Personalul lucrează printr-o stație care poate citi, în timp ce un PIN pe persoană deblochează scrierile, astfel încât fiecare modificare este atribuită cuiva.
Comerț
Plăți
Stripe Checkout + webhook-uri

Mai întâi plata, apoi există comanda

Sesiunea Checkout este calculată dintr-o ofertă din baza de date. Comanda este creată abia după plată, din webhook sau din pagina de revenire, și este rambursată dacă nu poate fi plasată.
Timp real
Actualizări live
Supabase Realtime

Abonare subțire, preluare completă

Tabloul de bucătărie ascultă modificările din tabelul de comenzi și reîncarcă datele printr-un singur RPC, cu interogare la 30 de secunde ca rezervă. Paginile de comandă ale clienților se reîmprospătează la fiecare 15 secunde.
Operațiuni
Joburi programate
Vercel Cron

O singură rută zilnică autentificată, la 03:00 UTC

Jobul eliberează comenzile online expirate, deschide ziua de lucru prin același RPC folosit de barista și șterge abonamentele push învechite.
Mesagerie
Notificări
Web Push (VAPID) + Resend

Mai întâi push, e-mailul ca rezervă

Alertele de comandă gata pleacă prin Web Push, printr-un service worker minimal; dacă nu se livrează niciun push, se trimite un e-mail când există o adresă. Chitanțele sunt trimise pe e-mail prin Resend.
Design
Interfață
Tailwind CSS v4 + Framer Motion + WebGL

Linii fine, etichete mono, alternative pentru mișcare redusă

Structura vine din linii de un pixel, nu din carduri sau umbre. Tokenii de mișcare sunt comuni, iar fiecare animație ambientală se degradează la o stare statică, utilizabilă, în regim de mișcare redusă.
// 04. Sub capotă

Construit pentru a menține corecte banii, stocul și permisiunile

Patru decizii care țin sistemul laolaltă

// 01 • Plăți și comenzi

Plata înainte ca comanda să existe

O comandă cu cardul este creată doar dintr-o sesiune Stripe deja plătită, astfel încât o comandă neplătită cu cardul nu ajunge niciodată în tabelul de comenzi.

  • Prețul se calculează în baza de date: Oferta dinaintea plății și inserarea de după plată folosesc același rezolvator de linii. Clientul trimite ID-uri de produse, cantități și nume de modificatori; fiecare preț este citit din menu_items.
  • Webhook și pagina de revenire: checkout.session.completed și redirecționarea de confirmare apelează același handler, idempotent pe stripe_session_id.
  • Rambursare la eșec: Dacă o sesiune plătită nu poate deveni comandă, este rambursată automat, cu o cheie de idempotență pe sesiune.
  • Semnătură verificată: Webhook-ul respinge orice cerere fără o semnătură Stripe validă.
  • Stripe
  • Webhook-uri
  • Idempotență
  • Postgres RPC
// 02 • Date și securitate

Postgres ca autoritate

Row Level Security și funcțiile security-definer pun regulile lângă date, astfel încât nicio cale din client să nu le poată ocoli.

  • Date de client delimitate: Politicile RLS limitează clienții la propriile coșuri, profiluri, favorite și comenzi.
  • Comenzi de invitat prin token: Invitații nu au nicio politică pe comenzi. Un token imposibil de ghicit deschide exact o comandă prin order_by_token(), care omite tokenul, ID-ul de utilizator și coloanele Stripe.
  • Instantanee: Numele produsului, prețul, cota de TVA și eligibilitatea pentru ștampilă sunt copiate pe fiecare linie de comandă, astfel încât modificările ulterioare ale meniului să nu rescrie istoria.
  • Solduri derivate: Soldul cardului cu ștampile este o interogare asupra istoricului comenzilor, nu un contor stocat.
  • Testat în SQL: Testele SQL exersează schema reală într-o tranzacție care este anulată la final, în timp ce node:test acoperă logica din partea aplicației.
  • Row Level Security
  • security definer
  • Supabase
  • node:test
// 03 • Personal și permisiuni

Roluri, PIN-uri și un jurnal de audit

Rolurile de proprietar, manager și personal sunt impuse de o singură funcție staff_can(), apelată de politicile RLS și de fiecare RPC de scriere.

  • O singură funcție de permisiuni: Oglinda în TypeScript există doar pentru a ascunde butoane; granița este baza de date.
  • Recitirea actorului: Fiecare RPC de scriere citește actorul din tabelul staff, astfel încât dezactivarea cuiva oprește următoarea sa scriere chiar dacă cookie-ul său este încă valid.
  • Blocare la PIN greșit: Cinci PIN-uri greșite blochează o persoană pentru 15 minute și scriu un eveniment staff.locked.
  • Revendicarea proprietății: Primul cont autentificat care apelează claim_owner() devine proprietar, iar ușa se închide în urma lui — fără nicio credențială predefinită.
  • Jurnal de audit: Acțiunile scriu într-un jurnal staff_events pe care managerii și proprietarii îl pot citi.
  • staff_can()
  • pgcrypto
  • Blocare
  • Jurnal de audit
// 04 • Operațiuni

O zi de lucru care se conduce singură

Un job zilnic și o deschidere sigură la concurență mențin magazinul în funcțiune fără niciun pas manual.

  • Cron zilnic: vercel.ts programează /api/cron/release-holds la 03:00 UTC, autentificat cu un token bearer CRON_SECRET.
  • Ce face: Eliberează comenzile online expirate, deschide ziua prin open_service (acționând ca cea mai veche persoană activă din personal, după data creării) și șterge abonamentele push mai vechi de 24 de ore.
  • Deschidere sigură la concurență: Deschiderea este o inserare pe cheia primară a zilei, astfel încât deschiderile concurente lasă o singură zi și nu resetează niciodată stocul dimineții.
  • Stoc serializat: Rândurile de meniu sunt blocate cu for update când stocul este scăzut, astfel încât doi cumpărători care vor ultimul produs stau la coadă.
  • Tichete numerotate: Numerele de tichet provin din rândul zilei, în aceeași funcție de creare a comenzii care scade stocul.
  • Vercel Cron
  • open_service
  • Blocări de rânduri
  • Zile de lucru
// 05. Provocări și obstacole

Obstacole de inginerie și soluții

Acolo unde banii reali, stocul real și personalul real au făcut greșită abordarea ușoară

// 01 • Concurență și bani

Două persoane, o ultimă chiflă

Obstacolul

O comandă online nu mai blochează stocul cât timp clientul se află la Stripe, așa că două persoane pot plăti pentru ultimul produs. Oferta refuză un produs deja epuizat, dar nu poate opri această cursă.

Rezolvarea

Funcția de creare a comenzii blochează rândurile de meniu cu for update înainte de a scădea stocul. Inserarea perdantului ridică o eroare, iar handlerul de plăți îi returnează banii. Reîncercările sunt sigure: comenzile sunt unice pe stripe_session_id, webhook-ul și pagina de revenire folosesc același handler, iar rambursarea poartă o cheie de idempotență pe sesiune.

Impact: O plată devine cel mult o comandă sau este rambursată. Compromisul, acceptat deliberat, este că perdantul plătește și este rambursat, în loc să fie blocat mai devreme.

  • Blocări de rânduri
  • Idempotență
  • Rambursare automată
  • Stripe
// 02 • Transport de date

Un coș care încape în metadatele Stripe

Obstacolul

Comanda este creată după plată de cod care vede doar sesiunea Checkout, așa că coșul trebuie să călătorească odată cu ea — iar valorile metadatelor Stripe sunt scurte.

Rezolvarea

packItems() serializează coșul în JSON și îl împarte în chei items_N de 500 de caractere, limitate la 45 pentru a lăsa loc numelui, notelor și ID-urilor. unpackItems() îl reasamblează, iar baza de date reconstruiește și reevaluează comanda pornind de la ID-urile produselor, în loc să se bazeze pe metadate.

Impact: Nu există niciun rând de comandă provizorie înainte de plată, deci un checkout abandonat nu lasă nimic în urmă. Comenzile sunt limitate la 50 de linii.

  • Metadate Stripe
  • Fragmentare
  • Server Actions
// 03 • Securitate și identitate

iPad-ul care nu se deconectează niciodată

Obstacolul

Dispozitivul de la bar rămâne autentificat toată ziua, așa că sesiunea lui nu poate spune cine a făcut ce — iar un iPad furat nu ar trebui să poată rambursa pe nimeni.

Rezolvarea

O stație păstrează sesiunea de lungă durată pentru ca tabloul să poată citi, dar o constrângere din baza de date interzice ca o stație să aibă PIN, deci nu se poate crea niciodată o sesiune de actor pentru ea. Oamenii deblochează scrierile cu un PIN din 4 cifre verificat în Postgres, iar fiecare RPC recitește actorul. Politicile pentru personal care interogau tabelul staff intrau în recursivitate, așa că un helper security-definer is_staff() rupe ciclul.

Impact: Scrierile sunt atribuite unei persoane, dezactivarea intră în vigoare la următoarea scriere, iar capacitatea de a vedea tabloul nu conferă niciodată capacitatea de a-l modifica.

  • PIN + pgcrypto
  • Blocare
  • security definer
  • RLS
// 06. Stack tehnologic

Cu ce este construit

Defalcare pe straturi a framework-urilor, serviciilor și uneltelor

Frontend și UI
  • Next.js 16
  • React 19
  • Tailwind CSS v4
  • shadcn/ui
  • Base UI
  • Framer Motion
Backend și API
  • Supabase Postgres
  • Stripe Checkout
  • Stripe Webhooks
Timp real și WSS
  • Supabase Realtime
Cozi în fundal
  • Vercel Cron
Stocare și media
  • ImageKit
Autentificare și securitate
  • Supabase Auth
  • Row Level Security
  • pgcrypto
Comunicații
  • Web Push (VAPID)
  • Resend
DevOps și implementare
  • Vercel
  • pnpm
// 07. Rulare locală și evaluare

Încercați live și reproduceți local

Vitrina de la kroma.lenardtunya.com se deschide singură în fiecare dimineață, așa că există mereu un meniu live din care puteți comanda. Plățile cu cardul rulează în modul de test Stripe — folosiți 4242 4242 4242 4242, orice dată de expirare viitoare și orice CVC.

Panoul personalului este real, exact de aceea PIN-ul său nu este publicat: este o credențială cu drept de scriere asupra unor date demo live. Pentru a-l vedea, rulați proiectul local — seed-ul vine cu propriul PIN de unică folosință — sau contactați-mă pentru o prezentare interactivă.

local-setup.sh
# 1. Clone and install
git clone https://github.com/JustTunya/kroma.git
cd kroma
pnpm install

# 2. Start local Postgres + Auth, seeded from supabase/seed.sql
supabase start

# 3. Configure environment variables (see README for each one)
cp .env.local.example .env.local

# 4. Start the app
pnpm dev
# Storefront: http://localhost:3000
# Dashboard:  http://localhost:3000/dashboard

# 5. Unlock the staff dashboard (local seed only)
#    a. Sign up at /auth/sign-up, confirm via the local Inbucket mail UI
#       (`supabase status` prints its URL, usually http://localhost:54324)
#    b. Link that account to the seeded staff row:
#       update staff set user_id = (select id from auth.users
#         where email = 'you@example.com') where display_name = 'Demo Owner';
#    c. Sign in, open /dashboard, enter the seeded PIN 1234, then open the shop

Informări privind evaluarea și detalii despre simulare

  • Cafenea simulată: KROMA este fictivă. Plățile cu cardul rulează în modul de test Stripe, fără taxări reale, iar textele despre prăjire și aprovizionare din vitrină sunt text de atmosferă, nu un lanț de aprovizionare real.
  • Web Push real, fără SMS: Alertele de comandă gata sunt notificări reale VAPID Web Push, cu e-mail ca rezervă. Nu există nicio cale prin SMS sau număr de telefon.
  • Licență PolyForm Noncommercial: Codul sursă este public pentru citire, studiu și utilizare personală sau educațională conform PolyForm Noncommercial 1.0.0. Utilizarea comercială — inclusiv rularea lui ca magazin real — necesită o licență separată.

Descoperiți KROMA în acțiune

Accesibil public pentru evaluare tehnică, revizuirea codului și analiza arhitecturii.