skip to content
sys.name
vvhybe_os 26.10
status
200 building papyrus
user
vvhybe (yassine bouba)

cat jnaninyoga-command-center.pwa

05 / 19

Jnanin Yoga Command Center

The studio's back office: schedule, check-ins, a credits wallet per student, health alerts, B2B invoicing.

role
sole developer
year
2026
access
-rwxr-xr-x (open source)

TL;DR

problem
One instructor runs the whole studio: group classes, private sessions, outdoor retreats, and hotels and companies that book groups. She needed one place for all of it, on her phone.
approach
Google stays the source of truth for time and people: every session is a Calendar event, every client a Google contact. The app keeps only what Google can't: credit wallets, the attendance ledger, health logs, and the B2B paperwork.
outcome
One installable app for the week, check-in with health guardrails, prepaid credits per student, and quote-to-invoice billing that prints French invoices, from the quote to the last payment.
Jnanin Yoga Command Center, screenshot

// overview

An installable, mobile-first admin app for a solo yoga instructor. Students buy credits, not dates: each check-in spends one and a missed class costs nothing. Before anyone is checked in, an active health condition raises an alert, from asthma to a due date.

Google Calendar stays the source of truth for group classes and private sessions, and every new client lands in Google Contacts, so they show up in WhatsApp too. Outdoor events keep track of their participants and guests.

A B2B side serves partner companies: quotes turn into invoices, partially if need be with a backorder for the rest, payments are tracked, and every document prints to PDF.

// the door

There is one user, the instructor, and she signs in with Google. The same consent lets the app read and write her Calendar and her Contacts, the two things everything else is built on.

Google doesn't always hand the refresh token back through the auth provider, so the app keeps its own copy, refreshes it five minutes before it expires, and only sends her back to sign in when Google says no. It installs from the browser like any app, and says so when the connection drops and again when it's back.

fig. 01sign inThe only way in. Signing in also grants Calendar and Contacts.
Jnanin Yoga Command Center: sign in
fig. 01 sign inThe only way in. Signing in also grants Calendar and Contacts.
fig. 1 / 1

// the week

Sessions live in her Google Calendar, not in a database. Each event carries its type in private properties Google keeps for the app: group, private, outdoor or B2B, with a price where it matters, and each type gets a colour of its own in the calendar.

Creating a session checks three things before it reaches Google: it isn't in the past, a studio class fits the opening hours set for that weekday, and Google's FreeBusy finds no clash. Outdoor and B2B sessions happen elsewhere, so they skip the opening hours.

01 / 03
fig. 02todayToday opens first. A class goes LIVE fifteen minutes before it starts, and its card becomes the way to check people in. The B2B session below is priced from a tier: groups of 11 to 15.
fig. 03an outdoor retreatAn outdoor day carries its price. Its guests come from the calendar invitation, matched to clients by email; nothing is checked in on site, so the card opens the event instead.
fig. 04a private sessionA private session: one student, one check-in.
Jnanin Yoga Command Center: today
fig. 02 todayToday opens first. A class goes LIVE fifteen minutes before it starts, and its card becomes the way to check people in. The B2B session below is priced from a tier: groups of 11 to 15.
fig. 1 / 3

// credits, not dates

A card of five or ten classes, or a month's pass, fills a wallet, and each check-in spends one credit. The price follows the student's category: adults, students and children each have their own discount.

The health log speaks up at the door: anything marked warning or critical raises an alert she acknowledges before the check-in goes through. The same step refuses to check one person in twice for the same session.

New students come in through a four-step intake (details, health, membership, consultation), kept as a draft while she types. Then they land in Google Contacts under a label of their own, so they're in her phone and on WhatsApp the moment they sign up.

fig. 05studentsEach card shows what's left in the wallet, the category discount, a health badge, and one tap to email, call or WhatsApp.
Jnanin Yoga Command Center: students
fig. 05 studentsEach card shows what's left in the wallet, the category discount, a health badge, and one tap to email, call or WhatsApp.
fig. 1 / 1

// partners

The B2B side serves hotels, riads and companies that book group sessions. Each partner keeps its ICE, the Moroccan company identifier an invoice can't go out without, its contacts, synced to Google Contacts under their own label, and every document exchanged with it.

01 / 02
fig. 06partnersContacts, documents and the ICE at a glance, one click to copy.
fig. 07one partnerA partner's history: a quote accepted, then billed in two invoices.
Jnanin Yoga Command Center: partners
fig. 06 partnersContacts, documents and the ICE at a glance, one click to copy.
fig. 1 / 2

// quotes, invoices, backorders

A quote turns into an invoice line by line. When only part of it is billed now, confirming the invoice offers a backorder: a new draft for what's left, the way Odoo does it. Each invoice line remembers the quote line it came from, so nothing is billed twice.

Numbers like INV-2026-0013 come from a counter per type and year, locked in the same transaction as the insert, so two invoices never share one. Totals are worked out again on the server; whatever the browser sends is ignored. Once issued, a document can't be edited or deleted, only archived with a reason.

01 / 02
fig. 08documentsEvery quote and invoice in one table: status, backorder, paid so far, still due.
fig. 09an invoicePartly paid. The strip under the header is its backorder chain: invoice 1 of 2, and what the whole quote still owes.
Jnanin Yoga Command Center: documents
fig. 08 documentsEvery quote and invoice in one table: status, backorder, paid so far, still due.
fig. 1 / 2

// payments

A payment is spread over the open invoices of the same chain, oldest number first, and each one turns partially paid or paid as it fills. Paying more than is owed is refused, with the balance in the message.

Every payment dialog carries an id of its own, and the database accepts each id once, so a double tap on a slow connection records one payment, not two.

fig. 10payment historyTwo payments against one invoice, with 1,000 MAD still due.
Jnanin Yoga Command Center: payment history
fig. 10 payment historyTwo payments against one invoice, with 1,000 MAD still due.
fig. 1 / 1

// the paper

Quotes and invoices print to PDF in the browser, nothing to run on a server: French labels, TOTAL HT and TTC, the studio's logo, signature and bank details from its profile. An invoice drawn from a quote says so, "Reliquat du devis", and the amount is written out in words by a small French number speller written for it.

fig. 11the printed invoiceWhat the partner receives. Test data; the footer's contact lines are cut from this copy.
Jnanin Yoga Command Center: the printed invoice
fig. 11 the printed invoiceWhat the partner receives. Test data; the footer's contact lines are cut from this copy.
fig. 1 / 1

// stack

  • next.js
  • typescript
  • supabase
  • drizzle
  • shadcn/ui
  • tanstack table
  • google apis
  • pwa

// links