Aller au contenu
Retour aux réalisations sélectionnées
Démo en ligne · Stripe en mode test
Logo KROMA

KROMA

Un coffee shop avec commande anticipée conçu de bout en bout — une vitrine en direct avec paiement Stripe, et de vraies coulisses : tableau de cuisine, journée de service et chiffres.

Visuel principal de la vitrine KROMA

Un coffee shop de spécialité fictif conçu comme un système complet de commande anticipée, pas comme une maquette. Les clients parcourent un menu du jour en direct, personnalisent une boisson, paient via Stripe et suivent la commande ; l’équipe traite cette même commande sur un tableau de cuisine, mène une journée de service qui se clôt par un comptage de caisse, et consulte les chiffres du shop — tandis que les prix, le stock, l’état des paiements et les permissions sont décidés dans Postgres, sans jamais s’en remettre au navigateur.

// Couche de données

Logique d’abord dans Postgres

Les prix, le stock, les permissions et les rapports vivent dans des fonctions SQL protégées par Row Level Security.

// Paiements

Payer avant de commander

Une commande par carte n’est créée qu’une fois le paiement confirmé par Stripe ; les échecs sont remboursés.

// Côté équipe

Tableau de cuisine Realtime

Supabase Realtime sur les commandes, avec un repli par interrogation toutes les 30 secondes.

// Vérification

Testé à deux niveaux

node:test pour la logique applicative, plus des tests SQL exécutés dans une transaction annulée.

// aperçu

Ce qu’est KROMA

KROMA est un coffee shop de spécialité fictif, conçu comme une application web complète, pensée pour la production. Un client parcourt un menu du jour en direct, personnalise une boisson, paie par carte via Stripe ou au bar, et suit la commande ; l’équipe traite cette même commande sur un tableau de cuisine et gère le stock, le menu, les remboursements et le comptage de caisse de fin de journée depuis la même application. C’est un projet de portfolio — le shop est fictif et les paiements tournent en mode test Stripe — mais le flux qui se cache derrière est bien réel.

Chaque commande est tarifée, approvisionnée et contrôlée côté permissions dans Postgres, si bien que la vitrine, le tableau de cuisine et le rapport de la journée lisent le même enregistrement de commande.

// 01. Contexte stratégique

Pourquoi KROMA est conçu ainsi

01 // Problème

Un bouton de paiement ne fait pas une boutique

Les démos de commande anticipée s’arrêtent souvent à un bouton Commander : un menu statique et rien derrière. Ce qui sépare un système qui fonctionne d’une maquette, c’est tout ce que ces démos laissent de côté — un stock qui s’épuise en milieu de matinée, un état de paiement à vérifier côté serveur, et une seconde interface pour les personnes qui préparent réellement la commande.

Dynamique clé : Deux interfaces, une commande
02 // Solution

Un seul enregistrement de commande, deux interfaces

KROMA est conçu comme un système à deux faces. La page de commande du client et le tableau de cuisine de l’équipe lisent et font avancer le même enregistrement de commande. Des fonctions Postgres calculent le prix de chaque commande, décrémentent le stock et décident qui peut faire quoi ; l’application Next.js les appelle.

Dynamique clé : Les règles vivent à côté des données
03 // Résultat

Une boutique qui gère sa propre journée

Une tâche Vercel Cron quotidienne ouvre la journée de service, et l’équipe la clôt en comptant le tiroir-caisse par rapport à un rapport généré. La démo reste sans risque face à des inconnus : les paiements par carte tournent en mode test Stripe, et le PIN de l’équipe est un véritable identifiant d’accès en écriture, qui n’est pas publié.

Dynamique clé : Journée ouverte par cron, clôture comptée
// principe d’architecture
« Le navigateur propose ; Postgres décide. Le client envoie des identifiants d’articles, des quantités et des noms de modificateurs — chaque prix, chaque changement de stock et chaque contrôle de permission est résolu dans la base de données. »
— Principe d’architecture
// 02. Visite du produit

Voir KROMA en action

Une visite guidée de la vitrine côté client et de l’espace équipe — ce que fait chaque partie et ce que l’on ressent en l’utilisant

Côté client

Ce que rencontre un invité ou un membre, de la consultation du menu au retrait de la commande

01Vitrine

Vitrine à menu en direct

Un menu du jour en direct, présenté comme une liste éditoriale plutôt que comme une grille de cartes. Le stock et les prix viennent directement de Postgres, si bien qu’une fournée épuisée apparaît comme terminée en moins d’une minute.

