Ayurvedic clinic · online dispensary
Sri Ayurvedic Vaidya Mitra
An Ayurvedic clinic and online dispensary platform — catalogue, consultation booking, manual UPI payments and an admin back office — built as a single Next.js application.
- Client
- Sri Ayurvedic Vaidya Mitra, Srirangam, Trichy
- Status
- Deployed

- 369
- Products live
- Migrated from WordPress
- 573
- Product images
- Moved into storage
- 22,497
- Lines of code
- Single application
- 47
- API endpoints
- Route handlers
- 9
- SQL migrations
- 19 tables, RLS on all
- 36
- Automated checks
- Against real Postgres
Overview
Two jobs, one system.
Sri Ayurvedic Vaidya Mitra is a registered Ayurvedic clinic and dispensary in Srirangam, Trichy, founded by Mrs. T. Mari Sri, with consultations given by Dr. R. Balamurgan, BAMS. The practice needed one system to do two jobs that are usually separate: sell classical Ayurvedic medicines online, and take bookings for clinical consultation.
The result is a single Next.js application backed by Supabase for database, file storage and authentication, with Resend for transactional email. It carries a 369-product catalogue imported from the practice's previous WordPress site, four consultation offerings, a manual UPI payment flow with admin verification, a gated content library, and an admin back office covering every one of those.
Payments without a gateway

Scope
What was delivered.
Storefront
DeliveredCatalogue with search, faceted filtering, category, brand and concern browsing; product pages; cart and wishlist that survive sign-in; three-layer header with mega menus.
Accounts
DeliveredSign-up with name, mobile, email and password, verified by emailed one-time code. Sign-in by password or code, password recovery, and a customer account area.
Orders & payment
DeliveredServer-priced checkout, UPI QR generation, transaction-reference submission with optional screenshot, admin verification, stock reservation and status tracking.
Consultations
DeliveredFour offerings, booking with a seven-day lead time, payment before confirmation, and admin verification that confirms the slot in one transaction.
Free library
DeliveredArticles, books and videos with type filtering. Metadata is public; the files themselves open only for signed-in readers.
Admin back office
DeliveredProducts, taxonomy, orders, bookings, articles, users and settings, plus a bulk CSV/XLSX importer that migrates external product images into storage.


Orders & payment
Pay by UPI, verified by hand.
Checkout prices every line on the server, generates a UPI QR for the shop's own VPA, and takes the transaction reference back from the customer with an optional screenshot. Nothing is reserved until an administrator matches that reference against the bank statement.

The live UPI ID and QR are masked in this screenshot.
Consultations
Booking, with the clinic's rules enforced.
Four consultation offerings, a seven-day booking lead time, and payment before confirmation. The lead time lives in the database rather than the form, because the booking endpoint is reachable without the interface.


Admin back office
Where the shop is actually run.
One dashboard covers everything that needs attention: products, taxonomy, orders, bookings, articles, users and settings. Stock alerts, pending UPI verifications and unconfirmed consultations surface on the landing screen rather than waiting to be found.


