Production-oriented MVP for a custom t-shirt design and ordering platform built around CustomCat fulfillment.
- Next.js 15 + App Router + TypeScript
- Tailwind CSS for UI
- PostgreSQL + Prisma ORM
- NextAuth (credentials-based baseline)
- Stripe Checkout + webhooks
- CustomCat provider abstraction (mock/live)
- Object storage abstraction (local or S3-compatible)
- Catalog sync job fetches CustomCat products + variants and caches to
ProductCache/ProductVariantCache. - User designs shirt in a unified
DesignerStudioUI (product + color + size + qty + upload + drag/resize inside print area). - Designer saves a
DesignSession, then createsCartItemwith persisted transform + artwork metadata. - User checks out in Stripe Checkout.
- Stripe
checkout.session.completedwebhook:- marks payment as paid
- finalizes artwork asset from temp -> final storage URL (
design_url) - creates local
OrderandOrderItemrecords - attempts CustomCat order submission
- stores
customCatOrderIdorsubmissionError
- CustomCat webhook updates fulfillment and tracking status.
- Account + admin dashboards expose order state and operational health.
src/lib/customcat/*: provider interface + live/mock adapterssrc/lib/storage/*: local/S3 provider interface + implementationssrc/lib/services/*: pricing, catalog sync, cart, checkout, fulfillment orchestrationsrc/lib/security/*: upload validation, webhook signature validation, simple rate limiting
.
├── Dockerfile
├── docker-compose.yml
├── .env.docker
├── prisma
│ ├── schema.prisma
│ └── seed.ts
├── scripts
│ ├── docker/dev-start.sh
│ └── sync-catalog.ts
├── src
│ ├── app
│ │ ├── api
│ │ │ ├── admin/products/toggle
│ │ │ ├── auth/[...nextauth]
│ │ │ ├── auth/register
│ │ │ ├── cart
│ │ │ ├── cart/items
│ │ │ ├── catalog/sync
│ │ │ ├── checkout
│ │ │ ├── designer/session
│ │ │ ├── designer/upload
│ │ │ ├── orders/retry
│ │ │ └── webhooks/{stripe,customcat}
│ │ ├── account
│ │ ├── admin
│ │ ├── cart
│ │ ├── catalog
│ │ ├── checkout
│ │ ├── designer
│ │ ├── login
│ │ ├── orders/[orderNumber]
│ │ ├── orders/confirmation
│ │ ├── register
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components
│ │ ├── auth
│ │ ├── cart
│ │ ├── designer
│ │ ├── orders
│ │ └── ui
│ ├── lib
│ │ ├── auth
│ │ ├── customcat
│ │ ├── db
│ │ ├── jobs
│ │ ├── security
│ │ ├── services
│ │ ├── storage
│ │ ├── env.ts
│ │ └── utils.ts
│ ├── auth.ts
│ └── types/next-auth.d.ts
├── public/uploads/{temp,final}
└── .env.exampleImplemented in prisma/schema.prisma:
UserProductCacheProductVariantCacheDesignSessionCartCartItemOrderOrderItemPaymentUploadedAssetWebhookEventAppSettingProductPricingRuleIntegrationConfig- NextAuth models (
Account,Session,VerificationToken)
Includes required fields for:
catalog_sku, color, size, quantity- artwork URL + transform JSON
- mockup preview URL
- Stripe IDs
- CustomCat order ID
- fulfillment status + tracking
Use .env.example. Core keys:
- app:
APP_BASE_URL,APP_SETTINGS_ENCRYPTION_KEY - database:
DATABASE_URL - auth:
NEXTAUTH_SECRET,NEXTAUTH_URL - stripe:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY - customcat:
CUSTOMCAT_MODE,CUSTOMCAT_API_BASE_URL,CUSTOMCAT_API_KEY,CUSTOMCAT_WEBHOOK_SECRET - storage:
STORAGE_PROVIDER+ optionalS3_*
See:
src/lib/customcat/types.tssrc/lib/customcat/live-provider.tssrc/lib/customcat/mock-provider.tssrc/lib/customcat/index.ts
Interface includes:
getCatalogProducts()getCatalogCategories()getProductVariants(productId)getCatalogSkuDetails(catalogSku)createOrder(payload)getOrderStatus(customCatOrderId)handleWebhook(payload)
- Library job:
src/lib/jobs/catalog-sync.ts - Script:
scripts/sync-catalog.ts - Protected API trigger:
POST /api/catalog/sync(admin session orAuthorization: Bearer $CRON_SECRET)
Main component: src/components/designer/designer-studio.tsx
Features:
- unified left/center/right workflow UI
- product/color/size/qty selectors
- upload PNG/JPG with validation + low-resolution warning
- bounded print area with drag + resize controls
- alignment + scale controls
- save design session + add configured item to cart
Admin area uses a left-sidebar navigation model similar to WordPress at /admin:
- Dashboard overview
- Content manager (
/admin/content) to edit homepage copy - Pricing manager (
/admin/pricing) for global markup/shipping + per-product markup overrides - Integrations manager (
/admin/integrations) for Square, Stripe, CustomCat, storage, and other connection settings - Operations page (
/admin/operations) for catalog sync, webhook logs, product visibility, and retrying failed submissions
- Cart UI:
src/app/cart/page.tsx,src/components/cart/cart-view.tsx - Checkout page:
src/app/checkout/page.tsx - Checkout session API:
POST /api/checkout
- Server client:
src/lib/services/stripe.ts - Checkout session creation:
src/lib/services/checkout.ts - Webhook handler:
src/app/api/webhooks/stripe/route.ts
Fulfillment orchestrator: src/lib/services/order-fulfillment.ts
Behavior on paid webhook:
- finalize artwork asset to permanent storage URL
- create local order/payment records
- submit to CustomCat automatically
- mark failures for admin retry (
/api/orders/retry)
- Stripe:
src/app/api/webhooks/stripe/route.ts - CustomCat:
src/app/api/webhooks/customcat/route.ts
Both store WebhookEvent records for audit/debug.
npm install
cp .env.example .env
# configure DATABASE_URL, NEXTAUTH_SECRET, Stripe keys, etc.
npx prisma generate
npx prisma migrate dev --name init
npm run prisma:seed
npm run devSeed creates:
- admin user (from
ADMIN_EMAILSfirst value) with temporary passwordChangeMe123! - product/variant cache via CustomCat provider (
mockby default)
Run everything (app + Postgres) with Docker:
docker compose up --buildThen open:
Useful Docker commands:
# stop containers
docker compose down
# stop and remove volumes (full reset)
docker compose down -vNotes:
- Docker uses
.env.dockerby default. - App runs on host port
3001so it will not conflict with an existinglocalhost:3000. - Postgres is exposed on host
localhost:5433.
- Start Postgres and set
DATABASE_URL. - Run Prisma generate + migrate/db push.
- Seed DB.
- Run app (
npm run dev -- --port 3001if3000is occupied). - For checkout testing, run Stripe CLI forwarding webhooks to
/api/webhooks/stripe. - For scheduled sync, trigger
POST /api/catalog/syncvia cron usingCRON_SECRET.
src/lib/customcat/live-provider.ts- TODO: confirm final CustomCat endpoint paths + exact payload/response field names.
src/lib/services/order-fulfillment.ts- TODO: align final CustomCat order payload format with production contract.
- Fraud controls
- TODO: replace baseline signal flags with external fraud scoring/rules.
- API keys are never exposed to the browser.
- Designer flow is the primary path; catalog/cart/checkout support it.
CUSTOMCAT_MODE=mockallows local development without live credentials while preserving production interfaces.