Capture d’écran de la vitrine KROMA montrant la liste éditoriale du menu avec son panneau d’aperçu fixe
  • Menu du jour en direct: Les articles, les prix et le stock par article sont lus dans Postgres et revalidés toutes les 30 secondes. Si la requête ne renvoie rien, la vitrine bascule sur un instantané local menu.json.
  • Héros WebGL à carte de profondeur: Un fragment shader déplace la photo du héros le long d’une carte de profondeur en niveaux de gris au gré du curseur. Il se replie sur une image fixe sans WebGL, en cas d’échec du chargement ou avec le mouvement réduit.
  • Liste de menu éditoriale: Des lignes pleine largeur, séparées par des filets, avec un panneau d’aperçu fixe sur ordinateur qui change la photographie au survol ou au focus clavier.
  • Filtrage par catégorie: Une navigation en pastilles dont le remplissage actif glisse d’une catégorie à l’autre au lieu de sauter.
  • Personnalisation de l’article: Un panneau latéral coulissant pour les modificateurs et variantes, avec le prix recalculé au fil de vos choix.
  • États du stock: Un article épuisé affiche « Épuisé pour aujourd’hui » ; s’il en reste cinq ou moins, « Plus que N ».
Next.js 16SupabaseFramer MotionWebGLRevalidation 30 s
02Paiement et suivi

Commander, payer, suivre

Payez par carte via Stripe Checkout ou choisissez de payer au bar, puis suivez la commande sur une page partageable. Une commande par carte n’existe qu’une fois le paiement confirmé par Stripe.

  • Invité ou compte: On peut commander sans compte. Le panier d’un client connecté est stocké dans Postgres, et un panier d’invité y est fusionné à la connexion.
  • Stripe Checkout: Les paiements par carte tournent en mode test Stripe. La session est tarifée à partir d’un devis côté serveur et expire au bout de 30 minutes.
  • Paiement au bar: Une commande au comptoir est enregistrée comme en attente ; le barista note espèces ou carte lorsqu’il la marque comme payée.
  • Contrôle alimentaire: Les régimes et allergènes enregistrés signalent dans le panier les articles en conflit, avant le paiement.
  • Suivi de commande: Une page partageable /order/[token] montre la commande passer de payée à en préparation puis à prête, avec un rafraîchissement toutes les 15 secondes. Avant qu’elle n’arrive au bar, le client peut l’annuler, et un paiement en ligne est remboursé.
  • Alerte de commande prête: Une notification Web Push est envoyée lorsque la commande est prête, avec un e-mail en repli lorsque le push n’est pas disponible.
  • Reçus: Une page de reçu imprimable avec code-barres et ligne de TVA, plus un reçu envoyé par e-mail via Resend lorsqu’une adresse est disponible.
Stripe CheckoutStripe WebhooksWeb Push (VAPID)ResendServer Actions
03Comptes

Compte et carte de fidélité

La connexion par e-mail ou Google débloque une carte de fidélité à tampons, un « comme d’habitude » en un geste, l’historique des commandes et les préférences alimentaires enregistrées.

  • Connexion: E-mail et mot de passe avec e-mail de confirmation et réinitialisation du mot de passe, ou Google OAuth.
  • Carte à tampons: Seules les catégories de boissons rapportent un tampon. Un nouveau compte démarre avec 2 tampons et une carte pleine en compte 10. Le solde est dérivé de l’historique des commandes plutôt que stocké, de sorte qu’une commande annulée ou remboursée emporte ses tampons avec elle.
  • Article offert: Une carte pleine s’échange au paiement contre une unité d’un article.
  • Comme d’habitude: L’article que le client a le plus commandé, avec les modificateurs de sa dernière commande, prêt à être recommandé.
  • Préférences: Un nom d’affichage, plus un nom de bar distinct annoncé au passe, des régimes (végan, végétarien, pescétarien, sans gluten) et des allergènes à éviter.
  • Historique des commandes: Les commandes passées, avec un bouton pour recommander.
Supabase AuthGoogle OAuthRow Level SecuritySolde dérivé

Côté équipe

Les coulisses sur /dashboard, derrière un vrai compte et un PIN à 4 chiffres

PIN à 4 chiffres

Connexion de l’équipe en deux couches

