← Back to prototype
BBpillow · Platform documentation
Wireframes, database schema & tech stack.
Supporting documentation for the BBpillow e-commerce platform — website & LINE Mini-App.
Built to serve B2C direct sales and B2B lead generation from a single unified backend.
1. Wireframe outline
Mobile-first, structural breakdown. Both pages designed to flex from a 360px viewport up to a 1180px content max.
Homepage
bbpillow ●EN · TH · 🛒 · 👤
HERO
Eyebrow · 100-year brand
H1: "Quality sleep at flea-market prices"
Sub-copy · CTA primary (Shop) · CTA secondary (Bulk inquiry)
Stats: 22M฿ · 100K+ customers · 4.8★
[product imagery composition / right]
Free
ship
1-yr
warranty
7-day
returns
100-yr
brand
CATEGORIES (5 tiles · image + label)
BEST SELLERS
carousel/grid · 4 product cards · "View all" link
BRAND STORY (dark band)
Portrait of founder · pull quote · CTA "Read story"
B2B strip (conditional) — Wholesale inquiry
LINE OA · "Add friend, get ฿100 off + points"
Footer · 4 columns© 2026
Structural notes
- Sticky chrome: mode toggle (web ↔ mini-app) + lang switch · always on top.
- Hero: headline split — Thai-first; CTA pair guides B2C left, B2B right.
- Value props strip: light tinted band — reduces purchase anxiety up front.
- Categories: 5 photo tiles. Tap routes to filtered Shop.
- Best sellers: 4 cards. Server-driven from
products.sold ranking.
- Brand story band: dark soil background — visual rhythm break. Encapsulates Rinen philosophy.
- LINE CTA: green-on-cream block — frictionless entry to LOA / Mini-App.
Hierarchy of intent
P1 Shop CTA (B2C) ·
P2 Bulk inquiry (B2B) ·
P3 LINE add-friend ·
P4 Brand story
Shop / Catalog
← Breadcrumb · Home / Shop120 items
H1 · "All products" + count
All
Pillow
Mattress
Topper
Sheet
FILTERS
▢ Material
▢ Price (range)
◯ Sort by
Product card
Product card
Product card
Product card
Product card
Product card
FooterPagination
Product card anatomy
- Square image · -% badge top-left · "new" badge top-right
- Bilingual product name (clamp 2 lines)
- Stars · rating · review count
- Price (Clay, large) · compare price (strike)
- Inline "Add to cart" pill on hover/tap
Filter logic
Client-side filter for hi-fi feel; server-driven in production via:
GET /products?cat=pillow&mat=microfiber&p_max=300&sort=popular
Mobile behavior
Filters collapse into a bottom sheet · category chips remain horizontal scroll · grid drops to 2 columns at <860px.
2. Database schema
PostgreSQL recommended. Schema supports both B2C consumer accounts and B2B lead/quote workflow, plus unified inventory shared by Web + LINE Mini-App.
Core entities
users — B2C customers (LINE-linked)
addresses — shipping addresses, 1:N to users
products — master catalog (shared)
product_variants — size, color, SKU
inventory — single-source stock per variant
orders + order_items — B2C transactions
reviews — UGC, moderated
loyalty_ledger — point credits/debits
B2B lead-gen entities
b2b_leads — quote requests from Wholesale form
b2b_accounts — converted companies (post-sales contact)
price_tiers — quantity bands → unit price
quotes — generated PDFs, lifecycle states
Schema definition
-- ───── B2C members + auth ─────
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
line_user_id text UNIQUE, -- LIFF sub claim
email text UNIQUE,
phone text,
display_name text,
avatar_url text,
tier text DEFAULT 'soft', -- soft|warm|gold
points integer DEFAULT 0,
locale text DEFAULT 'th',
created_at timestamptz DEFAULT now(),
last_login timestamptz
);
CREATE TABLE addresses (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
label text, -- home, office
recipient text,
phone text,
line1 text,
district text,
city text,
zip text,
is_default boolean DEFAULT false
);
-- ───── catalog (shared by web + mini-app) ─────
CREATE TABLE products (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
slug text UNIQUE,
name_th text NOT NULL,
name_en text NOT NULL,
desc_th text,
desc_en text,
category text, -- pillow|mattress|sheet|blanket|topper
material text,
base_price integer, -- THB
compare_at integer,
active boolean DEFAULT true,
created_at timestamptz DEFAULT now()
);
CREATE TABLE product_variants (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product_id uuid REFERENCES products(id) ON DELETE CASCADE,
sku text UNIQUE,
size text,
color text,
price integer -- override base if set
);
CREATE TABLE inventory (
variant_id uuid PRIMARY KEY REFERENCES product_variants(id),
stock integer DEFAULT 0, -- single source of truth
reserved integer DEFAULT 0, -- in-flight orders
updated_at timestamptz DEFAULT now()
);
-- ───── B2C orders ─────
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
order_no text UNIQUE NOT NULL, -- BB-XXXXXX
user_id uuid REFERENCES users(id),
channel text, -- web | line_mini
status text DEFAULT 'pending', -- pending|paid|shipped|delivered|refunded
subtotal integer,
shipping integer,
total integer,
payment text, -- cod|promptpay|card|line_pay
address_id uuid REFERENCES addresses(id),
notes text,
created_at timestamptz DEFAULT now()
);
CREATE TABLE order_items (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
order_id uuid REFERENCES orders(id) ON DELETE CASCADE,
variant_id uuid REFERENCES product_variants(id),
qty integer,
unit_price integer
);
-- ───── loyalty + reviews ─────
CREATE TABLE loyalty_ledger (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES users(id),
delta integer, -- + earn / - redeem
reason text, -- order:BB-... / reward:R-...
ref_id uuid,
created_at timestamptz DEFAULT now()
);
CREATE TABLE reviews (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product_id uuid REFERENCES products(id),
user_id uuid REFERENCES users(id),
order_id uuid REFERENCES orders(id), -- verified purchase
stars smallint CHECK (stars BETWEEN 1 AND 5),
body_th text,
body_en text,
photos text[], -- UGC image URLs
status text DEFAULT 'pending', -- pending|approved|rejected
created_at timestamptz DEFAULT now()
);
-- ───── B2B lead gen ─────
CREATE TABLE b2b_leads (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company text NOT NULL,
contact text,
email text,
phone text,
biz_type text, -- hotel|dorm|retail|other
qty_band text, -- 50-99|100-499|...
products uuid[], -- product IDs of interest
message text,
status text DEFAULT 'new', -- new|contacted|quoted|won|lost
assigned_to uuid, -- sales rep id
source text, -- web|line|referral
created_at timestamptz DEFAULT now()
);
CREATE TABLE b2b_accounts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
lead_id uuid REFERENCES b2b_leads(id),
company text NOT NULL,
tax_id text,
credit_terms text, -- net30, prepaid
primary_rep uuid,
active boolean DEFAULT true
);
CREATE TABLE price_tiers (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product_id uuid REFERENCES products(id),
min_qty integer,
max_qty integer,
unit_price integer
);
CREATE TABLE quotes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
quote_no text UNIQUE,
lead_id uuid REFERENCES b2b_leads(id),
account_id uuid REFERENCES b2b_accounts(id),
total integer,
pdf_url text,
valid_until date,
status text DEFAULT 'draft', -- draft|sent|accepted|expired
created_at timestamptz DEFAULT now()
);
Key relationships & choices
- Single product table. Both B2C and B2B reference the same products. B2B sees additional
price_tiers; web only ever shows the base price.
- Inventory is per-variant, not per-product. Single row per SKU prevents drift between Web and Mini-App.
- Channel field on orders. Lets analytics split web vs LINE revenue without separate tables.
- Loyalty ledger, not a counter. Append-only — every earn/redeem is auditable.
users.points is a denormalized cache.
- LINE user ID =
sub claim from LIFF's verified ID token. Source of truth for identity in mini-app.
- Verified reviews only. Reviews require an
order_id reference; prevents fake UGC.
3. Recommended tech stack
Optimized for speed of delivery, LINE-first user base, and ability to scale from 100K to 1M users without a rewrite.
Frontend
Next.js 14 (App Router)
Server components for SEO on shop/PDP, edge runtime for the Thai market. Shared component library between web and LIFF view.
TypeScript + Tailwind
Type-safe data layer end-to-end. Tailwind tokens map directly to the Paper/Soil/Sky/Clay palette in this prototype.
@line/liff SDK
LIFF v2 for the Mini-App. Wraps LINE Login, profile, message-sharing, and payment intents.
next-intl
Bilingual TH/EN out of the box, persistent locale via cookie + LINE profile language.
Backend
NestJS or tRPC + Hono
REST + typed RPC. tRPC is faster to ship since web and mini-app share the call signatures. NestJS is the safer pick if a separate B2B admin team will use the API.
PostgreSQL (Supabase / Neon)
Managed Postgres + row-level security. Auth, storage, realtime in one. Schema above runs as-is.
Redis (Upstash)
Cart sessions, rate limits, hot product cache. Edge-replicated for SE-Asia latency.
BullMQ workers
Background jobs — order confirmations, LINE OA broadcast, low-stock alerts, weekly B2B digest.
Payments & logistics
Omise / 2C2P
Cards + PromptPay + bank QR. Best Thai-market payment processors.
LINE Pay
Direct LINE Pay integration for Mini-App orders — 1-tap checkout, native UX.
Flash Express / Kerry / Thailand Post APIs
Programmatic label generation, tracking webhooks → order status updates.
Ops & analytics
Vercel
Hosting for Next.js · edge functions · preview deploys.
Sentry + PostHog
Errors + product analytics. PostHog session replay reveals where B2B leads abandon the quote form.
Cloudinary
Product image CDN, auto-format (WebP/AVIF), on-the-fly transforms — keeps the visual quality at the price-point promise.
Resend + LINE Notify
Transactional email + LINE-channel notifications for orders, B2B quotes, stock alerts.
4. LINE ecosystem integrations
LINE Login (SSO)
Both web and mini-app authenticate via LINE OAuth.
Server verifies the ID token, looks up or creates a row in users keyed by line_user_id,
auto-fills profile fields. One identity, two channels.
LIFF / Mini-App
Mini-App is a LIFF page deployed at liff.bbpillow.co.th.
Uses liff.getProfile() for instant identity, liff.sendMessages() to share products to friends,
liff.scanCode() for store QR pickup orders.
LINE OA + Notify
Order updates, shipment tracking, and B2B quote PDFs sent via LINE Messaging API.
Promotional broadcast for new product drops; "buy in LINE" deep-link returns to the Mini-App.
Unified inventory
Web and Mini-App both call GET /api/inventory/:variant.
Reservation happens at order placement against inventory.reserved with a 15-minute TTL.
Stock can never be oversold across channels.
Next steps
1. Validate wireframes with the founder · 2. Lock palette + type pair via the prototype's Tweaks panel ·
3. Spike LIFF + LINE Login on a throwaway branch · 4. Migrate the 8 sample products into the real schema · 5. Soft-launch to existing LINE OA followers.