Kiran Gems is the world's largest manufacturer of natural polished diamonds. Its finished-jewelry arm sells to approved retail jewelers, never to the public. Social Animal built the platform those retailers now buy through: a gated wholesale terminal where each account sees only its own confidential pricing, selects exact certified diamonds from a live inventory feed, configures rings, orders on credit terms, and requests goods on memo.

This is a summary of what the build actually contains, measured from the codebase on 26 August 2026.

The short answer

Social Animal designed and built a full-stack B2B commerce platform on Next.js 15 and Supabase for a diamond manufacturer with more than 10,000 finished styles. The system has four surfaces sharing one catalogue: a public brand site, an authenticated retailer portal, a staff management dashboard, and a consumer store built behind a feature flag. Confidential per-account pricing is isolated at the database layer using Postgres row-level security, verified by a test suite that blocks merges in CI.

At a glance

Measure Value
Source 86,500 lines of TypeScript, TSX and SQL across 640 files
Routes 139 pages, 12 API handlers
Public brand site 19 pages including a five-part diamond education hub
Retailer portal 27 routes
Management dashboard 33 admin screens
Consumer store 6 routes, feature-flagged off at launch
Database 42 tables, 35 append-only migrations, 2,115 lines of SQL
Security RLS on 42 of 42 tables, 56 policies, CI-blocking isolation suite
Background jobs 6 Inngest functions for catalogue, price, stock and diamond sync
Rendering 528 of 640 files are server components or server-only modules
Delivery 410 commits, 11 June to 25 August 2026

What made this harder than a store

Three constraints shaped every decision.

Price is the security boundary. In wholesale diamonds, an account's price is the commercial relationship. A retailer seeing a competitor's number is not a bug, it is a breach. That rules out the usual pattern of filtering in the application layer and hoping the query is right.

Inventory is not ours. The finished catalogue lives in Kiran's GATI ERP. Loose certified diamonds live in a separate supplier system that also sells the same stones on RapNet, on the phone, and through other channels. The platform is a reader of two moving systems, not the owner of either.

Half the site must rank, half must never appear. The brand pages, education hub and trade-access flow need to be found by retail buyers and cited by AI assistants. Every priced route must be invisible to crawlers and to anyone without an approved account.

How the pricing isolation works

Wholesale pricing never resolves in application code. It resolves in Postgres.

Every table carries row-level security from the first migration, with no exceptions and no temporary disabling. A SQL helper resolves the authenticated user to a retailer, and every trade-area policy filters on it. The raw price tables are not selectable by retailer roles at all; prices come back only through a SECURITY DEFINER function that reads the caller's tier and any per-account override. The anonymous role can read active products and media, and no price column.

Two guards keep it honest:

  1. A cross-retailer isolation suite spins up an ephemeral Postgres in CI, applies all 35 migrations, and asserts that retailer A querying retailer B's orders, memos, samples and prices returns zero rows. Any leak fails the build.
  2. A separate CI script greps the tree for service-role key exposure, failing if the key appears under a NEXT_PUBLIC_ prefix or is referenced from any client component.

The result: an application bug cannot leak a price, because the database returns nothing to leak.

The loose-diamond selector

The headline feature is exact-stone selection. A retailer filters the live book of certified natural diamonds, opens the one they want, and carries it into a ring configuration or an order.

Filtering. Around twenty facets, built to the vocabulary dealers actually use: shape, carat range, colour, clarity, cut, polish, symmetry, lab, fluorescence, price band, plus numeric ranges for table percentage, depth percentage, length, width and length-to-width ratio. Certificate lookup by number is a first-class search.

Ingestion. Two Inngest jobs keep the table current. A block-scanning sync upserts on certificate number and converges across runs, because the upstream feed throttles bulk pulls and is slow at high offsets. A cheaper availability sweep re-checks statuses frequently and flips stones that sold elsewhere. Both degrade to a no-op rather than throwing when credentials are absent.

Quality gates on the way in. Only natural diamonds pass. Simulant material such as quartz, moissanite and cubic zirconia is rejected by pattern, GIA, IGI and HRD grading is required, and shape, colour and clarity are validated against explicit sets before a row is written. 3,260 stones passed those gates into production.

Not selling the same stone twice. Selecting a stone places a hold with a 20-minute time-to-live, re-confirmed at checkout. Because the same inventory sells through other channels, a cached "available" is never trusted: the platform re-checks the stone against the live feed at the moment of reservation, and marks the reservation unverified rather than blocking the retailer if the feed is down. A cross-channel upstream hold is written and flag-gated, waiting on the supplier's confirmation of hold duration.

The ring builder

The configurator runs setting, then shape, then carat, then metal, with the account's price resolving live at each step. Certified and non-certified paths fork early, since a buyer choosing an exact stone and a buyer choosing a carat band want different screens.

Details that came from the client's own trade practice rather than from a spec:

  • Certificate numbers are masked to the last three digits in the retailer view, with the full number carried into the SKU and the operations email.
  • Eternity rings compute stone count deterministically from finger size and stone diameter, so the quote matches what the factory will actually set.
  • Settings without a matching stone route to a request form rather than a dead end.
  • Imagery is labelled representative, while exact-stone lines show the real certificate data.

Where the loose-stone feed cannot serve a line, the builder falls back to carat bands. Both paths exist by design; the fallback was the contractual safety net for the one genuine integration risk in the project.

The management dashboard

Thirty-three admin screens, built as an operations terminal rather than an analytics product.

Order, memo and sample queues with guarded state transitions. Retailer management covering approval, pricing tier, credit terms, memo limit and per-account price overrides. A KYC and document review flow for trade-access applications. Photography management keyed to style number. Site content editing for the public pages, FAQ, guides and navigation. Sync health with last-run status and manual retry. An audit log, an invoice builder, an announcement composer, staff role-based access control with multi-factor authentication and recovery codes, and a security screen.