L’iPad derrière le bar garde une session de longue durée pour que le tableau puisse s’afficher ; une personne déverrouille le droit d’écrire avec un PIN à 4 chiffres. Le PIN n’est vérifié que dans Postgres avec pgcrypto, cinq saisies erronées bloquent cette personne pendant 15 minutes, et chaque déverrouillage est journalisé.

Capture d’écran du pavé PIN de l’équipe KROMA sur la surface sombre de l’affichage cuisine
04Affichage cuisine

Tableau des commandes

Un tableau sombre et très contrasté, conçu pour la ligne de service. Les commandes progressent dans quatre colonnes — Au passe, En préparation, Prêt au bar, Retiré — et la page du client suit le mouvement.

Capture d’écran du tableau de cuisine KROMA montrant les commandes réparties dans les quatre colonnes avec leurs indicateurs d’ancienneté
  • File d’attente en direct: Supabase Realtime sur la table des commandes recharge le tableau. Une pastille de connexion indique en direct, reconnexion ou hors ligne, une interrogation toutes les 30 secondes comble les trous, et les écritures sont désactivées hors ligne.
  • Indicateur d’ancienneté: Chaque commande affiche un minuteur en cours et passe de fraîche à tiède au bout de 5 minutes, puis à en retard au bout de 10.
  • Carillon de nouvelle commande: Un son retentit lorsqu’une nouvelle commande apparaît sur le tableau, une fois l’appareil activé d’une simple pression.
  • Avancer et annuler: Une pression fait avancer une commande. La reculer d’une colonne est ouvert à tous dans les 90 secondes ; ensuite, seuls un manager ou un propriétaire le peuvent.
  • Notes, annulations et remboursements: Toute personne en service peut ajouter une note. Les managers et propriétaires peuvent annuler ou rembourser, et le remboursement Stripe d’une commande en ligne est émis depuis l’application.
  • Remises et offerts: Les managers et propriétaires peuvent appliquer un pourcentage, un montant fixe ou une offre complète, avec un motif obligatoire.
  • Installable: Le tableau s’installe comme une PWA plein écran en mode paysage.
Supabase RealtimeRPC PostgresPWAFramer Motion
05Journée de service

Ouvrir, vendre, clôturer

La vente est organisée en journées. Quelqu’un ouvre la boutique avec les comptages du stock de référence, une boutique fermée refuse les commandes, et un manager ou un propriétaire la clôt en comptant le tiroir-caisse par rapport à un rapport généré.

  • Ouverture du service: L’ouverture remet le stock de chaque article à son niveau de référence, ou aux quantités saisies sur l’écran d’ouverture. N’importe quel rôle peut ouvrir, et deux appareils qui appuient sur Ouvrir produisent une seule ouverture.
  • Boutique fermée: Tant qu’aucune journée n’est ouverte, passer une commande déclenche « La boulangerie est fermée. »
  • Numéros de ticket: Chaque journée numérote ses tickets à partir de #001.
  • Mode de paiement: Au comptoir, le barista note si une commande a été payée en espèces ou par carte.
  • Comptage de caisse: L’écran de clôture prend un comptage par valeur de billet ou de pièce et affiche l’écart par rapport aux espèces attendues : fonds de caisse plus encaissements en espèces moins remboursements en espèces.
  • Garde-fou de clôture: Une journée ne peut pas être clôturée tant qu’il reste des commandes au passe ; l’erreur indique leurs numéros de ticket.
  • Rapport de la journée: Commandes, encaissements, net et TVA ; totaux espèces, carte et en ligne ; montants remisés, annulés, remboursés et jetés ; et ce qu’il reste en stock.
  • TVA: Les prix restent TTC. La TVA est extraite par catégorie (11 % sur tout ce qui se vend aujourd’hui) et figée en instantané sur chaque ligne de commande.
RPC PostgresJournées de serviceTVAVercel Cron
06Analytique

Chiffres et registre

Une vue de la boutique réservée aux managers et propriétaires, agrégée dans Postgres plutôt que dans le navigateur, si bien que le contrôle des permissions se trouve là où l’argent est additionné.

  • Revenus: Les encaissements sur une fenêtre de dates choisie, filtrables par catégorie.
  • Derrière le bar: Par personne : commandes démarrées, annulées et remboursées, avec des durées tirées des mêmes lignes de commande.
  • Registre: La piste d’audit, filtrée et paginée : déverrouillages, déplacements de commandes, remises, limites de service, ouverture et clôture, et modifications du menu.
  • Garde des permissions: Chaque agrégat relit le rôle de l’appelant dans la base de données et refuse toute personne en dessous du niveau manager.
