Ordini settimanali del Gruppo di Acquisto Solidale Porta Moneta
Translation pending
CommerceTypeScript · JavaScriptGitHub @federicodecilliaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
WeGrocery — open-source white-label platform for food co-ops and buying groups: weekly order cycles, shared ledger, supplier management. One codebase, brand-per-deploy. In production for a Rome co-op.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Open-source, white-label platform for food co-ops and buying groups.
Members place orders inside weekly cycles and track a prepaid ledger balance;
admins manage cycles, products, suppliers, top-ups, and analytics. One
codebase, MIT licensed — every group gets its own deployment with its own
name, logo, colors, and language, configured by a single environment variable.Read the full README ↗Open-source, white-label platform for food co-ops and buying groups. Members place orders inside weekly cycles and track a prepaid ledger balance; admins manage cycles, products, suppliers, top-ups, and analytics. One codebase, MIT licensed — every group gets its own deployment with its own name, logo, colors, and language, configured by a single environment variable.
| 🧪 Try the public demo | wegrocery-demo.vercel.app — one-click login, fake data |
| 🟢 In production | gas.portamoneta.org — a Milan residents' association, members only |
| 📋 Changelog | English · Italiano |
WeGrocery runs the weekly purchasing cycle of a buying group: members order within a time-boxed window, the system tracks each member's prepaid balance like a ledger, and an admin closes the cycle, charges everyone, splits shipping, and emails the order to the supplier.
It is not a toy demo: it has been in production since 2025 for Porta Moneta, a Milan residents' association running a food-buying group (GAS, Gruppo di Acquisto Solidale) that uses it every week — real money math, real concurrency control, real members. Porta Moneta is the first white-label deployment of this codebase.
It is also a complete reference implementation of a modern Next.js app: App Router, React Server Components, Server Actions, Better Auth, Drizzle ORM on serverless Postgres, deployed on Vercel.
Who it's for:
The app ships in English and Italian (one language per deployment, picked by the brand config); code, identifiers, comments, and docs are all in English.
wegrocery-demo.vercel.app is a fully interactive demo running on fake data:
Data is fake and reset automatically every night, so click around freely — nothing you do is permanent.
The production site (gas.portamoneta.org) is private: login is restricted to the co-op's members and holds real data. Use the demo to explore the app.
docs/self-hosting.md takes a group from nothing to a working installation in about 30 minutes, with a Deploy to Vercel button that creates the project and its database: no code to touch.
Every deployment is branded by one env var, NEXT_PUBLIC_BRAND_JSON. No env
var = the neutral WeGrocery brand you see in the demo. Example (every field:
docs/brand.example.json):
{
"appName": "Riverside Buying Club",
"shortName": "Riverside",
"locale": "en",
"currency": "EUR",
"logoUrl": "https://example.org/logo.png",
"supportEmail": "[email protected]",
"theme": { "primary": "#2f9e44", "accent": "#1971c2" }
}
Theme colours are hex (#rgb or #rrggbb). Give the fills only: the text
colour on buttons and the darker shade used for coloured text are computed so
that everything stays readable (WCAG AA). A palette that cannot reach that
contrast, or a non-hex value, logs a [brand] warning at startup.
Dates are shown and entered in Europe/Rome wall time. A group in another
zone sets NEXT_PUBLIC_TIME_ZONE (an IANA name, e.g. Europe/Lisbon); it is
inlined at build time, so redeploy after changing it.
Onboarding a new group = one Vercel project + one Neon database + one brand
JSON. Same main branch serves every deployment, so a bug fix or feature
lands everywhere with a single merge — no forks, no per-client branches.
The full operating model is documented in
docs/operating-two-environments.md.
members table (or with a valid membership card, where that check is set up)main)No charting library, no UI kit, no state-management library. The whole admin analytics dashboard is rendered with pure CSS and inline SVG.
├── app/ # Next.js App Router
│ ├── page.tsx # Home: balance hero, open cycles, recent ledger
│ ├── ordine/ # Order form (per-product +/- steppers)
│ ├── storico/ # Order history + ledger movements
│ ├── notifiche/ # In-app notifications
│ ├── guida/ # FAQ
│ └── admin/ # Admin panel with 7 tabs
├── components/
│ ├── app-shell.tsx # Layout wrapper (header, bottom nav on phones, top nav on desktop)
│ ├── home/ # Home-only components
│ ├── admin/ # One component per admin tab + shared modals
│ └── ui/ # Button, Card, Toast, ConfirmDialog
├── lib/
│ ├── db/
│ │ ├── schema.ts # Drizzle schema
│ │ ├── queries.ts # Read-only query helpers
│ │ └── client.ts # Neon connection
│ ├── actions/ # Server Actions (admin, order, notifications)
│ └── auth/session.ts # requireUserSession(), requireAdmin()
├── drizzle/ # Hand-written SQL migrations
├── auth.ts # Better Auth instance and auth()
├── proxy.ts # Auth gate (redirect to /login)
└── SETUP.md # Step-by-step local setup guide
UPDATE ... RETURNING
so two admins clicking "close" at the same time can never produce
duplicate ledger entries. If charge insertion fails mid-flow, the
status flip is rolled back so the cycle can be retried cleanly.supplier_products
table. When the price of a catalog item changes, the previous version is
archived (not overwritten) so historical orders still resolve correctly.Every page is a React Server Component that fetches its own data via
Drizzle queries. Mutations go through Next.js Server Actions with
requireUserSession() / requireAdmin() guards — the client never gets
direct DB access and never receives sensitive credentials.
| Table | Purpose |
|---|---|
members | Roster + role (admin / attivi / utenti) + email whitelist |
order_cycles | Time-boxed purchasing windows with shipping config |
products | Per-cycle catalog snapshot (so historical prices stay frozen) |
supplier_products | Reusable supplier catalog with price-change history |
orders | Line items: (memberId, cycleId, productId) → quantity |
ledger_entries | Append-only balance log: top-ups, order charges, shipping |
notifications | Per-member or per-role messages with read-at timestamp |
notification_preferences | Sparse per-member channel toggles (absent row = code default) |
audit_log | Append-only trace of admin actions |
suppliers | Supplier registry |
The HTTP driver (@neondatabase/serverless) doesn't support interactive
transactions, so all multi-statement workflows use a CAS pattern:
UPDATE ... WHERE status='X' RETURNING ... followed by per-row inserts,
with a manual rollback (status flip back) if a follow-up step fails.
The admin analytics tab draws four chart types — horizontal bar, area+line
trend, bar ranking, and a categorical split — using only Tailwind utilities
and inline SVG with a viewBox. Total bundle cost: zero.
All user-facing strings live in typed language packs (lib/i18n/it.ts /
en.ts — the English pack is type-checked against the Italian source, so a
missing translation fails the build). Branding (name, logo, colors, locale,
currency, support emails) comes from NEXT_PUBLIC_BRAND_JSON, validated
fail-fast at build time in lib/brand. Dates and money are formatted with
Intl using the deployment's locale and currency. The codebase itself
(identifiers, comments, commits, docs) is in English.
See SETUP.md for the full step-by-step setup (environment, database, a local admin, tests).
Quick start once .env.local is in place:
npm install
npm run db:migrate # apply the migrations in drizzle/ to the database
npm run dev # http://localhost:3000
Other scripts:
| Script | What it does |
|---|---|
npm run dev | Dev server with hot reload (Next.js) |
npm run build | Production build + type check |
npm run db:migrate | Apply pending migrations (drizzle/*.sql) to the database in .env.local |
npm run doctor | What this environment has set up, and what is missing |
npm test / npm run test:int | Unit tests / integration tests on a test database |
npm run db:studio | Drizzle Studio (visual DB browser) |
Pushing to main triggers a Vercel production deploy and staging gets a
preview deployment; other branches are not deployed (git.deploymentEnabled
in vercel.json). Migrations (drizzle/*.sql) are applied
before the code that needs them, with node scripts/db-migrate.mjs, or at
every production build with MIGRATE_ON_BUILD=true: see
docs/upgrading.md. What a deploy has set up shows in
admin → Impostazioni → Configuration status and in npm run doctor.
Contributions are welcome — this is a real product used weekly by real
groups, and improvements ship to every deployment. Start from
CONTRIBUTING.md: how to report a bug, propose a
feature, set up the project and open a pull request (against staging).
Security issues go through SECURITY.md, never a public issue;
everyone follows the Code of Conduct. If you run
WeGrocery for your own group, a star and a note about your setup help a lot.
Code is MIT licensed — fork it, adapt it, run it for your own
group. The "Porta Moneta" name, logo, and the portamoneta.org domain belong
to APS Porta Moneta and are not covered by the license; client brand
configurations are private to each deployment.
Run it yourself — it's free and MIT licensed: clone, deploy, set your brand JSON.
Or get it done for you. I set up turnkey, white-label instances — your logo, colors, language, and domain, like the Porta Moneta deployment — and build custom features on top. Get in touch: LinkedIn · GitHub
Built with Claude Code — architected and reviewed by a human, executed by an agent.