--- name: ecommerce-astro description: | Full-featured e-commerce site builder with Astro 6, React, Supabase backend. Creates online stores with optional multi-vendor marketplace, Thai language support, inventory tracking, and order management. Use when: building e-commerce sites, marketplaces, online stores, or Thai e-commerce stores. --- # E-commerce Astro - E-commerce Site Builder **Category:** `fullstack` **Tech Stack:** Astro 6 + React + Supabase + Tailwind v4 --- ## ๐ŸŽฏ Purpose Create complete e-commerce websites with these core features: - โœ… **Product catalog** - Browse, filter, search products from Supabase - โœ… **Inventory management** - Stock tracking with low-stock alerts - โœ… **Order management** - Cart, checkout, order tracking, status updates - โœ… **Thai language support** - Bilingual Thai/English with i18n routing - โœ… **Review system** - Verified purchase reviews with ratings - โœ… **Responsive design** - Mobile-first with React components ### Optional Features (Enable/Disable) | Feature | Default | Description | |---------|---------|-------------| | `multi_vendor` | `false` | Multi-vendor marketplace with vendor dashboards | | `payso_payment` | `false` | PaySo Thai payment gateway (stub for now) | | `vendor_payouts` | `false` | Automated payout tracking (requires multi_vendor) | --- ## ๐Ÿš€ Quick Start ```bash # Generate e-commerce site (interactive mode) python3 skills/ecommerce-astro/scripts/create_ecommerce.py \ --name "My Store" \ --output "./my-store" # With options python3 skills/ecommerce-astro/scripts/create_ecommerce.py \ --name "My Store" \ --output "./my-store" \ --multi-vendor true \ --languages "th" ``` --- ## ๐Ÿ“‹ Pre-Flight Questions Before running the script, gather these details: 1. **Store Name:** (e.g., "Deal Plus Tech Store") 2. **Store Slug:** (e.g., "deal-plus-tech-store") 3. **Supabase Project URL:** From supabase.com dashboard 4. **Supabase Anon Key:** Public key for client-side 5. **Supabase Service Role Key:** For admin/server-side operations 6. **Multi-Vendor Mode:** Enable/disable vendor system (true/false) 7. **Languages:** Thai only (th), English only (en), or bilingual (th,en) --- ## ๐Ÿ“ Generated Project Structure (Base) ``` store-name/ โ”œโ”€โ”€ astro.config.mjs โ”œโ”€โ”€ package.json โ”œโ”€โ”€ Dockerfile โ”œโ”€โ”€ docker-compose.yml โ”œโ”€โ”€ .env.example โ”œโ”€โ”€ .gitignore โ”‚ โ”œโ”€โ”€ supabase/ โ”‚ โ””โ”€โ”€ migrations/ โ”‚ โ””โ”€โ”€ 001_initial_schema.sql โ”‚ โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ components/ โ”‚ โ”‚ โ”œโ”€โ”€ cart/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CartBadge.tsx # Floating cart button โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CartButton.tsx # Header cart icon โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CartDrawer.tsx # Slide-out cart panel โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CartItems.tsx # Cart item list โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ CartSummary.tsx # Price breakdown โ”‚ โ”‚ โ”œโ”€โ”€ checkout/ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ CheckoutForm.tsx # Checkout form โ”‚ โ”‚ โ”œโ”€โ”€ product/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ProductCard.astro # Product grid card โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ProductFilters.tsx # Category/price filters โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ProductGallery.tsx # Image gallery โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ProductVariants.tsx # Size/color variants โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ StockBadge.tsx # Inventory status โ”‚ โ”‚ โ”œโ”€โ”€ review/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ReviewList.tsx # Product reviews โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ StarRating.tsx # Star rating display โ”‚ โ”‚ โ””โ”€โ”€ layout/ โ”‚ โ”‚ โ”œโ”€โ”€ Header.astro # Site header โ”‚ โ”‚ โ””โ”€โ”€ Footer.astro # Site footer โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ layouts/ โ”‚ โ”‚ โ””โ”€โ”€ Layout.astro # Base layout โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ lib/ โ”‚ โ”‚ โ”œโ”€โ”€ supabase.ts # Supabase client (SSR-safe) โ”‚ โ”‚ โ”œโ”€โ”€ auth.ts # JWT auth helpers โ”‚ โ”‚ โ”œโ”€โ”€ utils.ts # Utility functions โ”‚ โ”‚ โ””โ”€โ”€ types.ts # TypeScript types โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ stores/ โ”‚ โ”‚ โ”œโ”€โ”€ cart.ts # Zustand cart (SSR-safe) โ”‚ โ”‚ โ”œโ”€โ”€ auth.ts # Auth state โ”‚ โ”‚ โ””โ”€โ”€ vendor.ts # Vendor state (if multi_vendor) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ pages/ โ”‚ โ”‚ โ”œโ”€โ”€ index.astro # Homepage โ”‚ โ”‚ โ”œโ”€โ”€ products/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.astro # Product listing โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [slug].astro # Product detail โ”‚ โ”‚ โ”œโ”€โ”€ cart.astro # Full cart page โ”‚ โ”‚ โ”œโ”€โ”€ checkout.astro # Checkout page โ”‚ โ”‚ โ”œโ”€โ”€ search.astro # Search page โ”‚ โ”‚ โ”œโ”€โ”€ auth/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ login.astro # Login page โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ register.astro # Register page โ”‚ โ”‚ โ”œโ”€โ”€ account/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.astro # Account dashboard โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ orders/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.astro # Order history โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [id].astro # Order detail โ”‚ โ”‚ โ”œโ”€โ”€ vendor/ # Only if multi_vendor=true โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ dashboard.astro # Vendor dashboard โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ products/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ orders.astro โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ settings.astro โ”‚ โ”‚ โ”œโ”€โ”€ admin/ # Only if multi_vendor=true โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ dashboard.astro โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ vendors.astro โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ users.astro โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ orders.astro โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ categories.astro โ”‚ โ”‚ โ””โ”€โ”€ api/ โ”‚ โ”‚ โ”œโ”€โ”€ auth/ โ”‚ โ”‚ โ”œโ”€โ”€ products/ โ”‚ โ”‚ โ”œโ”€โ”€ orders/ โ”‚ โ”‚ โ””โ”€โ”€ payments/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ i18n/ โ”‚ โ”‚ โ”œโ”€โ”€ th.json # Thai translations โ”‚ โ”‚ โ””โ”€โ”€ en.json # English translations โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ styles/ โ”‚ โ””โ”€โ”€ global.css # Global styles โ”‚ โ””โ”€โ”€ public/ โ””โ”€โ”€ images/ ``` --- ## ๐Ÿ—„๏ธ Database Schema (Supabase PostgreSQL) The migration creates these tables. Schema can be customized per project. ### Core Tables (Always Included) | Table | Purpose | |-------|---------| | `users` | Customer/admin accounts (id, email, password_hash, name, role, avatar_url) | | `categories` | Product categories (hierarchical with parent_id) | | `products` | Product catalog (id, vendor_id, category_id, name, slug, description, price, images JSONB, inventory, status, track_inventory, featured) | | `reviews` | Product reviews (product_id, user_id, rating, comment, status) | | `orders` | Customer orders (id, order_number, user_id, status, payment_status, total, shipping_address JSONB) | | `order_items` | Line items per order (order_id, product_id, quantity, unit_price) | ### Multi-Vendor Tables (Only if multi_vendor=true) | Table | Purpose | |-------|---------| | `vendor_profiles` | Store info (user_id, store_name, store_slug, store_description, status) | | `product_variants` | Size/color variants (product_id, name, sku, price, inventory) | ### Indexes ```sql -- Core indexes CREATE INDEX idx_products_category ON products(category_id); CREATE INDEX idx_products_vendor ON products(vendor_id); CREATE INDEX idx_products_slug ON products(slug); CREATE INDEX idx_products_status ON products(status); CREATE INDEX idx_orders_user ON orders(user_id); CREATE INDEX idx_orders_status ON orders(status); CREATE INDEX idx_reviews_product ON reviews(product_id); ``` ### Row Level Security (RLS) ```sql -- Products: Public read, vendor write own -- Orders: User read own, vendor read own orders -- Vendors: Admin manages vendor_profiles ``` ### Key Lessons Learned 1. **Images stored as JSONB** - Parse with `typeof images === 'string' ? JSON.parse(images) : images` 2. **Use service_role key for SSR** - Anonymous key blocked by RLS during server-side rendering 3. **Format:** `Authorization: Bearer {key}` header for Supabase REST API --- ## ๐Ÿ’ณ Payment Integration Payment is stubbed by default. To enable real payments: 1. Add PaySo credentials to `.env` 2. Create `/api/payments/create.ts` endpoint 3. Create `/api/webhooks/payso.ts` handler 4. Update `checkout.astro` to call payment API ### Stub Implementation (Default) ```typescript // lib/payso.ts (stub) export async function createPayment(order: Order) { // TODO: Implement PaySo integration console.log('Payment stub for order:', order.id); return { success: true, paymentUrl: '/checkout/success' }; } ``` --- ## ๐Ÿ” Authentication ### User Roles | Role | Permissions | |------|-------------| | `customer` | Browse, cart, checkout, orders | | `vendor` | Products, orders (requires multi_vendor=true) | | `admin` | All management (requires multi_vendor=true) | ### Auth Flow 1. Register with email/password 2. Login โ†’ JWT token stored in httpOnly cookie 3. Protected routes check session cookie 4. Role-based access for vendor/admin pages ### SSR Authentication ```typescript // Check auth in Astro pages const token = Astro.cookies.get('session')?.value; if (!token) return Astro.redirect('/login'); ``` --- ## ๐Ÿ›’ Cart & Checkout ### Cart Features - **Floating Cart Button** - Fixed position bottom-right, blue circular button - **Cart Drawer** - Slide-out panel with HeadlessUI - **Persistent** - Zustand with localStorage (SSR-safe with getStorage) - **Guest cart** - localStorage only - **Logged-in cart** - Synced to database (optional) ### Cart Store (SSR-Safe) ```typescript // stores/cart.ts export const useCartStore = create()( persist( (set, get) => ({ ... }), { name: 'cart-storage', partialize: (state) => ({ items: state.items }), getStorage: () => { if (typeof window === 'undefined') { return { getItem: () => null, setItem: () => {}, removeItem: () => {} }; } return localStorage; }, } ) ); ``` ### Checkout Flow 1. Cart Review โ†’ 2. Shipping Info โ†’ 3. Payment โ†’ 4. Confirmation --- ## ๐Ÿ“ฆ Vendor Dashboard (Only if multi_vendor=true) When `multi_vendor=true`, these pages are generated: ### Vendor Features - **Dashboard** - Stats (products, orders, sales) - **Products** - Add/edit/archive products - **Orders** - View and manage orders - **Settings** - Store profile ### Vendor Onboarding Flow 1. Register as customer 2. Apply for vendor status (`/vendors/apply`) 3. Admin approves โ†’ Vendor profile created 4. Access `/vendor/dashboard` ### Hiding Vendor Pages When `multi_vendor=false`: - No vendor registration link - No `/vendor/*` routes - No admin vendor management pages - Products belong to "store" (no vendor_id) --- ## ๐ŸŒ Internationalization ### Thai/English Support - **URL Structure:** `/th/products`, `/en/products` (if bilingual) - **Fallback:** Missing translation โ†’ English - **Default:** Thai-only if `--languages th` ### Translation Keys ```json // i18n/th.json { "common": { "addToCart": "เน€เธžเธดเนˆเธกเธฅเธ‡เธ•เธฐเธเธฃเน‰เธฒ", "checkout": "เธŠเธณเธฃเธฐเน€เธ‡เธดเธ™", "login": "เน€เธ‚เน‰เธฒเธชเธนเนˆเธฃเธฐเธšเธš" }, "product": { "outOfStock": "เธชเธดเธ™เธ„เน‰เธฒเธซเธกเธ”", "inStock": "เธกเธตเธชเธดเธ™เธ„เน‰เธฒ" } } ``` --- ## ๐Ÿ”ง Environment Variables ```bash # Supabase (Required) SUPABASE_URL=https://xxx.supabase.co SUPABASE_ANON_KEY=eyJxxx # Public key (client-side) SUPABASE_SERVICE_ROLE_KEY=eyJxxx # Admin key (server-side only!) # JWT (Required for auth) JWT_SECRET=your-super-secret-jwt-key-min-32-chars # Site SITE_URL=https://yourdomain.com SITE_NAME=My Store # PaySo (Optional - stub by default) PAYSOLO_MERCHANT_ID=your-merchant-id PAYSOLO_API_KEY=your-api-key PAYSOLO_SECRET_KEY=your-secret-key PAYSOLO_CALLBACK_URL=https://yourdomain.com/api/webhooks/payso ``` --- ## ๐Ÿณ Docker Deployment ### Dockerfile (Astro SSR Mode) ```dockerfile FROM node:20-alpine WORKDIR /app # Build-time env vars (needed for npm run build) ENV PUBLIC_SUPABASE_URL=https://xxx.supabase.co ENV PUBLIC_SUPABASE_ANON_KEY=eyJxxx ENV SUPABASE_SERVICE_ROLE_KEY=eyJxxx ENV SITE_URL=https://yourdomain.com ENV JWT_SECRET=your-32-char-min-secret-key COPY package*.json ./ RUN npm install COPY . . RUN npm run build EXPOSE 4321 ENV HOST=0.0.0.0 ENV PORT=4321 CMD ["npm", "run", "start"] ``` ### docker-compose.yml ```yaml services: web: build: . ports: - "4321:4321" env_file: - .env ``` ### Key Points 1. **Build-time env vars** - Set `ENV` before `npm run build` so Astro can access them 2. **Service role key** - Only in Dockerfile ENV, not in client code 3. **Port 4321** - Astro default, map to 80 or your preferred port --- ## ๐Ÿš€ Deployment to Easypanel ```bash # 1. Generate site locally python3 skills/ecommerce-astro/scripts/create_ecommerce.py \ --name "my-store" \ --output "./my-store" # 2. Push to Gitea cd my-store git init git add . git commit -m "Initial e-commerce site" git remote add origin https://git.moreminimore.com/user/my-store.git git push -u origin main # 3. Deploy to Easypanel # Use easypanel-deploy skill or dashboard ``` --- ## โœ… Success Criteria - [ ] Astro dev server runs without errors - [ ] Supabase tables created successfully - [ ] Products display from Supabase (images as JSONB) - [ ] Cart adds/removes items (SSR-safe Zustand) - [ ] Checkout creates order - [ ] User registration/login works - [ ] (If multi_vendor=true) Vendor dashboard accessible - [ ] (If bilingual) Language switching works - [ ] Docker build succeeds - [ ] Deploys to Easypanel --- ## ๐Ÿ“š Dependencies ```json { "astro": "^6.1.4", "@astrojs/react": "^4.2.0", "@astrojs/node": "^9.1.0", "@astrojs/sitemap": "^3.2.0", "@supabase/supabase-js": "^2.47.0", "@supabase/ssr": "^0.6.1", "@tailwindcss/vite": "^4.2.1", "tailwindcss": "^4.2.1", "zustand": "^5.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", "jose": "^6.0.0", "@headlessui/react": "^2.0.0", "lucide-react": "^0.400.0" } ``` **Note:** Astro 6 is required for CSRF protection and other security features. --- ## ๐Ÿ”— Related Skills - **thai-frontend-dev** - Base Astro setup with PDPA compliance, cookie consent - **easypanel-deploy** - Deploy to Easypanel - **gitea-sync** - Sync code to Gitea --- ## ๐Ÿ“ Example Usage ### Single Vendor Store (Thai Only) ```bash python3 skills/ecommerce-astro/scripts/create_ecommerce.py \ --name "เธฃเน‰เธฒเธ™เธ„เน‰เธฒเธญเธญเธ™เน„เธฅเธ™เนŒ" \ --slug "online-store" \ --output "./thai-store" # multi_vendor=false by default ``` ### Multi-Vendor Marketplace (Bilingual) ```bash python3 skills/ecommerce-astro/scripts/create_ecommerce.py \ --name "ThaiMart" \ --slug "thaimart" \ --multi-vendor true \ --languages "th,en" \ --output "./thaimart" ``` --- **Note:** After generation: 1. Run the Supabase migration in your dashboard 2. Update `.env` with your Supabase credentials 3. Add sample products to test --- ## ๐Ÿ”ง Troubleshooting ### SSR Error: `Cannot read properties of undefined (reading 'value')` **Cause:** Zustand persist middleware tries to access localStorage during SSR. **Fix:** Add `getStorage` to handle server-side: ```typescript getStorage: () => { if (typeof window === 'undefined') { return { getItem: () => null, setItem: () => {}, removeItem: () => {} }; } return localStorage; } ``` ### RLS Policy Blocks Read **Cause:** Anon key doesn't bypass RLS during SSR. **Fix:** Use service role key for server-side fetches: ```typescript headers: { 'apikey': import.meta.env.SUPABASE_SERVICE_ROLE_KEY, 'Authorization': `Bearer ${import.meta.env.SUPABASE_SERVICE_ROLE_KEY}` } ``` ### Images Show as `[` or Empty **Cause:** Images stored as JSONB string, not array. **Fix:** Parse before use: ```typescript const images = typeof product.images === 'string' ? JSON.parse(product.images || '[]') : (product.images || []); ``` ### URLSearchParams Error **Cause:** Spread operator with undefined in template literal. **Fix:** Use string concatenation instead: ```typescript // Bad href={`/products?${new URLSearchParams({...category && {category}})}`} // Good href={`/products?sort=${sort}`} ``` ### Cross-site POST form submissions are forbidden **Cause:** Astro 6 has built-in CSRF protection that blocks native form POST from different origins. **Fix:** Use client-side fetch instead of native form submission: ```typescript // In your .astro page
```