RPC PostgresJournal d’auditContrôle par rôle
Gestion du menu

Modifier le menu en direct

Les managers et propriétaires modifient le menu depuis le tableau de bord : réordonner les articles, ouvrir la fiche d’un article avec son éditeur de modificateurs, gérer les catégories et supprimer des articles. Chaque écriture passe par une fonction à permissions vérifiées, et les commandes passées conservent le nom et le prix de leurs articles.

// 03. Architecture et topologie

Architecture full-stack serverless

Une application Next.js sur Vercel devant un projet Supabase, avec Stripe, cron et push reliés par des routes serveur

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           │
                            └──────────────────────┘

Matrice des décisions techniques et de leurs justifications

Où se loge chaque responsabilité, et pourquoi à cet endroit

Couche et périmètreChoix technologiqueCapacités clés et justification
Frontend
Framework applicatif
Next.js 16 (App Router) + React 19

Composants serveur, actions serveur, une seule frontière client

La vitrine est un composant serveur qui récupère le menu et le revalide toutes les 30 secondes ; une seule frontière client porte l’état des filtres et du panier. Les mutations sont des actions serveur, et les route handlers couvrent le webhook Stripe, la tâche cron et les callbacks d’authentification.
Persistance
Base de données et logique
Supabase Postgres

Règles métier sous forme de RPC security-definer, RLS sur les données clients

La tarification, le stock, les numéros de ticket, les permissions, les remboursements et les rapports vivent dans des fonctions SQL gérées comme des migrations versionnées, si bien que la vitrine, le tableau et la tâche cron passent tous par les mêmes règles.
Auth
Identité
Supabase Auth + table staff

E-mail/Google pour les clients ; session de poste plus PIN pour l’équipe

Les clients se connectent avec Supabase Auth. L’équipe travaille via un poste capable de lire, tandis qu’un PIN individuel déverrouille les écritures, de sorte que chaque modification est attribuée à quelqu’un.
Commerce
Paiements
Stripe Checkout + webhooks

Payer d’abord, la commande existe ensuite

La Checkout Session est tarifée à partir d’un devis stocké en base. La commande n’est créée qu’après le paiement, depuis le webhook ou la page de retour, et remboursée si elle ne peut pas être passée.
Temps réel
Mises à jour en direct
Supabase Realtime

S’abonner léger, récupérer en entier

Le tableau de cuisine écoute les changements de la table des commandes et recharge via une seule RPC, avec une interrogation toutes les 30 secondes en repli. Les pages de commande des clients interrogent toutes les 15 secondes.
Exploitation
Tâches planifiées
Vercel Cron

Une route quotidienne authentifiée à 03:00 UTC

La tâche libère les commandes en ligne expirées, ouvre la journée de service via la même RPC qu’utilise un barista, et supprime les abonnements push périmés.
Messagerie
Notifications
Web Push (VAPID) + Resend

Push d’abord, e-mail en repli

Les alertes de commande prête partent en Web Push via un service worker minimal ; si aucun push n’est délivré, un e-mail est envoyé lorsqu’une adresse existe. Les reçus sont envoyés par e-mail via Resend.
Design
Interface
Tailwind CSS v4 + Framer Motion + WebGL

Filets, étiquettes mono, replis pour le mouvement réduit

La structure vient de traits d’un pixel plutôt que de cartes ou d’ombres. Les tokens de mouvement sont partagés, et chaque animation d’ambiance se dégrade en un état fixe et utilisable lorsque le mouvement est réduit.
// 04. Sous le capot

Conçu pour garder l’argent, le stock et les permissions justes

Quatre décisions qui tiennent le système ensemble

// 01 • Paiements et commandes

Payer avant que la commande n’existe

Une commande par carte n’est créée qu’à partir d’une session Stripe déjà payée, si bien qu’une commande par carte impayée n’atteint jamais la table des commandes.

  • Tarifé dans la base de données: Le devis avant paiement et l’insertion après paiement partagent un même résolveur de lignes. Le client envoie des identifiants d’articles, des quantités et des noms de modificateurs ; chaque prix est lu dans menu_items.
  • Webhook et page de retour: checkout.session.completed et la redirection de confirmation appellent tous deux un même gestionnaire, idempotent sur stripe_session_id.
  • Remboursement en cas d’échec: Si une session payée ne peut pas devenir une commande, elle est remboursée automatiquement avec une clé d’idempotence par session.
  • Signature vérifiée: Le webhook rejette toute requête dépourvue de signature Stripe valide.
  • Stripe
  • Webhooks
  • Idempotence
  • Postgres RPC