Technology
Every choice, and why.
| Layer | Choice | Version | Why |
|---|---|---|---|
| Framework | Next.js (App Router) | 16.3.2 | Server components keep database access off the client entirely |
| UI | React | 19.2.8 | Server and client components in one tree |
| Language | TypeScript | 5 | Row types flow from database to component unchanged |
| Styling | Tailwind CSS | 4 | Design tokens declared in CSS, no config file to drift |
| Database | Supabase Postgres | 2.112.3 | Row Level Security enforced at the database, not the app |
| Validation | Zod | 4.4.3 | One schema shared by the browser form and the server handler |
| Resend | 6.22.0 | Verified sending domain; Supabase's own mailer is testing-grade | |
| Forms | React Hook Form | 7.86.0 | Uncontrolled inputs, validated by the shared Zod schema |
| Client state | Zustand | 5.0.15 | Guest cart and wishlist persisted before sign-in |
| Images | sharp | 0.35.3 | Icon, badge and social-card generation at build time |
Architecture
No component touches the database.
Pages and route handlers call a service, and services map snake_case database rows onto the domain shapes components render. That boundary is what allowed the entire backend to be moved from MongoDB to Supabase mid-project without editing a single component.
Three database clients
Supabase is reached through three separate clients, and choosing the wrong one fails quietly rather than loudly — which is precisely why they are kept distinct.
| Client | Credentials | Used for | Security |
|---|---|---|---|
| Public | anon key, no cookies | Catalogue reads on cached pages | Enforced as anonymous |
| Request | anon key + session | Cart, orders, bookings, admin reads | Enforced as that user |
| Service | service role | Storage writes, imports, auth admin | Bypasses RLS — server only |
Why the public client exists
Data model
Nineteen tables, all with RLS.
Nineteen tables across nine migrations, every one with Row Level Security enabled. Access rules live in the database, so a query issued from anywhere — the application, a script, a leaked key — is subject to the same policy.
| Domain | Tables | Live rows |
|---|---|---|
| Catalogue | products, product_images, categories, brands, tags, product_tags | 1,232 |
| Commerce | orders, order_items, order_status_history, order_notes, cart_items, wishlist_items | 12 |
| Clinic | consultations, consultation_bookings, booking_notes | 7 |
| Content | articles, settings | 1 |
| Identity | profiles | 4 |
Business logic in SQL
Sixteen database functions hold the rules that must not be influenced by a browser. The client sends product identifiers and quantities and nothing else — no prices, no totals.
create_orderPrices every line from the products table, applies the shipping rule from settings, and writes the order in one transaction.
verify_paymentReserves stock with a guarded update per line; any shortfall rolls the whole verification back. It is idempotent, so a double click cannot double-decrement.
create_bookingEnforces the seven-day consultation lead time server-side, because the booking endpoint is reachable without the interface.
verify_booking_paymentConfirms payment and the appointment slot together, and refuses any caller who is not an administrator.
product_facetsReturns all filter counts in one round trip, since PostgREST cannot express GROUP BY.
Security
Hiding an interface is never the control.
- Private files are genuinely private. Payment screenshots and library PDFs live in buckets with no public read policy; access is a short-lived signed URL minted only after the session has been checked.
- Uploads are identified by their bytes, not their declared type. A file renamed to .jpg is rejected because the signature is read directly.
- Rate limiting on sign-in, sign-up, password recovery and the contact form, per address and per source, so the shared email quota cannot be drained.
- The service-role key is server-only and the module carrying it is marked so it cannot be imported into browser code even by accident.
Verification
Types and lint meant very little here.
Type-checking and linting pass, but on this project they proved to mean very little on their own: every defect that mattered — storage permissions, a missing join modifier, cookies in a cached page, an unconfigured image host — passed both cleanly and appeared only against the real service.
Two habits followed from that. Database logic is exercised by npm run db:verify, which applies all nine migrations to an in-process Postgres and runs 36 checks over pricing, stock reservation, idempotency, authorisation and booking rules — it has caught real defects before deployment. Everything else is verified by exercising the actual endpoint against the live project and confirming the row or object exists, including a full customer journey from sign-up through payment.
Discoverability
Structured for local search.
The highest-intent queries for this practice are geographic. Pages carry MedicalClinic and Physician structured data with address, opening hours and credentials, which is what makes a clinic eligible for a local map result rather than appearing as an anonymous shop.
The sitemap covers 492 URLs and regenerates daily; robots rules exclude private and duplicate paths; product pages carry Product markup with images, price and availability. Analytics is wired through the Google tag, loaded after hydration so it never delays first paint.
A note on ratings
Responsive
Holds together at every width.
The three-layer header collapses to a sheet, the product grid reflows to a single column, and checkout stays usable on a phone — where most of this catalogue's traffic lands.


Operations
Running it, and what it costs.
Deployed on Vercel against the production Supabase project, with a verified sending domain for transactional mail. Eleven maintenance scripts cover the work that recurs: migration verification, seed regeneration, WooCommerce conversion, taxonomy seeding, icon and social-card generation, image optimisation and a live email delivery test.
| Resource | In use | Free-tier limit |
|---|---|---|
| Database | ~1,250 rows | 500 MB |
| File storage | 70.4 MB | 1 GB |
| Low volume | 3,000 / month |
The one real exposure
Outstanding
Deferred, with the reasoning recorded.
Product reviews
DeferredVerified-purchase reviews need order volume to be worth building; empty stars on 369 products read worse than none. Google Business Profile reviews are the higher-value target first.
WhatsApp notifications
CostedRoughly ₹30 per month at current volume, with no DLT registration required, unlike SMS. Needs Meta business verification and a dedicated number.
Three dosage categories
BlockedGranule, Kwatham and a Tablets category separate from Gulika do not exist yet. Splitting the latter means reassigning 116 products.
Google Business Profile
Client actionThe single largest lever for local ranking, and outside the codebase. The map coordinates in the site should be replaced with the exact pin once claimed.
Have a business that needs more than a brochure?
Catalogues, bookings, payments and the back office to run them — designed, built and deployed as one system.