The dashboard shows operational data. Bestseller ranking, margin analysis and regional intelligence sit deliberately outside it, as a separately quoted module. Holding that line is part of the engineering, not a footnote to it.

The integration layer

Everything upstream sits behind a SystemAdapter interface with three implementations: a mock adapter seeded from the printed catalogue, a GATI ERP adapter, and the loose-diamond feed client. Zod schemas validate every payload at the boundary.

Building the mock first meant the entire platform was functional before the client's API discovery finished. When GATI landed, the swap was a configuration change, and the read-sync jobs shipped env-gated and inert until credentials existed. Write-back stays stubbed until the ERP exposes the endpoints, and that gap is documented as a client dependency rather than worked around with a scraping hack.

The SEO and AEO architecture

The public half is engineered to be found and quoted; the priced half is engineered to be absent.

Structured data on 20 templates: Organization with full postal address and the Kiran Gems parent relationship, WebSite, BreadcrumbList on every page, Article on each education guide, FAQPage on the policy and FAQ pages, and Product with Offer on the consumer store.

An llms.txt file at the root that states plainly what the company is, that it is trade-only, that wholesale pricing is confidential and unreachable, and what every public URL contains. An AI assistant asked about Kiran gets an accurate answer instead of inferring a consumer shop.

Answer-first education content. A five-guide hub on the 4Cs, diamond shapes, certification, ring sizing and natural versus lab-grown, each written to answer the question in its first passage. These are the pages a retail buyer's research actually starts from.

Render-time meta discipline. Titles clamp to 60 characters and descriptions to 155 at a word boundary, applied at render rather than trusted from the database.

A sitemap that tells the truth. No lastmod, no priority, no changefreq. The original implementation stamped every entry with the current time on an hourly revalidate, which told Google that twenty pages changed every hour forever. A wrong date is worse than no date, because it trains a crawler to ignore the signal.

Crawl hygiene. Fifteen design-prototype routes, each a duplicate of the homepage, are permanently redirected to the canonical page, with exact sources rather than a wildcard, because several of those paths are also live asset folders serving the brand's photography. Every priced route is disallowed. A site-wide indexability gate keeps all crawlers out until launch day.

Flag-aware crawling. The consumer store's presence in both robots.txt and the sitemap is driven by the same feature flag that enables it, re-evaluated hourly. Kiran can activate the store from a settings screen and the crawl surface follows without a deployment.

Performance at catalogue scale

Server components are the default, with 112 client components out of 640 files pushed to interactive leaves. Product imagery runs through Cloudinary with AVIF and WebP served by next/image. Baseline security headers ship with a deliberately report-only Content Security Policy, running as an inventory pass across four different surfaces before anything is enforced, because a partially specified CSP silently breaks media rather than failing loudly.

Key takeaways

  • Put the security boundary in the database. When confidentiality is the product, row-level security plus a CI-blocking isolation suite is the only defensible design. Application-layer filtering is a promise; a policy is a guarantee.
  • Build the mock adapter first. An interface with a seeded implementation let the platform be finished before the client's API was documented, and turned integration into a swap rather than a rewrite.
  • Never trust a cached availability on shared inventory. If stock sells through channels you do not control, re-check at the moment of commitment and hold with a timeout.
  • Gated sites still need answer engine work. The public half carries the entity, the structured data and the llms.txt; the priced half stays out of the index. Both halves are deliberate.
  • A wrong signal is worse than a missing one. That applies to sitemap dates, to stone availability, and to representative imagery. Say nothing rather than say something false.

Frequently asked questions

What is a B2B trade platform for jewelry?

A gated wholesale ordering system where approved retail buyers log in to see confidential account pricing, place orders on credit terms rather than card, request goods on memo (consignment), request samples, and reorder past specifications in one click. Unlike a consumer store, no prices appear publicly and there is no card checkout on the trade side.

How do you stop one retailer seeing another retailer's wholesale pricing?

Enforce it in the database rather than in application code. Every table has Postgres row-level security enabled, price tables are not directly selectable by retailer roles at all, and prices resolve through a single SECURITY DEFINER function that reads the caller's identity. An application bug then cannot leak pricing, because the query returns zero rows.

Can a website let a buyer choose a specific certified diamond?

Yes, when the supplier exposes a per-stone feed carrying certificate number, measurements, price and availability. The feed syncs into Postgres, the stones are presented behind dealer-grade filters, the chosen stone is re-checked live at the moment of selection, and a timed hold prevents the same stone being claimed twice.

What does AEO mean for a trade-only website?

Answer engine optimization for a gated site means the public half must answer the questions buyers ask AI assistants, while the priced half stays invisible. That means an education hub, FAQ and company pages carrying FAQPage and Article structured data, an llms.txt file describing the business accurately, and every priced route disallowed in robots.txt.

How long does a platform like this take to build?

This one reached client review in 11 weeks and 410 commits, covering four surfaces, 42 database tables, two upstream integrations and 139 routes. The compressing factor was building against a mock adapter from day one, so no phase waited on someone else's API.

Technology

Layer Choice
Framework Next.js 15, App Router, React Server Components
Hosting Vercel
Database, auth, storage Supabase, Postgres 15, row-level security
Background jobs Inngest
Integrations GATI ERP, certified loose-diamond feed
AI Anthropic Claude API
Email Resend with React Email
Media Cloudinary
UI Tailwind CSS, shadcn/ui, Lucide icons
Validation TypeScript strict, Zod at every boundary

Social Animal builds commerce and content platforms where the hard part is upstream. If you have an ERP, a confidential price book and a catalogue that will not fit in a template, start a conversation. If you would rather start smaller, we also run technical SEO audits.