// 02 • Données et sécurité

Postgres comme autorité

Row Level Security et les fonctions security-definer placent les règles à côté des données, si bien qu’aucun chemin côté client ne peut les contourner.

  • Données clients cloisonnées: Les politiques RLS limitent les clients à leurs propres paniers, profils, favoris et commandes.
  • Commandes invité par jeton: Les invités n’ont aucune politique sur les commandes. Un jeton impossible à deviner donne accès à exactement une commande via order_by_token(), qui omet le jeton, l’identifiant utilisateur et les colonnes Stripe.
  • Instantanés: Le nom de l’article, le prix, le taux de TVA et l’éligibilité au tampon sont copiés sur chaque ligne de commande, de sorte que les modifications ultérieures du menu ne réécrivent pas l’historique.
  • Soldes dérivés: Le solde de la carte à tampons est une requête sur l’historique des commandes, pas un compteur stocké.
  • Testé en SQL: Les tests SQL exercent le vrai schéma dans une transaction annulée, tandis que node:test couvre la logique côté application.
  • Row Level Security
  • security definer
  • Supabase
  • node:test
// 03 • Équipe et permissions

Rôles, PIN et piste d’audit

Les rôles de propriétaire, de manager et d’équipe sont appliqués par une unique fonction staff_can() que les politiques RLS et chaque RPC d’écriture appellent.

  • Une seule fonction de permissions: Le miroir TypeScript n’existe que pour masquer des boutons ; la frontière, c’est la base de données.
  • Relecture de l’acteur: Chaque RPC d’écriture lit l’acteur dans la table staff, de sorte que désactiver quelqu’un stoppe sa prochaine écriture même si son cookie est encore valide.
  • Blocage par PIN: Cinq PIN erronés bloquent une personne pendant 15 minutes et écrivent un événement staff.locked.
  • Prise de propriété: Le premier compte connecté à appeler claim_owner() devient propriétaire et la porte se referme derrière lui — sans identifiant préinstallé.
  • Journal d’audit: Les actions écrivent dans un journal staff_events que les managers et propriétaires peuvent lire.
  • staff_can()
  • pgcrypto
  • Blocage
  • Journal d’audit
// 04 • Exploitation

Une journée de service qui se gère seule

Une tâche quotidienne et une ouverture à l’abri des accès concurrents font tourner la boutique sans aucune étape manuelle.

  • Cron quotidien: vercel.ts planifie /api/cron/release-holds à 03:00 UTC, authentifié par un jeton bearer CRON_SECRET.
  • Ce qu’il fait: Il libère les commandes en ligne expirées, ouvre la journée via open_service (en agissant comme la personne active de l’équipe créée le plus tôt) et supprime les abonnements push de plus de 24 heures.
  • Ouverture à l’abri des accès concurrents: L’ouverture est une insertion sur la clé primaire de la journée, si bien que des ouvertures simultanées ne laissent qu’une seule journée et ne réinitialisent jamais le stock du matin.
  • Stock sérialisé: Les lignes du menu sont verrouillées avec for update lors de la décrémentation du stock, de sorte que deux acheteurs qui visent le dernier article font la queue.
  • Tickets numérotés: Les numéros de ticket proviennent de la ligne de la journée, dans la même fonction de création de commande qui décrémente le stock.
  • Vercel Cron
  • open_service
  • Verrous de lignes
  • Journées de service
// 05. Défis et obstacles

Obstacles d’ingénierie et solutions

Là où l’argent réel, le stock réel et l’équipe réelle ont rendu l’approche facile erronée

// 01 • Concurrence et argent

Deux personnes, un dernier petit pain

L’obstacle

Une commande en ligne ne réserve plus le stock pendant que le client est chez Stripe, de sorte que deux personnes peuvent payer le dernier article. Le devis refuse un article déjà épuisé, mais il ne peut pas empêcher cette course.

La résolution

La fonction de création de commande verrouille les lignes du menu avec for update avant de décrémenter le stock. L’insertion du perdant lève une erreur et le gestionnaire de paiement le rembourse. Les nouvelles tentatives sont sûres : les commandes sont uniques par stripe_session_id, le webhook et la page de retour partagent un même gestionnaire, et le remboursement porte une clé d’idempotence par session.

