Install
openclaw skills install neway-commerce-osgenerate, scaffold, build, and prepare deployment for a reusable react + vite + hono commerce operating system focused on digital products, storefronts, subscriptions, ai-assisted sales, admin dashboards, and international stripe-style payments. use when the user wants a complete website skeleton from a short prompt, especially for ecommerce launches, creator stores, productized services, multi-product studios, or newaystudio-style product matrices with edgeone pages deployment and full-stack edge functions.
openclaw skills install neway-commerce-osGenerate a production-quality React + Vite storefront from a single product brief. Default output includes: product catalog, cart, product detail, checkout, wishlist, admin dashboard, AI sales concierge, mock-to-live payment switching, and EdgeOne Pages deployment files.
This Skill is the authoritative source for all code conventions used across all generated stores. Every agent invoking this Skill must follow the conventions below — no exceptions, no shortcuts.
templates/base/ into a new directory named <brand-slug>-store/.__PLACEHOLDERS__, fill in product data, set brand colors in tailwind.config.js.npm run build and ensure zero TypeScript errors.npm run dev) and verify all routes are accessible.<brand-slug>-store/
├── index.html ← must contain Google Fonts + correct lang attr
├── package.json ← must include all required deps (see below)
├── tailwind.config.js ← must define font-body, font-heading, brand colors
├── vite.config.ts
├── tsconfig.json
├── .env.example
├── .gitignore
├── edgeone.json
├── src/
│ ├── main.tsx
│ ├── App.tsx ← route definitions
│ ├── styles.css
│ ├── vite-env.d.ts
│ ├── components/
│ │ ├── Navbar.tsx
│ │ ├── Footer.tsx
│ │ ├── ProductCard.tsx
│ │ ├── CartDrawer.tsx ← slide-out cart (REQUIRED, not optional)
│ │ └── AIChatWidget.tsx
│ ├── pages/
│ │ ├── HomePage.tsx
│ │ ├── ShopPage.tsx ← dedicated browse/filter/sort page (REQUIRED)
│ │ ├── ProductPage.tsx ← /product/:id — MUST exist and be routed
│ │ ├── CartPage.tsx ← fallback full-page cart
│ │ ├── CheckoutPage.tsx
│ │ ├── WishlistPage.tsx
│ │ └── AdminPage.tsx
│ ├── data/
│ │ └── products.ts ← typed product records
│ ├── store/
│ │ └── cartStore.ts ← Zustand with persist middleware
│ └── types/
│ └── index.ts
└── functions/
├── api/
│ ├── products.ts
│ ├── checkout.ts
│ └── assistant.ts
└── node/
└── stripe-webhook.ts
| Path | Page | Notes |
|---|---|---|
/ | HomePage | |
/shop | ShopPage | browse + filter + sort |
/product/:id | ProductPage | dynamic slug — MUST be wired |
/cart | CartPage | |
/checkout | CheckoutPage | |
/wishlist | WishlistPage | |
/admin | AdminPage |
// package.json dependencies (exact versions may float, list them all)
{
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1",
"zustand": "^4.5.5",
"framer-motion": "^11.3.19",
"lucide-react": "^0.400.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"typescript": "^5.5.4",
"vite": "^5.4.2",
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38"
}
}
These rules apply to every file generated by this Skill. Any AI agent invoking this Skill must treat these as hard constraints, not suggestions.
Default font stack (apply to all generated projects):
"Helvetica Neue", Arial, sans-serifindex.html — required <link> tag:
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Playfair+Display:wght@400;700&display=swap" rel="stylesheet">
tailwind.config.js — required font definitions:
theme: {
extend: {
fontFamily: {
'body': ['"Noto Sans SC"', '"Helvetica Neue"', 'Arial', 'sans-serif'],
'heading': ['"Playfair Display"', 'serif'],
},
},
},
styles.css — apply body font globally:
body {
font-family: 'Noto Sans SC', 'Helvetica Neue', Arial, sans-serif;
}
All icons MUST use lucide-react components. Emoji is prohibited in all UI code.
Rules:
lucide-react: import { ShoppingCart, Heart, Star } from 'lucide-react'size={16} or size={20} — match surrounding text size<span> with emoji content as icon substitutesCommon icon mappings:
| Old emoji | Lucide component |
|---|---|
| 🛒 | <ShoppingCart> |
| ❤️ / 💝 | <Heart> |
| ⭐ / ✨ | <Star> |
| 🎉 | <PackageCheck> |
| 🚚 | <Truck> |
| 📦 | <PackageCheck> |
| 🌿 | <Leaf> |
| ❄️ | <Snowflake> |
| ✈️ | <Plane> |
| 🔄 | <RefreshCw> |
| ✓ / ✅ | <Check> |
Prohibited font sizes:
All odd px values in text-[Xpx] Tailwind classes are forbidden.
Correction table:
| Forbidden | Use instead |
|---|---|
text-[11px] | text-[12px] |
text-[13px] | text-[14px] |
text-[15px] | text-[16px] |
text-[9px] | text-[10px] |
Rule: All pixel font sizes must be even numbers. Use Tailwind's standard scale (text-xs, text-sm, text-base, etc.) wherever possible; only use text-[Xpx] when a specific size is needed and it must be even.
All mock product images must return HTTP 200.
Protocol:
image URL in src/data/products.ts, verify the URL is reachable.https://images.unsplash.com/photo-<ID>?w=800&auto=format&fit=crop
The cart store must use Zustand with the persist middleware so the cart survives page refresh:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useCartStore = create(
persist(
(set, get) => ({
items: [],
addItem: (product) => { /* ... */ },
removeItem: (id) => { /* ... */ },
updateQty: (id, qty) => { /* ... */ },
clearCart: () => set({ items: [] }),
total: () => get().items.reduce((sum, i) => sum + i.price * i.qty, 0),
}),
{ name: 'cart-storage' }
)
)
The /product/:id route is critical. It must:
App.tsx as <Route path="/product/:id" element={<ProductPage />} />id param with const { id } = useParams()products.ts data by matching slug or idProductCard must navigate on click:
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
// on card click:
navigate(`/product/${product.id}`)
// tailwind.config.js
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
'body': ['"Noto Sans SC"', '"Helvetica Neue"', 'Arial', 'sans-serif'],
'heading': ['"Playfair Display"', 'serif'],
},
colors: {
// Override with brand-specific palette per project
brand: {
50: '#fafafa',
500: '#18181b',
900: '#09090b',
},
},
},
},
plugins: [],
}
Use framer-motion for all transitions. Standard presets:
// Card hover lift
whileHover={{ y: -4, scale: 1.02 }}
transition={{ duration: 0.2 }}
// Page enter
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
// Stagger children
variants={{ hidden: { opacity: 0 }, visible: { opacity: 1 } }}
transition={{ staggerChildren: 0.08 }}
tsconfig.json must have "strict": trueProduct interface in src/types/index.tsany unless in placeholder API handlerstsc --noEmit before delivery; fix all errorsBefore delivering the project, run:
npm install
npm run build
The build MUST complete without errors. If there are TypeScript errors, fix them before delivery. A project that does not build is not a deliverable.
CRITICAL: These are NOT suggestions. Every generated project MUST meet all of the following minimum complexity levels. If time/effort is short, do FEWER pages but make them HIGHER quality rather than spreading thin. The test output standard is the FRAISE store method — match or exceed that quality level.
The project MUST have TWO separate Zustand stores, both with persist middleware:
1. cartStore — key '<brand>-cart'
items, addItem (merge same-product), removeItem, updateQty, clearCart, total(), count()2. wishlistStore — key '<brand>-wishlist'
ids: string[], toggle(id), has(id)Every project MUST define two complete color palettes (50–950), both semantic:
| Palette | Purpose | Examples |
|---|---|---|
brand | Primary actions, highlights, CTAs | Warm orange #d4751a, amber, or deep red |
leaf / fresh | Organic/success badges, nature cues | Green #74ae58, teal #4ECDC4 |
The theme colors must feel appropriate for the product category. A fruit store uses warm orange + green; a tech store might use indigo + teal. Never use a single-palette or generic gray-only theme.
Minimum required classes (all MUST be defined and used):
| Class | Purpose |
|---|---|
btn-primary | Main CTA button (brand fill, white text, rounded-full, hover lift) |
btn-outline | Secondary button (brand border, brand text, transparent bg) |
btn-ghost | Tertiary link-style button (gray text, hover dark) |
tag | Capsule badge (uppercase, tracking-widest, rounded-full) |
input-field | Form input (border, rounded-lg, focus ring in brand color) |
The HomePage MUST contain ALL of the following sections in order:
AnimatePresence fade transition, auto-advance 5s, navigation dots, left/right arrowsisNew products (4–8 items), with framer-motion fadeUp staggerA dedicated browse page (/shop) with:
?category=xxx)A slide-out drawer component (NOT an inline section in Navbar):
A three-step checkout flow with progress indicator, accessible via /checkout from CartDrawer or CartPage "前往结算" buttons:
Step 1 — 收货信息
^1[3-9]\d{9}$)Step 2 — 支付&优惠
Step 3 — 确认订单
clearCart()Critical bug fix required: Do NOT call clearCart() on initial submit — only clear after user confirms in step 3.
Progress indicator: 3-step horizontal stepper (numbered circles, step labels, connector lines), current step highlighted in brand color, completed steps show checkmark.
Sidebar: Sticky order summary panel (right side on desktop, hidden on mobile) showing:
Order confirmation after submission:
SL + timestamp base36)The component must accept a layout prop: 'grid' | 'list'
Every Product record MUST include ALL of these fields. No field is optional.
export interface Product {
id: string // URL-safe slug
name: string // Chinese product name
nameEn: string // English product name
origin: string // 产地
price: number // current price in CNY
originalPrice?: number // crossed-out price
unit: string // 件/盒/箱/斤
images: string[] // at least 2 images (primary + secondary for hover swap)
category: string
tags: string[] // e.g. ["new", "sale", "organic", "gift", "limited"]
weight: string // 规格 e.g. "500g/盒"
sweetness?: number // 1–5 sweetness rating (for fruits)
description: string // full description paragraph
highlight: string // one-line selling point
isNew?: boolean
isSale?: boolean
isOrganic?: boolean
rating: number // 0–5
reviewCount: number
stock: number
}
AccountPage.tsx, AiConcierge.tsx, products.json, lib/api.ts after generating.edgeone.json with real values, no __SITE_SLUG__ placeholders.__PLACEHOLDER__ tokens must be replaced in every file.The product data file is src/data/products.ts (NOT .json). The products.json template file must be DELETED after generation.
Product data placement:
products: Product[] array using the Product interface defined in the QUALITY FLOOR sectioncategories array with { id, label, Icon } — use Lucide components for Icon| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite 5 + TypeScript 5 + Tailwind CSS 3 |
| Icons | lucide-react (NO emoji) |
| Animation | framer-motion |
| State | Zustand + persist middleware |
| Routing | react-router-dom v6 |
| Fonts | Noto Sans SC (body) + Playfair Display (heading) |
| API | Hono (Edge Functions) + Node Functions |
| Payment | Stripe (mock dev / live prod) |
| Deployment | EdgeOne Pages |
#000 or #fffaspect-square or aspect-[4/5] for product cardsfont-heading) for H1/H2, body (font-body) for everything elseAfter scaffolding, always deliver notes in this format:
## Handoff Notes — <Brand> Store
### Scaffold Status
- [x] All 6 routes implemented
- [x] Product data wired (N products)
- [x] Cart with persist middleware
- [x] Mock checkout flow (3-step: address → payment → review)
- [x] AI concierge widget
- [x] Admin dashboard scaffold
- [x] Build passes (tsc + vite build)
- [x] All images verified 200
### Production Wiring Required
- [ ] Connect Stripe live keys (set STRIPE_SECRET_KEY)
- [ ] Wire AI assistant endpoint (set AI_API_KEY, AI_MODEL)
- [ ] Replace mock order IDs with database persistence
- [ ] Set VITE_SITE_NAME, VITE_DEFAULT_CURRENCY in EdgeOne env vars
### Dev Server
npm run dev → http://localhost:5173
npm run build)/product/:id is accessible from ProductCard clicks/shop has filter + sort + grid/list toggletext-[11px], text-[13px], etc.)__PLACEHOLDER__ tokens remain in any file