Portfolio Case Study

Seoul Skin Archive

TypeScriptReactCommerce ArchitectureDesign SystemDemo Engineering

A complete K-beauty subscription journey built as a safe interactive demo. Visitors can discover the brand, compare plans, take a skin quiz, build a box, use a cart, and review prepared account, order, and shipment states without touching production data.

May 11, 20266 min readDemo
Seoul Skin Archive storefront home
01 / 4Storefront

02 — Screens and brief

Customer journey
Discover → profile → cart → account
Design tokens
20+ scoped variables
Supported locales
5 (ko / en / ja / de / fr)

Client
Seoul Skin Archive
Role
Commerce journey design, frontend architecture, typed domain modeling, isolated visual system

03Domain Model

Production-grade TypeScript interfaces across the full commerce schema

Eight TypeScript interfaces define the commerce domain: SSAUser, Product, ProductImage, Subscription, Shipment, Address, Profile, and BeautyBoxCartItem. Field names, nullability, and relationship IDs match the conventions a Supabase-backed schema would expose — nullable recipient phones, optional address lines, is_primary and sort_order on product images, and enum-constrained status fields on subscriptions and shipments.

The product model is the most layered: a sorted image array (is_primary + sort_order), multi-valued skin_types and skin_concern string arrays, and nullable cost and retail fields that reflect real sourcing data quality. The structural match means the demo objects are a direct lift into production code, not a simplified placeholder that would require a rewrite.

04Demo Infrastructure

Supabase interface stub, synchronous auth, and a deterministic fixture graph

Three production dependencies are replaced at the interface boundary. The Supabase client (supabase.ts) defines the full SSAAuthClient and generic SSAQueryBuilder<T> interfaces — with correct method signatures for select, eq, order, limit, single, and upsert — but getSupabaseClient() returns null. Component code that imports supabase compiles against the real interface without bundling the library or making any network call.

The auth hook (useRequireAuth) returns DEMO_USER synchronously, eliminating async auth resolution. The fixture graph in demo-data.ts covers every domain object with consistent cross-referenced IDs: DEMO_USER → DEMO_PROFILE → DEMO_ADDRESSES → DEMO_SUBSCRIPTIONS → DEMO_SHIPMENTS → DEMO_PRODUCTS. Relationship fields (shipping_address_id, subscription_id, user_id) resolve correctly across the my/ account pages without a backend.

05Design System

CSS custom properties consumed through a typed runtime hook

The visual language is expressed in 20+ CSS custom properties scoped to the SSA context: --ssa-sage, --ssa-espresso, --ssa-cream, --ssa-forest, --ssa-forest-deep, --ssa-card-green, --ssa-sand, and more. The useTheme() hook reads these values at runtime and returns a typed ThemeTokens object used directly in inline style expressions across every component. No CSS class generation, no build-time processing, and no stylesheet interference with the host application.

Typography is centralized in keepBrandTerms() and keepBrandTermsDeep() utilities that protect brand strings from automatic capitalization or translation transforms. The i18n system loads server-side typed dictionary files for five locales (ko, en, ja, de, fr) in server components and passes them to client islands via props, requiring no client-side locale resolution at runtime.

06State Machine

Skin profile from the quiz persists as a typed field into the cart item

The purchase flow is a typed hand-off chain: the skin quiz writes the result to sessionStorage; SubscribeClient reads it on mount and passes it to buildBeautyBoxCartItem(), which embeds it in BeautyBoxCartItem.skinType; the cart serializes the item to localStorage; CartClient deserializes it and forwards skinType to the checkout success URL. Each boundary is a typed transition.

Plan selection is driven by BEAUTY_BOX_PLANS — a typed config array with price, numericPrice, and showKit flags — and normalizeBeautyBoxPlanId() coerces arbitrary URL param input (including the legacy onetime variant) to the canonical plan ID. The success page reads from the URL param rather than from upstream component state, making it stateless with respect to the subscription flow.

Interactive build

Seoul Skin Archive

Start at the storefront, compare the beauty-box plans, continue through the skin profile and cart, then inspect the prepared member, order, and shipment states. Every write stays in local browser storage.

See demo

Yurasis

상호: 유라시스 랩

대표: 이규형

사업자등록번호: 842-05-03371

주소: 서울특별시 강남구 선릉로69길 19, 101동 401호(역삼동, 역삼래미안)

업태: 정보통신업 · 종목: 응용 소프트웨어 개발 및 공급업, 컴퓨터 프로그래밍 서비스업

TEL: 010-2709-9846

Email: siholee@yurasis.com

Seoul Skin Archive Case study | YURASIS Portfolio