Files
opencode-skill/skills/ecommerce-astro/SKILL.md
Kunthawat Greethong b26c8199a5 Update skills: add website-creator, mql-developer, ecommerce-astro
Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
2026-04-16 17:40:27 +07:00

17 KiB

name, description
name description
ecommerce-astro 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

# 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

-- 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)

-- 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)

// 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

// 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)

// stores/cart.ts
export const useCartStore = create<CartStore>()(
  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

// i18n/th.json
{
  "common": {
    "addToCart": "เพิ่มลงตะกร้า",
    "checkout": "ชำระเงิน",
    "login": "เข้าสู่ระบบ"
  },
  "product": {
    "outOfStock": "สินค้าหมด",
    "inStock": "มีสินค้า"
  }
}

🔧 Environment Variables

# 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)

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

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

# 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

{
  "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.


  • 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)

python3 skills/ecommerce-astro/scripts/create_ecommerce.py \
  --name "ร้านค้าออนไลน์" \
  --slug "online-store" \
  --output "./thai-store"
# multi_vendor=false by default

Multi-Vendor Marketplace (Bilingual)

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:

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:

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:

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:

// 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:

// In your .astro page
<form id="registerForm">
  <input name="email" id="emailInput" />
  <input name="password" id="passwordInput" />
  <button type="submit">Register</button>
</form>

<script>
document.getElementById('registerForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const email = document.getElementById('emailInput').value;
  const password = document.getElementById('passwordInput').value;
  
  const res = await fetch('/api/auth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password })
  });
  
  if (res.ok) {
    window.location.href = '/';
  }
});
</script>