Impact : Un paiement devient au plus une commande, ou est remboursé. Le compromis, accepté délibérément, est que le perdant paie puis est remboursé plutôt que d’être bloqué plus tôt.

  • Verrous de lignes
  • Idempotence
  • Remboursement auto
  • Stripe
// 02 • Transport de données

Un panier qui tient dans les métadonnées Stripe

L’obstacle

La commande est créée après le paiement par du code qui ne voit que la Checkout Session, le panier doit donc voyager avec elle — et les valeurs de métadonnées Stripe sont courtes.

La résolution

packItems() sérialise le panier en JSON et le découpe en clés items_N de 500 caractères, plafonnées à 45 pour laisser de la place au nom, aux notes et aux identifiants. unpackItems() le réassemble, et la base de données reconstruit et retarife la commande à partir des identifiants d’articles plutôt que de se fier aux métadonnées.

Impact : Aucune ligne de commande provisoire n’existe avant le paiement, de sorte qu’un paiement abandonné ne laisse rien derrière lui. Les commandes sont plafonnées à 50 lignes.

  • Métadonnées Stripe
  • Découpage
  • Server Actions
// 03 • Sécurité et identité

L’iPad qui ne se déconnecte jamais

L’obstacle

L’appareil derrière le bar reste connecté toute la journée, sa session ne peut donc pas dire qui a fait quoi — et un iPad volé ne devrait pouvoir rembourser personne.

La résolution

Un poste détient la session de longue durée pour que le tableau puisse lire, mais une contrainte de base de données interdit à un poste d’avoir un PIN, de sorte qu’aucune session d’acteur ne peut jamais être créée pour lui. Les personnes déverrouillent les écritures avec un PIN à 4 chiffres vérifié dans Postgres, et chaque RPC relit l’acteur. Les politiques d’équipe qui interrogeaient la table staff partaient en récursion, c’est donc un assistant security-definer is_staff() qui brise le cycle.

Impact : Les écritures sont attribuées à une personne, la désactivation prend effet à la prochaine écriture, et pouvoir voir le tableau ne confère jamais le pouvoir de le modifier.

  • PIN + pgcrypto
  • Blocage
  • security definer
  • RLS
// 06. Stack technologique

Avec quoi il est construit

Détail couche par couche des frameworks, services et outils

Frontend et UI
  • Next.js 16
  • React 19
  • Tailwind CSS v4
  • shadcn/ui
  • Base UI
  • Framer Motion
Backend et API
  • Supabase Postgres
  • Stripe Checkout
  • Stripe Webhooks
Temps réel et WSS
  • Supabase Realtime
Files d’arrière-plan
  • Vercel Cron
Stockage et médias
  • ImageKit
Auth et sécurité
  • Supabase Auth
  • Row Level Security
  • pgcrypto
Communications
  • Web Push (VAPID)
  • Resend
DevOps et déploiement
  • Vercel
  • pnpm
// 07. Exécution locale et évaluation

Essayer en ligne et reproduire en local

La vitrine sur kroma.lenardtunya.com s’ouvre toute seule chaque matin, il y a donc toujours un menu en direct où commander. Les paiements par carte tournent en mode test Stripe — utilisez 4242 4242 4242 4242, n’importe quelle date d’expiration future et n’importe quel CVC.

Le tableau de bord de l’équipe est réel, et c’est précisément pour cela que son PIN n’est pas publié : c’est un identifiant d’accès en écriture sur des données de démo en direct. Pour le voir, exécutez le projet en local — le seed fournit son propre PIN jetable — ou contactez-moi pour une démonstration interactive.

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

Mentions d’évaluation et détails de la simulation

  • Boutique simulée : KROMA est fictif. Les paiements par carte tournent en mode test Stripe, sans aucun débit réel, et les textes sur la torréfaction et l’approvisionnement de la vitrine sont de l’ambiance, pas une vraie chaîne d’approvisionnement.
  • Vrai Web Push, pas de SMS : Les alertes de commande prête sont de véritables notifications VAPID Web Push, avec un e-mail en repli. Il n’existe aucun chemin par SMS ou numéro de téléphone.
  • Licence PolyForm Noncommercial : Le code source est public pour la lecture, l’étude et un usage personnel ou pédagogique sous PolyForm Noncommercial 1.0.0. Tout usage commercial — y compris l’exploiter comme une véritable boutique — nécessite une licence distincte.

Découvrez KROMA en action

Accessible publiquement pour l’évaluation technique, la revue de code et l’examen de l’architecture.