docs: add consolidated dev URL reference and migrate /shop to /storefront
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

- Add Development URL Quick Reference section to url-routing overview
  with all login URLs, entry points, and full examples
- Replace /shop/ path segments with /storefront/ across 50 docs files
- Update file references: shop_pages.py → storefront_pages.py,
  templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/
- Preserve domain references (orion.shop) and /store/ staff dashboard paths
- Archive docs left unchanged (historical)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 13:23:44 +01:00
parent 3df75e2e78
commit d648c921b7
50 changed files with 1104 additions and 1049 deletions

View File

@@ -7,12 +7,12 @@
│ Browser │ │ Browser │
│ │ │ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Admin Area │ │ Store Area │ │ Shop Area │ │ │ │ Admin Area │ │ Store Area │ │ Storefront Area │ │
│ │ /admin/* │ │ /store/* │ │ /shop/* │ │ │ │ /admin/* │ │ /store/* │ │ /storefront/* │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ 🍪 admin_token │ │ 🍪 store_token │ │ 🍪 customer_ │ │ │ │ 🍪 admin_token │ │ 🍪 store_token │ │ 🍪 customer_ │ │
│ │ Path: /admin │ │ Path: /store │ │ token │ │ │ │ Path: /admin │ │ Path: /store │ │ token │ │
│ │ │ │ │ │ Path: /shop │ │ │ │ │ │ │ │ Path: /storefront │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │ │ │ │ │ │ │ │ │
│ ├──────────────────────┼─────────────────────┤ │ │ ├──────────────────────┼─────────────────────┤ │
@@ -22,8 +22,8 @@
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Admin Backend │ │ Store Backend │ │ Shop Backend │ │ Admin Backend │ │ Store Backend │ │ Storefront Backend │
│ /admin/* │ │ /store/* │ │ /shop/* │ │ /admin/* │ │ /store/* │ │ /storefront/* │
│ │ │ │ │ │ │ │ │ │ │ │
│ ✅ admin_token │ │ ✅ store_token │ │ ✅ customer_ │ │ ✅ admin_token │ │ ✅ store_token │ │ ✅ customer_ │
│ ❌ store_token │ │ ❌ admin_token │ │ token │ │ ❌ store_token │ │ ❌ admin_token │ │ token │
@@ -128,14 +128,14 @@
└────────────────────────────────────┘ └────────────────────────────────────┘
``` ```
## Login Flow - Customer (Shop) ## Login Flow - Customer (Storefront)
``` ```
┌──────────┐ ┌──────────┐
│ Browser │ │ Browser │
└──────────┘ └──────────┘
│ POST /api/v1/shop/auth/login │ POST /api/v1/storefront/auth/login
│ { email, password } │ { email, password }
┌─────────────────────────┐ ┌─────────────────────────┐
@@ -146,20 +146,20 @@
│ 3. Generate JWT │ │ 3. Generate JWT │
└─────────────────────────┘ └─────────────────────────┘
│ Set-Cookie: customer_token=<JWT>; Path=/shop; HttpOnly; SameSite=Lax │ Set-Cookie: customer_token=<JWT>; Path=/storefront; HttpOnly; SameSite=Lax
│ Response: { access_token, user } │ Response: { access_token, user }
┌──────────┐ ┌──────────┐
│ Browser │──────────────────────────────────────┐ │ Browser │──────────────────────────────────────┐
│ │ │ │ │ │
│ 🍪 customer_token (Path=/shop) │ │ 🍪 customer_token (Path=/storefront) │
│ 💾 localStorage.access_token │ │ 💾 localStorage.access_token │
└──────────┘ │ └──────────┘ │
│ │ │ │
├── Navigate to /shop/account/dashboard ─────┤ ├── Navigate to /storefront/account/dashboard ─────┤
│ (Cookie sent automatically) │ │ (Cookie sent automatically) │
│ │ │ │
└── API call to /api/v1/shop/orders ─────────┤ └── API call to /api/v1/storefront/orders ─────────┤
(Authorization: Bearer <token>) │ (Authorization: Bearer <token>) │
┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
@@ -189,7 +189,7 @@
┌───────────────────────────┼───────────────────────────┐ ┌───────────────────────────┼───────────────────────────┐
│ │ │ │ │ │ │ │ │ │
Starts with Starts with Starts with Starts with Starts with Starts with Starts with Starts with Starts with Starts with
/admin/* /store/* /shop/* /api/* (public) /admin/* /store/* /storefront/* /api/* (public)
│ │ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐ ┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐
@@ -307,17 +307,17 @@ Customer trying to access admin route:
``` ```
``` ```
Customer cookie sent to shop route (allowed): Customer cookie sent to storefront route (allowed):
┌──────────────────────────────────────────┐ ┌──────────────────────────────────────────┐
│ Cookie: customer_token=<JWT> (Path=/shop)│ │ Cookie: customer_token=<JWT> (Path=/storefront)│
│ Request: GET /shop/account/orders │ │ Request: GET /storefront/account/orders │
└──────────────────────────────────────────┘ └──────────────────────────────────────────┘
Browser checks cookie path Browser checks cookie path
Path /shop matches /shop Path /storefront matches /storefront
✅ Cookie SENT automatically ✅ Cookie SENT automatically
@@ -342,7 +342,7 @@ LOGIN
├── Server sets cookie: ├── Server sets cookie:
│ • Name: admin_token, store_token, or customer_token │ • Name: admin_token, store_token, or customer_token
│ • Value: JWT │ • Value: JWT
│ • Path: /admin, /store, or /shop (context-specific) │ • Path: /admin, /store, or /storefront (context-specific)
│ • HttpOnly: true │ • HttpOnly: true
│ • Secure: true (production) │ • Secure: true (production)
│ • SameSite: Lax │ • SameSite: Lax
@@ -386,5 +386,5 @@ LOGOUT
2. **Role Checking** = Strict role validation at each boundary (admin, store, customer) 2. **Role Checking** = Strict role validation at each boundary (admin, store, customer)
3. **Dual Auth Support** = Cookies for HTML pages, headers for API endpoints 3. **Dual Auth Support** = Cookies for HTML pages, headers for API endpoints
4. **Security First** = HttpOnly, Secure, SameSite protection on all cookies 4. **Security First** = HttpOnly, Secure, SameSite protection on all cookies
5. **Clear Boundaries** = Each context (admin/store/shop) is completely isolated 5. **Clear Boundaries** = Each context (admin/store/storefront) is completely isolated
6. **Three User Types** = Admins manage platform, stores manage stores, customers shop 6. **Three User Types** = Admins manage platform, stores manage stores, customers shop

View File

@@ -27,7 +27,7 @@ def store_page(user: User = Depends(get_current_store_from_cookie_or_header)):
pass pass
# Customer page - NOTE: Returns Customer, not User! # Customer page - NOTE: Returns Customer, not User!
@router.get("/shop/account/dashboard") @router.get("/storefront/account/dashboard")
def customer_page(customer: Customer = Depends(get_current_customer_from_cookie_or_header)): def customer_page(customer: Customer = Depends(get_current_customer_from_cookie_or_header)):
pass # customer.id, customer.email, customer.store_id pass # customer.id, customer.email, customer.store_id
``` ```
@@ -53,7 +53,7 @@ def store_api(user: User = Depends(get_current_store_api)):
pass # user.token_store_id for store context pass # user.token_store_id for store context
# Customer API - NOTE: Returns Customer, not User! # Customer API - NOTE: Returns Customer, not User!
@router.post("/api/v1/shop/orders") @router.post("/api/v1/storefront/orders")
def customer_api(request: Request, customer: Customer = Depends(get_current_customer_api)): def customer_api(request: Request, customer: Customer = Depends(get_current_customer_api)):
pass # customer.id, request.state.store validated to match pass # customer.id, request.state.store validated to match
``` ```
@@ -66,7 +66,7 @@ def customer_api(request: Request, customer: Customer = Depends(get_current_cust
|---------|--------|------|------|--------| |---------|--------|------|------|--------|
| **Admin** | `admin_token` | `/admin` | `admin` | `/admin/*` | | **Admin** | `admin_token` | `/admin` | `admin` | `/admin/*` |
| **Store** | `store_token` | `/store` | `store` | `/store/*` | | **Store** | `store_token` | `/store` | `store` | `/store/*` |
| **Customer** | `customer_token` | `/shop` | `customer` | `/shop/account/*` | | **Customer** | `customer_token` | `/storefront` | `customer` | `/storefront/account/*` |
--- ---
@@ -208,8 +208,8 @@ console.log(parseJwt(localStorage.getItem('token')));
3.**Admins** cannot access store/customer portals 3.**Admins** cannot access store/customer portals
4.**Stores** cannot access admin/customer portals 4.**Stores** cannot access admin/customer portals
5.**Customers** cannot access admin/store portals 5.**Customers** cannot access admin/store portals
6.**Public shop** (`/shop/products`) needs no auth 6.**Public storefront** (`/storefront/products`) needs no auth
7.**Customer accounts** (`/shop/account/*`) need auth 7.**Customer accounts** (`/storefront/account/*`) need auth
--- ---

View File

@@ -25,8 +25,8 @@
The Orion platform uses a **context-based authentication system** with three isolated security domains: The Orion platform uses a **context-based authentication system** with three isolated security domains:
- **Admin Portal** - Platform administration and management - **Admin Portal** - Platform administration and management
- **Store Portal** - Multi-tenant shop management - **Store Portal** - Multi-tenant storefront management
- **Customer Shop** - Public storefront and customer accounts - **Customer Storefront** - Public storefront and customer accounts
Each context uses **dual authentication** supporting both cookie-based (for HTML pages) and header-based (for API calls) authentication with complete isolation between contexts. Each context uses **dual authentication** supporting both cookie-based (for HTML pages) and header-based (for API calls) authentication with complete isolation between contexts.
@@ -92,12 +92,12 @@ Each authentication context uses a separate cookie with path restrictions:
|----------|------------------|-------------|--------------| |----------|------------------|-------------|--------------|
| Admin | `admin_token` | `/admin` | Admin routes only | | Admin | `admin_token` | `/admin` | Admin routes only |
| Store | `store_token` | `/store` | Store routes only | | Store | `store_token` | `/store` | Store routes only |
| Customer | `customer_token` | `/shop` | Shop routes only | | Customer | `customer_token` | `/storefront` | Storefront routes only |
**Browser Behavior:** **Browser Behavior:**
- When requesting `/admin/*`, browser sends `admin_token` cookie only - When requesting `/admin/*`, browser sends `admin_token` cookie only
- When requesting `/store/*`, browser sends `store_token` cookie only - When requesting `/store/*`, browser sends `store_token` cookie only
- When requesting `/shop/*`, browser sends `customer_token` cookie only - When requesting `/storefront/*`, browser sends `customer_token` cookie only
This prevents cookie leakage between contexts. This prevents cookie leakage between contexts.
@@ -157,7 +157,7 @@ Set-Cookie: admin_token=<JWT>; Path=/admin; HttpOnly; Secure; SameSite=Lax
**Role:** `store` **Role:** `store`
**Cookie:** `store_token` (path=/store) **Cookie:** `store_token` (path=/store)
**Purpose:** Store shop management, product catalog, orders, team management. **Purpose:** Store management, product catalog, orders, team management.
**Access Control:** **Access Control:**
- ❌ Admin users blocked (admins use admin portal for store management) - ❌ Admin users blocked (admins use admin portal for store management)
@@ -205,24 +205,24 @@ Set-Cookie: store_token=<JWT>; Path=/store; HttpOnly; Secure; SameSite=Lax
### 3. Customer Context ### 3. Customer Context
**Routes:** `/shop/account/*` (authenticated), `/shop/*` (public) **Routes:** `/storefront/account/*` (authenticated), `/storefront/*` (public)
**Role:** `customer` **Role:** `customer`
**Cookie:** `customer_token` (path=/shop) **Cookie:** `customer_token` (path=/storefront)
**Purpose:** Product browsing (public), customer accounts, orders, profile management. **Purpose:** Product browsing (public), customer accounts, orders, profile management.
**Important - URL Pattern Context:** **Important - URL Pattern Context:**
The `/shop/*` routes work differently depending on deployment mode: The `/storefront/*` routes work differently depending on deployment mode:
- **Subdomain Mode** (Production): `https://store.platform.com/shop/products` - **Subdomain Mode** (Production): `https://store.platform.com/storefront/products`
- **Custom Domain** (Production): `https://customdomain.com/shop/products` - **Custom Domain** (Production): `https://customdomain.com/storefront/products`
- **Path-Based** (Development): `http://localhost:8000/stores/{store_code}/shop/products` - **Path-Based** (Development): `http://localhost:8000/storefront/{store_code}/products`
In path-based development mode, the full URL includes the store code (e.g., `/stores/acme/shop/products`), but the routes are still defined as `/shop/*` internally. See [URL Routing Guide](../architecture/url-routing/overview.md) for details. In path-based development mode, the full URL includes the store code (e.g., `/storefront/acme/products`), but the routes are still defined as `/storefront/*` internally. See [URL Routing Guide](../architecture/url-routing/overview.md) for details.
**Access Control:** **Access Control:**
- **Public Routes** (`/shop/products`, `/shop/cart`, etc.): - **Public Routes** (`/storefront/products`, `/storefront/cart`, etc.):
- ✅ Anyone can access (no authentication) - ✅ Anyone can access (no authentication)
- **Account Routes** (`/shop/account/*`): - **Account Routes** (`/storefront/account/*`):
- ❌ Admin users blocked - ❌ Admin users blocked
- ❌ Store users blocked - ❌ Store users blocked
- ✅ Customer users only - ✅ Customer users only
@@ -256,7 +256,7 @@ curl -X POST http://localhost:8000/api/v1/platform/stores/1/customers/login \
Additionally, sets cookie: Additionally, sets cookie:
``` ```
Set-Cookie: customer_token=<JWT>; Path=/shop; HttpOnly; Secure; SameSite=Lax Set-Cookie: customer_token=<JWT>; Path=/storefront; HttpOnly; Secure; SameSite=Lax
``` ```
--- ---
@@ -323,13 +323,13 @@ async def store_dashboard(
}) })
# Customer account page # Customer account page
@router.get("/shop/account/dashboard", response_class=HTMLResponse) @router.get("/storefront/account/dashboard", response_class=HTMLResponse)
async def customer_dashboard( async def customer_dashboard(
request: Request, request: Request,
current_user: User = Depends(get_current_customer_from_cookie_or_header), current_user: User = Depends(get_current_customer_from_cookie_or_header),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
return templates.TemplateResponse("shop/account/dashboard.html", { return templates.TemplateResponse("storefront/account/dashboard.html", {
"request": request, "request": request,
"user": current_user "user": current_user
}) })
@@ -375,7 +375,7 @@ def create_product(
return {"message": "Product created"} return {"message": "Product created"}
# Customer API # Customer API
@router.post("/api/v1/shop/orders") @router.post("/api/v1/storefront/orders")
def create_order( def create_order(
order_data: OrderCreate, order_data: OrderCreate,
current_user: User = Depends(get_current_customer_api), current_user: User = Depends(get_current_customer_api),
@@ -389,10 +389,10 @@ def create_order(
Simply don't use any authentication dependency: Simply don't use any authentication dependency:
```python ```python
@router.get("/shop/products") @router.get("/storefront/products")
async def public_products(request: Request): async def public_products(request: Request):
# No authentication required # No authentication required
return templates.TemplateResponse("shop/products.html", { return templates.TemplateResponse("storefront/products.html", {
"request": request "request": request
}) })
``` ```
@@ -498,7 +498,7 @@ def get_orders(
**Security Features:** **Security Features:**
- **Token type validation:** Only accepts tokens with `type: "customer"` - admin and store tokens are rejected - **Token type validation:** Only accepts tokens with `type: "customer"` - admin and store tokens are rejected
- **Store validation:** Validates that `token.store_id` matches `request.state.store.id` (URL-based store) - **Store validation:** Validates that `token.store_id` matches `request.state.store.id` (URL-based store)
- Prevents cross-store token reuse (customer from Store A cannot use token on Store B's shop) - Prevents cross-store token reuse (customer from Store A cannot use token on Store B's storefront)
**Usage:** **Usage:**
```python ```python
@@ -637,23 +637,23 @@ async def store_login_page(
```python ```python
from models.database.customer import Customer from models.database.customer import Customer
# Shop login page redirect # Storefront login page redirect
@router.get("/shop/account/login") @router.get("/storefront/account/login")
async def customer_login_page( async def storefront_login_page(
request: Request, request: Request,
customer: Customer | None = Depends(get_current_customer_optional) customer: Customer | None = Depends(get_current_customer_optional)
): ):
if customer: if customer:
# Customer already logged in, redirect to account page # Customer already logged in, redirect to account page
return RedirectResponse(url="/shop/account", status_code=302) return RedirectResponse(url="/storefront/account", status_code=302)
# Not logged in, show login form # Not logged in, show login form
return templates.TemplateResponse("shop/login.html", {"request": request}) return templates.TemplateResponse("storefront/login.html", {"request": request})
``` ```
**Use Cases:** **Use Cases:**
- Login pages (redirect if already authenticated) - Login pages (redirect if already authenticated)
- Public shop pages with conditional customer content (e.g., "My Orders" link) - Public storefront pages with conditional customer content (e.g., "My Orders" link)
- Root redirects based on authentication status - Root redirects based on authentication status
--- ---
@@ -744,7 +744,7 @@ All login endpoints return:
Additionally, the response sets an HTTP-only cookie: Additionally, the response sets an HTTP-only cookie:
- Admin: `admin_token` (path=/admin) - Admin: `admin_token` (path=/admin)
- Store: `store_token` (path=/store) - Store: `store_token` (path=/store)
- Customer: `customer_token` (path=/shop) - Customer: `customer_token` (path=/storefront)
--- ---
@@ -752,7 +752,7 @@ Additionally, the response sets an HTTP-only cookie:
### Role-Based Access Control Matrix ### Role-Based Access Control Matrix
| User Role | Admin Portal | Store Portal | Shop Catalog | Customer Account | | User Role | Admin Portal | Store Portal | Storefront Catalog | Customer Account |
|-----------|--------------|---------------|--------------|------------------| |-----------|--------------|---------------|--------------|------------------|
| Admin | ✅ Full | ❌ Blocked | ✅ View | ❌ Blocked | | Admin | ✅ Full | ❌ Blocked | ✅ View | ❌ Blocked |
| Store | ❌ Blocked | ✅ Full | ✅ View | ❌ Blocked | | Store | ❌ Blocked | ✅ Full | ✅ View | ❌ Blocked |
@@ -863,7 +863,7 @@ Custom exceptions (such as those raised for missing claims) are preserved with t
4. **Test navigation:** 4. **Test navigation:**
- Navigate to `/admin/dashboard` - Should work ✅ - Navigate to `/admin/dashboard` - Should work ✅
- Navigate to `/store/TESTSTORE/dashboard` - Should fail (cookie not sent) ❌ - Navigate to `/store/TESTSTORE/dashboard` - Should fail (cookie not sent) ❌
- Navigate to `/shop/account/dashboard` - Should fail (cookie not sent) ❌ - Navigate to `/storefront/account/dashboard` - Should fail (cookie not sent) ❌
5. **Logout:** 5. **Logout:**
``` ```
@@ -886,23 +886,23 @@ Custom exceptions (such as those raised for missing claims) are preserved with t
4. **Test navigation:** 4. **Test navigation:**
- Navigate to `/store/{STORE_CODE}/dashboard` - Should work ✅ - Navigate to `/store/{STORE_CODE}/dashboard` - Should work ✅
- Navigate to `/admin/dashboard` - Should fail ❌ - Navigate to `/admin/dashboard` - Should fail ❌
- Navigate to `/shop/account/dashboard` - Should fail ❌ - Navigate to `/storefront/account/dashboard` - Should fail ❌
#### Test Customer Authentication #### Test Customer Authentication
1. **Navigate to customer login:** 1. **Navigate to customer login:**
``` ```
http://localhost:8000/shop/account/login http://localhost:8000/storefront/account/login
``` ```
2. **Login with customer credentials** 2. **Login with customer credentials**
3. **Verify cookie in DevTools:** 3. **Verify cookie in DevTools:**
- Look for `customer_token` cookie - Look for `customer_token` cookie
- Verify `Path` is `/shop` - Verify `Path` is `/storefront`
4. **Test navigation:** 4. **Test navigation:**
- Navigate to `/shop/account/dashboard` - Should work ✅ - Navigate to `/storefront/account/dashboard` - Should work ✅
- Navigate to `/admin/dashboard` - Should fail ❌ - Navigate to `/admin/dashboard` - Should fail ❌
- Navigate to `/store/{CODE}/dashboard` - Should fail ❌ - Navigate to `/store/{CODE}/dashboard` - Should fail ❌
@@ -955,7 +955,7 @@ curl -X POST http://localhost:8000/api/v1/platform/stores/1/customers/login \
-d '{"username":"customer","password":"customer123"}' -d '{"username":"customer","password":"customer123"}'
# Test authenticated endpoint with token # Test authenticated endpoint with token
curl http://localhost:8000/api/v1/shop/orders \ curl http://localhost:8000/api/v1/storefront/orders \
-H "Authorization: Bearer <customer_access_token>" -H "Authorization: Bearer <customer_access_token>"
# Test cross-context blocking # Test cross-context blocking
@@ -1101,8 +1101,8 @@ Admins should not log into store portal as this violates security boundaries.
**Symptom:** Customer trying to access management interfaces **Symptom:** Customer trying to access management interfaces
**Explanation:** Customers only have access to: **Explanation:** Customers only have access to:
- Public shop routes: `/shop/products`, etc. - Public storefront routes: `/storefront/products`, etc.
- Their account: `/shop/account/*` - Their account: `/storefront/account/*`
Admin and store portals are not accessible to customers. Admin and store portals are not accessible to customers.
@@ -1169,7 +1169,7 @@ Look for:
2. **Don't mix authentication contexts:** 2. **Don't mix authentication contexts:**
- Admin users should use admin portal - Admin users should use admin portal
- Store users should use store portal - Store users should use store portal
- Customers should use shop - Customers should use storefront
3. **Always check user.is_active:** 3. **Always check user.is_active:**
```python ```python

View File

@@ -167,7 +167,7 @@ Returns JSON error responses suitable for API clients.
### Admin/Store Dashboard (`/admin/*`, `/store/*`) ### Admin/Store Dashboard (`/admin/*`, `/store/*`)
Returns JSON errors or redirects to error pages based on accept headers. Returns JSON errors or redirects to error pages based on accept headers.
### Shop Requests (`/shop/*`) ### Storefront Requests (`/storefront/*`)
Returns themed error pages matching the store's shop design. Returns themed error pages matching the store's shop design.
## Logging ## Logging

View File

@@ -335,5 +335,5 @@ Customer → Store Portal Customer → Own Account
Cookie Isolation: Cookie Isolation:
admin_token (path=/admin) ← Only sent to /admin/* admin_token (path=/admin) ← Only sent to /admin/*
store_token (path=/store) ← Only sent to /store/* store_token (path=/store) ← Only sent to /store/*
customer_token (path=/shop) ← Only sent to /shop/* customer_token (path=/storefront) ← Only sent to /storefront/*
``` ```

View File

@@ -86,7 +86,7 @@ The application operates in three isolated contexts:
|---------|--------|----------------|------------| |---------|--------|----------------|------------|
| **Admin** | `/admin/*` | `admin_token` cookie | `super_admin`, `platform_admin` | | **Admin** | `/admin/*` | `admin_token` cookie | `super_admin`, `platform_admin` |
| **Store** | `/store/*` | `store_token` cookie | `merchant_owner`, `store_member` | | **Store** | `/store/*` | `store_token` cookie | `merchant_owner`, `store_member` |
| **Shop** | `/shop/account/*` | `customer_token` cookie | Customers | | **Storefront** | `/storefront/account/*` | `customer_token` cookie | Customers |
**Important:** These contexts are security boundaries. Admin users cannot access store routes, store users cannot access admin routes, and customers are entirely separate. **Important:** These contexts are security boundaries. Admin users cannot access store routes, store users cannot access admin routes, and customers are entirely separate.
@@ -802,7 +802,7 @@ role = Role(
┌─────────────┐ ┌─────────────┐
│ Client │ │ Client │
│ │ │ │
│ 🍪 customer_token (path=/shop) │ │ 🍪 customer_token (path=/storefront) │
│ 💾 localStorage.token │ │ 💾 localStorage.token │
└─────────────┘ └─────────────┘
``` ```
@@ -838,7 +838,7 @@ response.set_cookie(
response.set_cookie( response.set_cookie(
key="customer_token", key="customer_token",
value=jwt_token, value=jwt_token,
path="/shop", # Only sent to /shop/* routes path="/storefront", # Only sent to /storefront/* routes
httponly=True, httponly=True,
secure=True, secure=True,
samesite="lax" samesite="lax"

View File

@@ -92,7 +92,7 @@ Get paginated list of products for current store.
```http ```http
GET /api/v1/storefront/products?skip=0&limit=20&is_featured=true GET /api/v1/storefront/products?skip=0&limit=20&is_featured=true
Referer: http://localhost:8000/stores/orion/shop/products Referer: http://localhost:8000/storefront/orion/products
``` ```
**Response (200 OK):** **Response (200 OK):**
@@ -142,7 +142,7 @@ Get detailed information for a specific product.
```http ```http
GET /api/v1/storefront/products/1 GET /api/v1/storefront/products/1
Referer: http://localhost:8000/stores/orion/shop/products Referer: http://localhost:8000/storefront/orion/products
``` ```
**Response (200 OK):** **Response (200 OK):**
@@ -200,7 +200,7 @@ Retrieve cart contents for a session.
```http ```http
GET /api/v1/storefront/cart/session-abc-123 GET /api/v1/storefront/cart/session-abc-123
Referer: http://localhost:8000/stores/orion/shop/cart Referer: http://localhost:8000/storefront/orion/cart
``` ```
**Response (200 OK):** **Response (200 OK):**

View File

@@ -6,9 +6,9 @@
## Executive Summary ## Executive Summary
The platform currently has **two parallel API structures** for shop/customer-facing endpoints: The platform currently has **two parallel API structures** for storefront/customer-facing endpoints:
1. **Original:** `/api/v1/platform/stores/{store_id}/*` 1. **Original:** `/api/v1/platform/stores/{store_id}/*`
2. **New:** `/api/v1/shop/*` 2. **New:** `/api/v1/storefront/*`
This divergence creates confusion, maintenance overhead, and potential bugs. This document analyzes the situation and proposes a consolidation strategy. This divergence creates confusion, maintenance overhead, and potential bugs. This document analyzes the situation and proposes a consolidation strategy.
@@ -47,26 +47,26 @@ POST /api/v1/platform/stores/auth/register → Customer registration
--- ---
### 2. New Architecture (`/api/v1/shop/`) ### 2. New Architecture (`/api/v1/storefront/`)
**Location:** `app/api/v1/shop/` **Location:** `app/api/v1/storefront/`
**Endpoints:** **Endpoints:**
``` ```
GET /api/v1/shop/content-pages/navigation → CMS navigation pages GET /api/v1/storefront/content-pages/navigation → CMS navigation pages
GET /api/v1/shop/content-pages/{slug} → CMS page content GET /api/v1/storefront/content-pages/{slug} → CMS page content
``` ```
**Characteristics:** **Characteristics:**
-**Store-agnostic URLs:** Clean paths without store_id -**Store-agnostic URLs:** Clean paths without store_id
-**Middleware-driven:** Relies on `StoreContextMiddleware` to inject store -**Middleware-driven:** Relies on `StoreContextMiddleware` to inject store
-**Simpler URLs:** `/api/v1/shop/products` vs `/api/v1/platform/stores/123/products` -**Simpler URLs:** `/api/v1/storefront/products` vs `/api/v1/platform/stores/123/products`
-**Incomplete:** Only CMS endpoints implemented -**Incomplete:** Only CMS endpoints implemented
-**Divergent:** Not consistent with existing public API -**Divergent:** Not consistent with existing public API
**Current Usage:** **Current Usage:**
- CMS content pages only - CMS content pages only
- Called from shop templates (e.g., `shop/products.html`, `shop/home.html`) - Called from storefront templates (e.g., `storefront/products.html`, `storefront/home.html`)
--- ---
@@ -77,7 +77,7 @@ GET /api/v1/shop/content-pages/{slug} → CMS page content
```javascript ```javascript
// ❌ INCONSISTENT - Two different patterns for same context // ❌ INCONSISTENT - Two different patterns for same context
// CMS pages use new pattern // CMS pages use new pattern
fetch('/api/v1/shop/content-pages/about') fetch('/api/v1/storefront/content-pages/about')
// Products use old pattern // Products use old pattern
fetch('/api/v1/platform/stores/123/products') fetch('/api/v1/platform/stores/123/products')
@@ -86,7 +86,7 @@ fetch('/api/v1/platform/stores/123/products')
### Confusion ### Confusion
Developers must remember: Developers must remember:
- "Is this endpoint under `/shop` or `/public/stores`?" - "Is this endpoint under `/storefront` or `/public/stores`?"
- "Do I need to pass store_id or is it from middleware?" - "Do I need to pass store_id or is it from middleware?"
- "Which authentication endpoints do I use?" - "Which authentication endpoints do I use?"
@@ -102,7 +102,7 @@ Developers must remember:
**Current Issue:** CMS pages not loading at `/stores/orion/about` **Current Issue:** CMS pages not loading at `/stores/orion/about`
**Root Cause:** **Root Cause:**
- CMS API exists at `/api/v1/shop/content-pages/{slug}` - CMS API exists at `/api/v1/storefront/content-pages/{slug}`
- No corresponding HTML route handler in `store_pages.py` - No corresponding HTML route handler in `store_pages.py`
- JavaScript might be calling wrong endpoint - JavaScript might be calling wrong endpoint
@@ -110,13 +110,13 @@ Developers must remember:
## Options Analysis ## Options Analysis
### Option 1: Move Everything to `/api/v1/shop/*` (Middleware-Driven) ### Option 1: Move Everything to `/api/v1/storefront/*` (Middleware-Driven)
**Approach:** Consolidate all customer-facing endpoints under `/api/v1/shop/*` **Approach:** Consolidate all customer-facing endpoints under `/api/v1/storefront/*`
**Proposed Structure:** **Proposed Structure:**
``` ```
/api/v1/shop/ /api/v1/storefront/
├── auth/ ├── auth/
│ ├── POST /login → Customer login │ ├── POST /login → Customer login
│ ├── POST /register → Customer registration │ ├── POST /register → Customer registration
@@ -143,7 +143,7 @@ Developers must remember:
**Implementation:** **Implementation:**
- Store extracted by `StoreContextMiddleware` from request - Store extracted by `StoreContextMiddleware` from request
- All endpoints use `request.state.store` instead of path parameter - All endpoints use `request.state.store` instead of path parameter
- URLs are cleaner: `/api/v1/shop/products` instead of `/api/v1/platform/stores/123/products` - URLs are cleaner: `/api/v1/storefront/products` instead of `/api/v1/platform/stores/123/products`
**Pros:** **Pros:**
- ✅ Clean, consistent API structure - ✅ Clean, consistent API structure
@@ -162,17 +162,17 @@ Developers must remember:
--- ---
### Option 2: Keep `/api/v1/platform/stores/*` and Deprecate `/api/v1/shop/*` ### Option 2: Keep `/api/v1/platform/stores/*` and Deprecate `/api/v1/storefront/*`
**Approach:** Move CMS endpoints to `/api/v1/platform/stores/{store_id}/content-pages/*` **Approach:** Move CMS endpoints to `/api/v1/platform/stores/{store_id}/content-pages/*`
**Proposed Changes:** **Proposed Changes:**
``` ```
# Move CMS endpoints # Move CMS endpoints
FROM: /api/v1/shop/content-pages/navigation FROM: /api/v1/storefront/content-pages/navigation
TO: /api/v1/platform/stores/{store_id}/content-pages/navigation TO: /api/v1/platform/stores/{store_id}/content-pages/navigation
FROM: /api/v1/shop/content-pages/{slug} FROM: /api/v1/storefront/content-pages/{slug}
TO: /api/v1/platform/stores/{store_id}/content-pages/{slug} TO: /api/v1/platform/stores/{store_id}/content-pages/{slug}
``` ```
@@ -228,7 +228,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)):
## Recommendation ## Recommendation
### **OPTION 1: Consolidate to `/api/v1/shop/*` (Middleware-Driven)** ### **OPTION 1: Consolidate to `/api/v1/storefront/*` (Middleware-Driven)**
**Rationale:** **Rationale:**
@@ -237,7 +237,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)):
2. **User Experience**: Cleaner URLs are easier for frontend developers: 2. **User Experience**: Cleaner URLs are easier for frontend developers:
```javascript ```javascript
// ✅ GOOD // ✅ GOOD
fetch('/api/v1/shop/products') fetch('/api/v1/storefront/products')
// ❌ BAD // ❌ BAD
fetch('/api/v1/platform/stores/123/products') fetch('/api/v1/platform/stores/123/products')
@@ -245,7 +245,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)):
3. **Multi-Tenant Best Practice**: Store context should be implicit (from domain/path), not explicit in every API call. 3. **Multi-Tenant Best Practice**: Store context should be implicit (from domain/path), not explicit in every API call.
4. **Consistency**: All shop endpoints follow same pattern - no mixing `/shop` and `/public/stores`. 4. **Consistency**: All storefront endpoints follow same pattern - no mixing `/storefront` and `/public/stores`.
5. **Future-Proof**: Easier to add new shop features without worrying about store_id paths. 5. **Future-Proof**: Easier to add new shop features without worrying about store_id paths.
@@ -258,7 +258,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)):
**Day 1-2: Move Products** **Day 1-2: Move Products**
```bash ```bash
# Copy and adapt # Copy and adapt
app/api/v1/platform/stores/products.py → app/api/v1/shop/products.py app/api/v1/platform/stores/products.py → app/api/v1/storefront/products.py
# Changes: # Changes:
- Remove store_id path parameter - Remove store_id path parameter
@@ -268,24 +268,24 @@ app/api/v1/platform/stores/products.py → app/api/v1/shop/products.py
**Day 3: Move Cart** **Day 3: Move Cart**
```bash ```bash
app/api/v1/platform/stores/cart.py → app/api/v1/shop/cart.py app/api/v1/platform/stores/cart.py → app/api/v1/storefront/cart.py
``` ```
**Day 4: Move Orders** **Day 4: Move Orders**
```bash ```bash
app/api/v1/platform/stores/orders.py → app/api/v1/shop/orders.py app/api/v1/platform/stores/orders.py → app/api/v1/storefront/orders.py
``` ```
**Day 5: Move Auth** **Day 5: Move Auth**
```bash ```bash
app/api/v1/platform/stores/auth.py → app/api/v1/shop/auth.py app/api/v1/platform/stores/auth.py → app/api/v1/storefront/auth.py
``` ```
### Phase 2: Update Frontend (Week 1) ### Phase 2: Update Frontend (Week 1)
**Templates:** **Templates:**
- Update all `fetch()` calls in shop templates - Update all `fetch()` calls in shop templates
- Change from `/api/v1/platform/stores/${storeId}/...` to `/api/v1/shop/...` - Change from `/api/v1/platform/stores/${storeId}/...` to `/api/v1/storefront/...`
**JavaScript:** **JavaScript:**
- Update any shop-related API client code - Update any shop-related API client code
@@ -335,10 +335,10 @@ const storeId = 123;
fetch(`/api/v1/platform/stores/${storeId}/products`) fetch(`/api/v1/platform/stores/${storeId}/products`)
``` ```
### After (Proposed - `/api/v1/shop`) ### After (Proposed - `/api/v1/storefront`)
```python ```python
# app/api/v1/shop/products.py # app/api/v1/storefront/products.py
@router.get("/products") @router.get("/products")
def get_product_catalog( def get_product_catalog(
request: Request, request: Request,
@@ -350,7 +350,7 @@ def get_product_catalog(
```javascript ```javascript
// Frontend // Frontend
fetch('/api/v1/shop/products') // Store context automatic fetch('/api/v1/storefront/products') // Store context automatic
``` ```
--- ---
@@ -382,7 +382,7 @@ If full migration is not approved immediately, we can do a **minimal fix** for t
### Quick Fix: Just Move CMS to Public API ### Quick Fix: Just Move CMS to Public API
```python ```python
# Move: app/api/v1/shop/content_pages.py # Move: app/api/v1/storefront/content_pages.py
# To: app/api/v1/platform/stores/content_pages.py # To: app/api/v1/platform/stores/content_pages.py
# Update routes: # Update routes:
@@ -401,7 +401,7 @@ If full migration is not approved immediately, we can do a **minimal fix** for t
**Question for Team:** **Question for Team:**
Should we: Should we:
1. ✅ **Consolidate to `/api/v1/shop/*`** (Recommended) 1. ✅ **Consolidate to `/api/v1/storefront/*`** (Recommended)
2. ❌ **Keep `/api/v1/platform/stores/*`** and move CMS there 2. ❌ **Keep `/api/v1/platform/stores/*`** and move CMS there
3. ❌ **Hybrid approach** with both patterns 3. ❌ **Hybrid approach** with both patterns
4. ❌ **Quick fix only** - move CMS, address later 4. ❌ **Quick fix only** - move CMS, address later
@@ -422,15 +422,15 @@ Should we:
- 🚧 `search.py` - Stub - 🚧 `search.py` - Stub
- 🚧 `shop.py` - Stub - 🚧 `shop.py` - Stub
### `/api/v1/shop/*` ### `/api/v1/storefront/*`
- ✅ `content_pages.py` - CMS pages - ✅ `content_pages.py` - CMS pages
### To Be Created (if Option 1 chosen) ### To Be Created (if Option 1 chosen)
- 📝 `shop/products.py` - 📝 `storefront/products.py`
- 📝 `shop/cart.py` - 📝 `storefront/cart.py`
- 📝 `shop/orders.py` - 📝 `storefront/orders.py`
- 📝 `shop/auth.py` - 📝 `storefront/auth.py`
- 📝 `shop/stores.py` (marketplace listing) - 📝 `storefront/stores.py` (marketplace listing)
--- ---

View File

@@ -1,55 +1,55 @@
# API Migration Status - `/api/v1/shop/*` Consolidation # API Migration Status - `/api/v1/storefront/*` Consolidation
**Date:** 2025-11-22 **Date:** 2025-11-22
**Status:** 🎉 MIGRATION COMPLETE - All Phases Done **Status:** 🎉 MIGRATION COMPLETE - All Phases Done
**Decision:** Option 1 - Full Consolidation to `/api/v1/shop/*` **Decision:** Option 1 - Full Consolidation to `/api/v1/storefront/*`
--- ---
## Progress Overview ## Progress Overview
### ✅ Phase 1: New Shop API Endpoints (COMPLETE) ### ✅ Phase 1: New Storefront API Endpoints (COMPLETE)
All new shop endpoints have been created using middleware-based store context: All new storefront endpoints have been created using middleware-based store context:
### ✅ Middleware Update: Referer-Based Store Extraction (COMPLETE) ### ✅ Middleware Update: Referer-Based Store Extraction (COMPLETE)
Updated `StoreContextMiddleware` to support shop API routes: Updated `StoreContextMiddleware` to support storefront API routes:
- Added `is_shop_api_request()` method to detect `/api/v1/shop/*` routes - Added `is_storefront_api_request()` method to detect `/api/v1/storefront/*` routes
- Added `extract_store_from_referer()` method to extract store from Referer/Origin headers - Added `extract_store_from_referer()` method to extract store from Referer/Origin headers
- Modified `dispatch()` to handle shop API routes specially (no longer skips them) - Modified `dispatch()` to handle storefront API routes specially (no longer skips them)
- Shop API now receives store context from the page that made the API call - Storefront API now receives store context from the page that made the API call
**How it works:** **How it works:**
1. Browser JavaScript on `/stores/orion/shop/products` calls `/api/v1/shop/products` 1. Browser JavaScript on `/storefront/orion/products` calls `/api/v1/storefront/products`
2. Browser automatically sends `Referer: http://localhost:8000/stores/orion/shop/products` 2. Browser automatically sends `Referer: http://localhost:8000/storefront/orion/products`
3. Middleware extracts `orion` from Referer path 3. Middleware extracts `orion` from Referer path
4. Queries database to get Store object 4. Queries database to get Store object
5. Sets `request.state.store` for the API endpoint 5. Sets `request.state.store` for the API endpoint
### ✅ Phase 1a: Endpoint Testing (COMPLETE) ### ✅ Phase 1a: Endpoint Testing (COMPLETE)
Tested shop API endpoints with Referer header: Tested storefront API endpoints with Referer header:
| Endpoint | Method | Test Result | Notes | | Endpoint | Method | Test Result | Notes |
|----------|--------|-------------|-------| |----------|--------|-------------|-------|
| `/api/v1/shop/products` | GET | ✅ Working | Returns paginated product list | | `/api/v1/storefront/products` | GET | ✅ Working | Returns paginated product list |
| `/api/v1/shop/products?search=Sample` | GET | ✅ Working | Search functionality works | | `/api/v1/storefront/products?search=Sample` | GET | ✅ Working | Search functionality works |
| `/api/v1/shop/products?is_featured=true` | GET | ✅ Working | Featured filter works | | `/api/v1/storefront/products?is_featured=true` | GET | ✅ Working | Featured filter works |
| `/api/v1/shop/products/{id}` | GET | ✅ Working | Product details returned | | `/api/v1/storefront/products/{id}` | GET | ✅ Working | Product details returned |
| `/api/v1/shop/cart/{session_id}` | GET | ✅ Working | Empty cart returns correctly | | `/api/v1/storefront/cart/{session_id}` | GET | ✅ Working | Empty cart returns correctly |
| `/api/v1/shop/content-pages/navigation` | GET | ✅ Working | Navigation links returned | | `/api/v1/storefront/content-pages/navigation` | GET | ✅ Working | Navigation links returned |
**All tested endpoints successfully receive store context from Referer header.** **All tested endpoints successfully receive store context from Referer header.**
| Endpoint File | Status | Routes | Description | | Endpoint File | Status | Routes | Description |
|--------------|--------|--------|-------------| |--------------|--------|--------|-------------|
| `shop/products.py` | ✅ Complete | 3 routes | Product catalog, details, search | | `storefront/products.py` | ✅ Complete | 3 routes | Product catalog, details, search |
| `shop/cart.py` | ✅ Complete | 5 routes | Cart CRUD operations | | `storefront/cart.py` | ✅ Complete | 5 routes | Cart CRUD operations |
| `shop/orders.py` | ✅ Complete | 3 routes | Order placement & history | | `storefront/orders.py` | ✅ Complete | 3 routes | Order placement & history |
| `shop/auth.py` | ✅ Complete | 5 routes | Login, register, password reset | | `storefront/auth.py` | ✅ Complete | 5 routes | Login, register, password reset |
| `shop/content_pages.py` | ✅ Existing | 2 routes | CMS pages (already present) | | `storefront/content_pages.py` | ✅ Existing | 2 routes | CMS pages (already present) |
| `shop/__init__.py` | ✅ Updated | - | Router aggregation | | `storefront/__init__.py` | ✅ Updated | - | Router aggregation |
**Total:** 18 new API endpoints created **Total:** 18 new API endpoints created
@@ -57,46 +57,46 @@ Tested shop API endpoints with Referer header:
## New API Structure ## New API Structure
### Shop Endpoints (`/api/v1/shop/*`) ### Storefront Endpoints (`/api/v1/storefront/*`)
All endpoints use `request.state.store` (injected by `StoreContextMiddleware`). All endpoints use `request.state.store` (injected by `StoreContextMiddleware`).
#### Products (Public - No Auth) #### Products (Public - No Auth)
``` ```
GET /api/v1/shop/products → Product catalog (paginated) GET /api/v1/storefront/products → Product catalog (paginated)
GET /api/v1/shop/products/{id} → Product details GET /api/v1/storefront/products/{id} → Product details
GET /api/v1/shop/products/search?q=... → Search products GET /api/v1/storefront/products/search?q=... → Search products
``` ```
#### Cart (Public - Session Based) #### Cart (Public - Session Based)
``` ```
GET /api/v1/shop/cart/{session_id} → Get cart GET /api/v1/storefront/cart/{session_id} → Get cart
POST /api/v1/shop/cart/{session_id}/items → Add to cart POST /api/v1/storefront/cart/{session_id}/items → Add to cart
PUT /api/v1/shop/cart/{session_id}/items/{id} → Update item PUT /api/v1/storefront/cart/{session_id}/items/{id} → Update item
DELETE /api/v1/shop/cart/{session_id}/items/{id} → Remove item DELETE /api/v1/storefront/cart/{session_id}/items/{id} → Remove item
DELETE /api/v1/shop/cart/{session_id} → Clear cart DELETE /api/v1/storefront/cart/{session_id} → Clear cart
``` ```
#### Orders (Authenticated - Customer) #### Orders (Authenticated - Customer)
``` ```
POST /api/v1/shop/orders → Place order (auth required) POST /api/v1/storefront/orders → Place order (auth required)
GET /api/v1/shop/orders → Order history (auth required) GET /api/v1/storefront/orders → Order history (auth required)
GET /api/v1/shop/orders/{id} → Order details (auth required) GET /api/v1/storefront/orders/{id} → Order details (auth required)
``` ```
#### Authentication (Public) #### Authentication (Public)
``` ```
POST /api/v1/shop/auth/register → Register customer POST /api/v1/storefront/auth/register → Register customer
POST /api/v1/shop/auth/login → Customer login POST /api/v1/storefront/auth/login → Customer login
POST /api/v1/shop/auth/logout → Customer logout POST /api/v1/storefront/auth/logout → Customer logout
POST /api/v1/shop/auth/forgot-password → Request reset POST /api/v1/storefront/auth/forgot-password → Request reset
POST /api/v1/shop/auth/reset-password → Reset password POST /api/v1/storefront/auth/reset-password → Reset password
``` ```
#### CMS Content (Public) #### CMS Content (Public)
``` ```
GET /api/v1/shop/content-pages/navigation → Navigation links GET /api/v1/storefront/content-pages/navigation → Navigation links
GET /api/v1/shop/content-pages/{slug} → Page content GET /api/v1/storefront/content-pages/{slug} → Page content
``` ```
--- ---
@@ -130,7 +130,7 @@ def endpoint_handler(request: Request, ...):
- **Public endpoints** (products, cart, CMS): No authentication - **Public endpoints** (products, cart, CMS): No authentication
- **Authenticated endpoints** (orders): Use `get_current_customer_api` dependency - **Authenticated endpoints** (orders): Use `get_current_customer_api` dependency
- **Cookie strategy**: Customer tokens stored at `path=/shop` only - **Cookie strategy**: Customer tokens stored at `path=/storefront` only
### Error Handling ### Error Handling
@@ -145,7 +145,7 @@ def endpoint_handler(request: Request, ...):
### Files Created ✨ ### Files Created ✨
``` ```
app/api/v1/shop/ app/api/v1/storefront/
├── products.py (NEW - 182 lines) ├── products.py (NEW - 182 lines)
├── cart.py (NEW - 242 lines) ├── cart.py (NEW - 242 lines)
├── orders.py (NEW - 193 lines) ├── orders.py (NEW - 193 lines)
@@ -156,9 +156,9 @@ app/api/v1/shop/
### Files Modified 🔧 ### Files Modified 🔧
``` ```
app/exceptions/error_renderer.py → Added base_url calculation for shop context app/exceptions/error_renderer.py → Added base_url calculation for storefront context
app/routes/store_pages.py → Added CMS route handler app/routes/store_pages.py → Added CMS route handler
app/templates/shop/errors/*.html → Fixed links to use base_url app/templates/storefront/errors/*.html → Fixed links to use base_url
docs/architecture/ docs/architecture/
├── api-consolidation-proposal.md → Analysis & recommendation ├── api-consolidation-proposal.md → Analysis & recommendation
└── api-migration-status.md → This file └── api-migration-status.md → This file
@@ -170,27 +170,27 @@ docs/architecture/
### ✅ Phase 2: Frontend Migration (COMPLETE) ### ✅ Phase 2: Frontend Migration (COMPLETE)
Updated all shop templates to use new API endpoints: Updated all storefront templates to use new API endpoints:
| Template | Old Endpoint | New Endpoint | Status | | Template | Old Endpoint | New Endpoint | Status |
|----------|-------------|--------------|---------| |----------|-------------|--------------|---------|
| `shop/account/login.html` | `/api/v1/platform/stores/${id}/customers/login` | `/api/v1/shop/auth/login` | ✅ Complete | | `storefront/account/login.html` | `/api/v1/platform/stores/${id}/customers/login` | `/api/v1/storefront/auth/login` | ✅ Complete |
| `shop/account/register.html` | `/api/v1/platform/stores/${id}/customers/register` | `/api/v1/shop/auth/register` | ✅ Complete | | `storefront/account/register.html` | `/api/v1/platform/stores/${id}/customers/register` | `/api/v1/storefront/auth/register` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/stores/${id}/products/${pid}` | `/api/v1/shop/products/${pid}` | ✅ Complete | | `storefront/product.html` | `/api/v1/platform/stores/${id}/products/${pid}` | `/api/v1/storefront/products/${pid}` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/stores/${id}/products?limit=4` | `/api/v1/shop/products?limit=4` | ✅ Complete | | `storefront/product.html` | `/api/v1/platform/stores/${id}/products?limit=4` | `/api/v1/storefront/products?limit=4` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete | | `storefront/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/storefront/cart/${sid}` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items` | `/api/v1/shop/cart/${sid}/items` | ✅ Complete | | `storefront/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items` | `/api/v1/storefront/cart/${sid}/items` | ✅ Complete |
| `shop/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete | | `storefront/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/storefront/cart/${sid}` | ✅ Complete |
| `shop/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (PUT) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete | | `storefront/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (PUT) | `/api/v1/storefront/cart/${sid}/items/${pid}` | ✅ Complete |
| `shop/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (DELETE) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete | | `storefront/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (DELETE) | `/api/v1/storefront/cart/${sid}/items/${pid}` | ✅ Complete |
| `shop/products.html` | Already using `/api/v1/shop/products` | (No change needed) | ✅ Already Updated | | `storefront/products.html` | Already using `/api/v1/storefront/products` | (No change needed) | ✅ Already Updated |
| `shop/home.html` | Already using `/api/v1/shop/products?featured=true` | (No change needed) | ✅ Already Updated | | `storefront/home.html` | Already using `/api/v1/storefront/products?featured=true` | (No change needed) | ✅ Already Updated |
**Total Changes:** 9 API endpoint migrations across 3 template files **Total Changes:** 9 API endpoint migrations across 3 template files
**Verification:** **Verification:**
```bash ```bash
grep -r "api/v1/public/stores" app/templates/shop --include="*.html" grep -r "api/v1/public/stores" app/templates/storefront --include="*.html"
# Returns: (no results - all migrated) # Returns: (no results - all migrated)
``` ```
@@ -199,16 +199,16 @@ grep -r "api/v1/public/stores" app/templates/shop --include="*.html"
Cleaned up old `/api/v1/platform/stores/*` endpoints: Cleaned up old `/api/v1/platform/stores/*` endpoints:
**Files Removed:** **Files Removed:**
-`auth.py` - Migrated to `/api/v1/shop/auth.py` -`auth.py` - Migrated to `/api/v1/storefront/auth.py`
-`products.py` - Migrated to `/api/v1/shop/products.py` -`products.py` - Migrated to `/api/v1/storefront/products.py`
-`cart.py` - Migrated to `/api/v1/shop/cart.py` -`cart.py` - Migrated to `/api/v1/storefront/cart.py`
-`orders.py` - Migrated to `/api/v1/shop/orders.py` -`orders.py` - Migrated to `/api/v1/storefront/orders.py`
-`payments.py` - Empty placeholder (removed) -`payments.py` - Empty placeholder (removed)
-`search.py` - Empty placeholder (removed) -`search.py` - Empty placeholder (removed)
-`shop.py` - Empty placeholder (removed) -`storefront.py` - Empty placeholder (removed)
**Files Kept:** **Files Kept:**
-`stores.py` - Store lookup endpoints (truly public, not shop-specific) -`stores.py` - Store lookup endpoints (truly public, not storefront-specific)
- `GET /api/v1/platform/stores/by-code/{store_code}` - `GET /api/v1/platform/stores/by-code/{store_code}`
- `GET /api/v1/platform/stores/by-subdomain/{subdomain}` - `GET /api/v1/platform/stores/by-subdomain/{subdomain}`
- `GET /api/v1/platform/stores/{store_id}/info` - `GET /api/v1/platform/stores/{store_id}/info`
@@ -216,7 +216,7 @@ Cleaned up old `/api/v1/platform/stores/*` endpoints:
**Updated:** **Updated:**
-`/app/api/v1/platform/__init__.py` - Now only includes store lookup endpoints -`/app/api/v1/platform/__init__.py` - Now only includes store lookup endpoints
**Result:** Old shop endpoints completely removed, only store lookup remains in `/api/v1/platform/stores/*` **Result:** Old storefront endpoints completely removed, only store lookup remains in `/api/v1/platform/stores/*`
### ⚠️ Phase 4: Deprecation Warnings (SKIPPED - Not Needed) ### ⚠️ Phase 4: Deprecation Warnings (SKIPPED - Not Needed)
@@ -291,12 +291,12 @@ Old endpoint cleanup completed immediately (no gradual migration needed):
### After (New Pattern) ### After (New Pattern)
``` ```
# Clean - store from context # Clean - store from context
/api/v1/shop/products /api/v1/storefront/products
/api/v1/shop/products/456 /api/v1/storefront/products/456
/api/v1/shop/cart/abc-session-id /api/v1/storefront/cart/abc-session-id
/api/v1/shop/cart/abc-session-id/items /api/v1/storefront/cart/abc-session-id/items
/api/v1/shop/orders /api/v1/storefront/orders
/api/v1/shop/auth/login /api/v1/storefront/auth/login
``` ```
**URL Reduction:** ~40% shorter URLs on average **URL Reduction:** ~40% shorter URLs on average
@@ -308,7 +308,7 @@ Old endpoint cleanup completed immediately (no gradual migration needed):
### For Frontend Developers ### For Frontend Developers
- ✅ Cleaner, more intuitive URLs - ✅ Cleaner, more intuitive URLs
- ✅ No need to track store_id in state - ✅ No need to track store_id in state
- ✅ Consistent API pattern across all shop endpoints - ✅ Consistent API pattern across all storefront endpoints
- ✅ Automatic store context from middleware - ✅ Automatic store context from middleware
### For Backend Developers ### For Backend Developers
@@ -331,7 +331,7 @@ If issues arise, rollback is simple since old endpoints still exist:
1. **Revert frontend changes:** 1. **Revert frontend changes:**
```bash ```bash
git checkout app/templates/shop/*.html git checkout app/templates/storefront/*.html
``` ```
2. **Old endpoints still work:** 2. **Old endpoints still work:**
@@ -357,7 +357,7 @@ Add logging to track which pattern is being used:
logger.info( logger.info(
"API call", "API call",
extra={ extra={
"endpoint_pattern": "new" if "/shop/" in request.url.path else "old", "endpoint_pattern": "new" if "/storefront/" in request.url.path else "old",
"path": request.url.path, "path": request.url.path,
"store_id": store.id if store else None, "store_id": store.id if store else None,
} }
@@ -377,7 +377,7 @@ logger.info(
### ✅ Decided ### ✅ Decided
1. **Use `/api/v1/shop/*` pattern?** → YES (Option 1) 1. **Use `/api/v1/storefront/*` pattern?** → YES (Option 1)
2. **Store from middleware?** → YES 2. **Store from middleware?** → YES
3. **Keep old endpoints during migration?** → YES 3. **Keep old endpoints during migration?** → YES
4. **Deprecation period?** → 2-4 weeks 4. **Deprecation period?** → 2-4 weeks
@@ -412,7 +412,7 @@ logger.info(
Migration is considered successful when: Migration is considered successful when:
- [x] All new endpoints created and tested - [x] All new endpoints created and tested
- [x] Middleware updated to support shop API routes - [x] Middleware updated to support storefront API routes
- [x] Store context extraction from Referer working - [x] Store context extraction from Referer working
- [x] All frontend templates updated (9 API calls across 3 files) - [x] All frontend templates updated (9 API calls across 3 files)
- [x] Old endpoint usage drops to zero (verified with grep) - [x] Old endpoint usage drops to zero (verified with grep)
@@ -432,7 +432,7 @@ Migration is considered successful when:
- [Middleware Documentation](./middleware.md) - How middleware works - [Middleware Documentation](./middleware.md) - How middleware works
**Issues?** Review: **Issues?** Review:
- Server logs: Check for `[SHOP_API]` log entries - Server logs: Check for `[STOREFRONT_API]` log entries
- Browser console: Check for failed API calls - Browser console: Check for failed API calls
- Network tab: Verify correct endpoints are called - Network tab: Verify correct endpoints are called

View File

@@ -7,7 +7,7 @@ Complete guide to the authentication and authorization system powering the multi
The platform uses a JWT-based authentication system combined with role-based access control (RBAC) to secure all interfaces: The platform uses a JWT-based authentication system combined with role-based access control (RBAC) to secure all interfaces:
- **Admin** interface - **Admin** interface
- **Store** dashboard - **Store** dashboard
- **Shop** storefront - **Storefront**
- **REST API** endpoints - **REST API** endpoints
## Authentication System ## Authentication System
@@ -60,17 +60,17 @@ User.role: "super_admin" | "platform_admin" | "merchant_owner" | "store_member"
### Customer Role (Separate Model) ### Customer Role (Separate Model)
**Access**: Public shop and own account space **Access**: Public storefront and own account space
**Capabilities**: **Capabilities**:
- Browse store shops - Browse store storefronts
- Place orders - Place orders
- Manage their own account and order history - Manage their own account and order history
- View order status - View order status
- Update profile information - Update profile information
- Can register directly from shop frontend - Can register directly from storefront frontend
**Account Creation**: Self-registration via shop frontend (email verification required) **Account Creation**: Self-registration via storefront frontend (email verification required)
**Authentication**: Standard JWT authentication (separate `Customer` model, not `User`) **Authentication**: Standard JWT authentication (separate `Customer` model, not `User`)
@@ -89,7 +89,7 @@ User.role: "super_admin" | "platform_admin" | "merchant_owner" | "store_member"
- Manage products and inventory - Manage products and inventory
- Process orders - Process orders
- View analytics and reports - View analytics and reports
- Configure shop settings - Configure storefront settings
- Manage team members (invite, remove, update roles) - Manage team members (invite, remove, update roles)
- Access store-specific APIs - Access store-specific APIs
@@ -155,8 +155,8 @@ The platform has three distinct areas with different access requirements:
| Area | URL Pattern | Access | Purpose | | Area | URL Pattern | Access | Purpose |
|------|-------------|--------|---------| |------|-------------|--------|---------|
| **Admin** | `/admin/*` or `admin.platform.com` | Super admins and platform admins (`is_admin`) | Platform administration and store management | | **Admin** | `/admin/*` or `admin.platform.com` | Super admins and platform admins (`is_admin`) | Platform administration and store management |
| **Store** | `/store/*` | Merchant owners and store members (`is_store_user`) | Store dashboard and shop management | | **Store** | `/store/*` | Merchant owners and store members (`is_store_user`) | Store dashboard and storefront management |
| **Shop** | `/shop/*`, custom domains, subdomains | Customers and public | Public-facing eCommerce storefront | | **Storefront** | `/storefront/*`, custom domains, subdomains | Customers and public | Public-facing eCommerce storefront |
| **API** | `/api/*` | All authenticated users (role-based) | REST API for all operations | | **API** | `/api/*` | All authenticated users (role-based) | REST API for all operations |
## Account Registration Flow ## Account Registration Flow
@@ -174,7 +174,7 @@ The platform has three distinct areas with different access requirements:
- Activation: Upon clicking email verification link - Activation: Upon clicking email verification link
### Customer Accounts ### Customer Accounts
- ✅ Can register directly on store shop - ✅ Can register directly on store storefront
- Activation: Upon clicking registration email link - Activation: Upon clicking registration email link
- Used for: Shopping and order management - Used for: Shopping and order management
@@ -222,8 +222,8 @@ Allows access to customer users and admins.
**Usage**: **Usage**:
```python ```python
@app.get("/shop/orders") @app.get("/storefront/orders")
async def customer_orders( async def storefront_orders(
current_user: User = Depends(auth_manager.require_customer) current_user: User = Depends(auth_manager.require_customer)
): ):
return {"orders": [...]} return {"orders": [...]}
@@ -390,13 +390,13 @@ graph TD
D[Merchant Owner<br/>role=merchant_owner] --> E[Store Dashboard] D[Merchant Owner<br/>role=merchant_owner] --> E[Store Dashboard]
D --> F[Team Management] D --> F[Team Management]
D --> G[Shop Settings] D --> G[Storefront Settings]
D --> H[All Store Permissions - 75] D --> H[All Store Permissions - 75]
I[Store Member<br/>role=store_member] --> E I[Store Member<br/>role=store_member] --> E
I --> J[Role-Based Permissions] I --> J[Role-Based Permissions]
K[Customer<br/>separate model] --> L[Shop Access] K[Customer<br/>separate model] --> L[Storefront Access]
K --> M[Own Orders] K --> M[Own Orders]
K --> N[Own Profile] K --> N[Own Profile]
``` ```

View File

@@ -68,7 +68,7 @@ The `FrontendDetector` uses the following priority order:
2. Path-based detection: 2. Path-based detection:
- /admin/* or /api/v1/admin/* → ADMIN - /admin/* or /api/v1/admin/* → ADMIN
- /store/* or /api/v1/store/* → STORE - /store/* or /api/v1/store/* → STORE
- /storefront/*, /shop/*, /stores/* → STOREFRONT - /storefront/*, /stores/* → STOREFRONT
- /api/v1/platform/* → PLATFORM - /api/v1/platform/* → PLATFORM
3. Store subdomain (orion.omsflow.lu) → STOREFRONT 3. Store subdomain (orion.omsflow.lu) → STOREFRONT
4. Store context set by middleware → STOREFRONT 4. Store context set by middleware → STOREFRONT
@@ -88,8 +88,8 @@ STORE_PATH_PREFIXES = ("/store/", "/api/v1/store")
STOREFRONT_PATH_PREFIXES = ( STOREFRONT_PATH_PREFIXES = (
"/storefront", "/storefront",
"/api/v1/storefront", "/api/v1/storefront",
"/shop", # Legacy support "/shop", # Legacy support (redirects to /storefront)
"/api/v1/shop", # Legacy support "/api/v1/shop", # Legacy support (redirects to /api/v1/storefront)
"/stores/", # Path-based store access "/stores/", # Path-based store access
) )

View File

@@ -186,7 +186,7 @@ function languageSelector(currentLang, enabledLanguages) {
### Rule FE-003: Language Selector Must Be In Shared JavaScript ### Rule FE-003: Language Selector Must Be In Shared JavaScript
**Language selector function MUST be defined in:** **Language selector function MUST be defined in:**
- `static/shop/js/shop-layout.js` for storefront - `static/storefront/js/storefront-layout.js` for storefront
- `static/store/js/init-alpine.js` for store dashboard - `static/store/js/init-alpine.js` for store dashboard
```javascript ```javascript
@@ -444,7 +444,7 @@ static/locales/
"title": "Tableau de bord" "title": "Tableau de bord"
} }
}, },
"shop": { "storefront": {
"cart": { "cart": {
"empty": "Votre panier est vide" "empty": "Votre panier est vide"
} }
@@ -466,7 +466,7 @@ static/locales/
### Language Selector Implementation Checklist ### Language Selector Implementation Checklist
- [ ] Function defined in appropriate JS file (`shop-layout.js` or `init-alpine.js`) - [ ] Function defined in appropriate JS file (`storefront-layout.js` or `init-alpine.js`)
- [ ] Function exported to `window.languageSelector` - [ ] Function exported to `window.languageSelector`
- [ ] Uses `tojson|safe` filter for language array - [ ] Uses `tojson|safe` filter for language array
- [ ] Provides default values for both parameters - [ ] Provides default values for both parameters
@@ -492,14 +492,14 @@ static/locales/
| File | Type | Notes | | File | Type | Notes |
|------|------|-------| |------|------|-------|
| `static/shop/js/shop-layout.js` | JS | `languageSelector()` for storefront | | `static/storefront/js/storefront-layout.js` | JS | `languageSelector()` for storefront |
| `app/modules/core/static/store/js/init-alpine.js` | JS | `languageSelector()` for store dashboard | | `app/modules/core/static/store/js/init-alpine.js` | JS | `languageSelector()` for store dashboard |
| `app/modules/core/static/admin/js/init-alpine.js` | JS | `languageSelector()` for admin | | `app/modules/core/static/admin/js/init-alpine.js` | JS | `languageSelector()` for admin |
| `app/modules/core/static/merchant/js/init-alpine.js` | JS | `languageSelector()` for merchant | | `app/modules/core/static/merchant/js/init-alpine.js` | JS | `languageSelector()` for merchant |
| `app/templates/store/partials/header.html` | Template | Store dashboard language selector | | `app/templates/store/partials/header.html` | Template | Store dashboard language selector |
| `app/templates/admin/partials/header.html` | Template | Admin language selector | | `app/templates/admin/partials/header.html` | Template | Admin language selector |
| `app/templates/merchant/partials/header.html` | Template | Merchant language selector | | `app/templates/merchant/partials/header.html` | Template | Merchant language selector |
| `app/templates/shop/base.html` | Template | Storefront language selector | | `app/templates/storefront/base.html` | Template | Storefront language selector |
| `app/modules/core/routes/api/platform.py` | API | Language endpoints (`/api/v1/platform/language/*`) | | `app/modules/core/routes/api/platform.py` | API | Language endpoints (`/api/v1/platform/language/*`) |
| `middleware/language.py` | Middleware | Language detection per frontend type | | `middleware/language.py` | Middleware | Language detection per frontend type |
| `static/locales/*.json` | JSON | Translation files | | `static/locales/*.json` | JSON | Translation files |

View File

@@ -8,7 +8,7 @@ The application uses a custom middleware stack that processes **every request**
- REST API calls (`/api/*`) - REST API calls (`/api/*`)
- Admin interface pages (`/admin/*`) - Admin interface pages (`/admin/*`)
- Store dashboard pages (`/store/*`) - Store dashboard pages (`/store/*`)
- Shop pages (`/shop/*` or custom domains) - Storefront pages (`/storefront/*` or custom domains)
This middleware layer is **system-wide** and enables the multi-tenant architecture to function seamlessly. This middleware layer is **system-wide** and enables the multi-tenant architecture to function seamlessly.
@@ -89,7 +89,7 @@ INFO Response: 200 for GET /admin/dashboard (0.143s)
### 2. Store Context Middleware ### 2. Store Context Middleware
**Purpose**: Detect which store's shop the request is for (multi-tenant core) **Purpose**: Detect which store's storefront the request is for (multi-tenant core)
**What it does**: **What it does**:
- Detects store from: - Detects store from:
@@ -103,7 +103,7 @@ INFO Response: 200 for GET /admin/dashboard (0.143s)
**Example**: **Example**:
``` ```
Request: https://orion.platform.com/shop/products Request: https://orion.platform.com/storefront/products
Middleware detects: store_code = "orion" Middleware detects: store_code = "orion"
@@ -111,14 +111,14 @@ Queries database: SELECT * FROM stores WHERE code = 'orion'
Injects: request.state.store = <Store object> Injects: request.state.store = <Store object>
request.state.store_id = 1 request.state.store_id = 1
request.state.clean_path = "/shop/products" request.state.clean_path = "/storefront/products"
``` ```
**Why it's critical**: Without this, the system wouldn't know which store's data to show **Why it's critical**: Without this, the system wouldn't know which store's data to show
**See**: [Multi-Tenant System](multi-tenant.md) for routing modes **See**: [Multi-Tenant System](multi-tenant.md) for routing modes
**Note on Path-Based Routing:** Previous implementations used a `PathRewriteMiddleware` to rewrite paths at runtime. This has been replaced with **double router mounting** in `main.py`, where shop routes are registered twice with different prefixes (`/shop` and `/stores/{store_code}/shop`). This approach is simpler and uses FastAPI's native routing capabilities. **Note on Path-Based Routing:** Previous implementations used a `PathRewriteMiddleware` to rewrite paths at runtime. This has been replaced with **double router mounting** in `main.py`, where storefront routes are registered twice with different prefixes (`/storefront` and `/stores/{store_code}/storefront`). This approach is simpler and uses FastAPI's native routing capabilities.
### 3. Frontend Type Detection Middleware ### 3. Frontend Type Detection Middleware
@@ -139,7 +139,7 @@ Injects: request.state.store = <Store object>
2. Path-based detection: 2. Path-based detection:
- /admin/* or /api/v1/admin/* ADMIN - /admin/* or /api/v1/admin/* ADMIN
- /store/* or /api/v1/store/* STORE - /store/* or /api/v1/store/* STORE
- /storefront/*, /shop/*, /stores/* STOREFRONT - /storefront/*, /stores/* STOREFRONT
- /api/v1/platform/* PLATFORM - /api/v1/platform/* PLATFORM
3. Store subdomain (orion.omsflow.lu) STOREFRONT 3. Store subdomain (orion.omsflow.lu) STOREFRONT
4. Store context set by middleware STOREFRONT 4. Store context set by middleware STOREFRONT
@@ -171,7 +171,7 @@ Injects: request.state.store = <Store object>
} }
``` ```
**Why it's needed**: Each store shop can have custom branding **Why it's needed**: Each store storefront can have custom branding
## Naming Conventions ## Naming Conventions
@@ -311,7 +311,7 @@ graph TD
**Breaking this order will break the application!** **Breaking this order will break the application!**
**Note:** Path-based routing (e.g., `/stores/{code}/shop/*`) is handled by double router mounting in `main.py`, not by middleware. Platform path-based routing (e.g., `/platforms/oms/`) IS handled by PlatformContextMiddleware which rewrites the path. **Note:** Path-based routing (e.g., `/stores/{code}/storefront/*`) is handled by double router mounting in `main.py`, not by middleware. Platform path-based routing (e.g., `/platforms/oms/`) IS handled by PlatformContextMiddleware which rewrites the path.
## Request State Variables ## Request State Variables
@@ -332,7 +332,7 @@ Middleware components inject these variables into `request.state`:
```python ```python
from fastapi import Request from fastapi import Request
@app.get("/shop/products") @app.get("/storefront/products")
async def get_products(request: Request): async def get_products(request: Request):
# Access store # Access store
store = request.state.store store = request.state.store
@@ -374,26 +374,26 @@ async def get_products(request: Request):
## Request Flow Example ## Request Flow Example
### Example: Shop Product Page Request ### Example: Storefront Product Page Request
**URL**: `https://orion.myplatform.com/shop/products` **URL**: `https://orion.myplatform.com/storefront/products`
**Middleware Processing**: **Middleware Processing**:
``` ```
1. LoggingMiddleware 1. LoggingMiddleware
↓ Starts timer ↓ Starts timer
↓ Logs: "Request: GET /shop/products from 192.168.1.100" ↓ Logs: "Request: GET /storefront/products from 192.168.1.100"
2. StoreContextMiddleware 2. StoreContextMiddleware
↓ Detects subdomain: "orion" ↓ Detects subdomain: "orion"
↓ Queries DB: store = get_store_by_code("orion") ↓ Queries DB: store = get_store_by_code("orion")
↓ Sets: request.state.store = <Store: Orion> ↓ Sets: request.state.store = <Store: Orion>
↓ Sets: request.state.store_id = 1 ↓ Sets: request.state.store_id = 1
↓ Sets: request.state.clean_path = "/shop/products" ↓ Sets: request.state.clean_path = "/storefront/products"
3. FrontendTypeMiddleware 3. FrontendTypeMiddleware
↓ Uses FrontendDetector with path: "/shop/products" ↓ Uses FrontendDetector with path: "/storefront/products"
↓ Has store context: Yes ↓ Has store context: Yes
↓ Detects storefront frontend ↓ Detects storefront frontend
↓ Sets: request.state.frontend_type = FrontendType.STOREFRONT ↓ Sets: request.state.frontend_type = FrontendType.STOREFRONT
@@ -403,7 +403,7 @@ async def get_products(request: Request):
↓ Sets: request.state.theme = {...theme config...} ↓ Sets: request.state.theme = {...theme config...}
5. FastAPI Router 5. FastAPI Router
↓ Matches route: @app.get("/shop/products") ↓ Matches route: @app.get("/storefront/products")
↓ Calls handler function ↓ Calls handler function
6. Route Handler 6. Route Handler
@@ -415,7 +415,7 @@ async def get_products(request: Request):
↓ Returns HTML with store theme ↓ Returns HTML with store theme
9. LoggingMiddleware (response phase) 9. LoggingMiddleware (response phase)
↓ Logs: "Response: 200 for GET /shop/products (0.143s)" ↓ Logs: "Response: 200 for GET /storefront/products (0.143s)"
↓ Adds header: X-Process-Time: 0.143 ↓ Adds header: X-Process-Time: 0.143
``` ```
@@ -510,9 +510,9 @@ def test_store_detection_subdomain():
Test the full middleware stack: Test the full middleware stack:
```python ```python
def test_shop_request_flow(client): def test_storefront_request_flow(client):
response = client.get( response = client.get(
"/shop/products", "/storefront/products",
headers={"Host": "orion.platform.com"} headers={"Host": "orion.platform.com"}
) )

View File

@@ -4,14 +4,14 @@ Complete guide to the multi-tenant architecture supporting custom domains, subdo
## Overview ## Overview
The Orion platform supports **three deployment modes** for multi-tenancy, allowing each store to have their own isolated shop while sharing the same application instance and database. The Orion platform supports **three deployment modes** for multi-tenancy, allowing each store to have their own isolated storefront while sharing the same application instance and database.
**Key Concept**: One application, multiple isolated store shops, each accessible via different URLs. **Key Concept**: One application, multiple isolated store storefronts, each accessible via different URLs.
**Important Distinction:** **Important Distinction:**
- **Store Dashboard** (all modes): `/store/{code}/*` (singular) - Management interface for stores - **Store Dashboard** (all modes): `/store/{code}/*` (singular) - Management interface for stores
- **Shop Storefront** (path-based only): `/stores/{code}/shop/*` (plural) - Customer-facing shop - **Storefront** (path-based only): `/storefront/{code}/*` - Customer-facing storefront
- This naming distinction helps separate administrative routes from public-facing shop routes - This naming distinction helps separate administrative routes from public-facing storefront routes
## The Three Routing Modes ## The Three Routing Modes
@@ -21,16 +21,16 @@ The Orion platform supports **three deployment modes** for multi-tenancy, allowi
**Example**: **Example**:
``` ```
customdomain1.com → Store 1 Shop customdomain1.com → Store 1 Storefront
anothershop.com → Store 2 Shop anothershop.com → Store 2 Storefront
beststore.net → Store 3 Shop beststore.net → Store 3 Storefront
``` ```
**How it works**: **How it works**:
1. Store registers a custom domain 1. Store registers a custom domain
2. Domain's DNS is configured to point to the platform 2. Domain's DNS is configured to point to the platform
3. Platform detects store by matching domain in database 3. Platform detects store by matching domain in database
4. Store's shop is displayed with their theme/branding 4. Store's storefront is displayed with their theme/branding
**Use Case**: Professional stores who want their own branded domain **Use Case**: Professional stores who want their own branded domain
@@ -50,9 +50,9 @@ store_id | domain
**Example**: **Example**:
``` ```
store1.platform.com → Store 1 Shop store1.platform.com → Store 1 Storefront
store2.platform.com → Store 2 Shop store2.platform.com → Store 2 Storefront
store3.platform.com → Store 3 Shop store3.platform.com → Store 3 Storefront
admin.platform.com → Admin Interface admin.platform.com → Admin Interface
``` ```
@@ -69,9 +69,9 @@ admin.platform.com → Admin Interface
# Stores table # Stores table
id | code | name id | code | name
---|---------|---------- ---|---------|----------
1 | store1 | Store One Shop 1 | store1 | Store One Storefront
2 | store2 | Store Two Shop 2 | store2 | Store Two Storefront
3 | store3 | Store Three Shop 3 | store3 | Store Three Storefront
``` ```
### 3. Path-Based Mode ### 3. Path-Based Mode
@@ -80,9 +80,9 @@ id | code | name
**Example**: **Example**:
``` ```
platform.com/stores/store1/shop → Store 1 Shop platform.com/storefront/store1 → Store 1 Storefront
platform.com/stores/store2/shop → Store 2 Shop platform.com/storefront/store2 → Store 2 Storefront
platform.com/stores/store3/shop → Store 3 Shop platform.com/storefront/store3 → Store 3 Storefront
``` ```
**How it works**: **How it works**:
@@ -94,7 +94,7 @@ platform.com/stores/store3/shop → Store 3 Shop
**Use Case**: Development and testing environments only **Use Case**: Development and testing environments only
**Path Patterns**: **Path Patterns**:
- `/stores/{code}/shop/*` - Storefront pages (correct pattern) - `/storefront/{code}/*` - Storefront pages (correct pattern)
- `/store/{code}/*` - Store dashboard pages (different context) - `/store/{code}/*` - Store dashboard pages (different context)
## Routing Mode Comparison ## Routing Mode Comparison
@@ -107,7 +107,7 @@ platform.com/stores/store3/shop → Store 3 Shop
| **SEO Benefits** | Best (own domain) | Good | Limited | | **SEO Benefits** | Best (own domain) | Good | Limited |
| **Cost** | High (domain + SSL) | Low (wildcard SSL) | Lowest | | **Cost** | High (domain + SSL) | Low (wildcard SSL) | Lowest |
| **Isolation** | Best (separate domain) | Good | Good | | **Isolation** | Best (separate domain) | Good | Good |
| **URL Appearance** | `shop.com` | `shop.platform.com` | `platform.com/store/shop` | | **URL Appearance** | `shop.com` | `shop.platform.com` | `platform.com/storefront/store` |
## Implementation Details ## Implementation Details
@@ -133,7 +133,7 @@ def detect_store(request):
# 3. Try path-based # 3. Try path-based
path = request.url.path path = request.url.path
if path.startswith("/store/") or path.startswith("/stores/"): if path.startswith("/store/") or path.startswith("/storefront/"):
store_code = extract_code_from_path(path) store_code = extract_code_from_path(path)
store = find_by_code(store_code) store = find_by_code(store_code)
if store: if store:
@@ -146,11 +146,11 @@ def detect_store(request):
For path-based routing, clean paths are extracted: For path-based routing, clean paths are extracted:
**Path-Based Shop Routes** (Development): **Path-Based Storefront Routes** (Development):
``` ```
Original: /stores/ORION/shop/products Original: /storefront/ORION/products
Extracted: store_code = "ORION" Extracted: store_code = "ORION"
Clean: /shop/products Clean: /storefront/products
``` ```
**Store Dashboard Routes** (All environments): **Store Dashboard Routes** (All environments):
@@ -160,11 +160,11 @@ Extracted: store_code = "ORION"
Clean: /dashboard Clean: /dashboard
``` ```
**Note**: The shop storefront uses `/stores/` (plural) while the store dashboard uses `/store/` (singular). This distinction helps separate customer-facing shop routes from store management routes. **Note**: The storefront uses `/storefront/` while the store dashboard uses `/store/` (singular). This distinction helps separate customer-facing storefront routes from store management routes.
**Why Clean Path?** **Why Clean Path?**
- FastAPI routes don't include store prefix - FastAPI routes don't include store prefix
- Routes defined as: `@app.get("/shop/products")` - Routes defined as: `@app.get("/storefront/products")`
- Path must be rewritten to match routes - Path must be rewritten to match routes
## Database Schema ## Database Schema
@@ -197,7 +197,7 @@ CREATE TABLE store_domains (
```sql ```sql
-- Stores -- Stores
INSERT INTO stores (code, name) VALUES INSERT INTO stores (code, name) VALUES
('orion', 'Orion Shop'), ('orion', 'Orion Storefront'),
('techstore', 'Tech Store'), ('techstore', 'Tech Store'),
('fashionhub', 'Fashion Hub'); ('fashionhub', 'Fashion Hub');
@@ -220,9 +220,9 @@ INSERT INTO store_domains (store_id, domain) VALUES
**URLs**: **URLs**:
``` ```
myplatform.com/admin myplatform.com/admin
myplatform.com/stores/shop1/shop myplatform.com/storefront/shop1
myplatform.com/stores/shop2/shop myplatform.com/storefront/shop2
myplatform.com/stores/shop3/shop myplatform.com/storefront/shop3
``` ```
**Infrastructure**: **Infrastructure**:
@@ -272,8 +272,8 @@ shop3.myplatform.com → Store 3
shop4.myplatform.com → Store 4 shop4.myplatform.com → Store 4
# Path-based (free tier) # Path-based (free tier)
myplatform.com/stores/shop5/shop → Store 5 myplatform.com/storefront/shop5 → Store 5
myplatform.com/stores/shop6/shop → Store 6 myplatform.com/storefront/shop6 → Store 6
``` ```
**Infrastructure**: **Infrastructure**:
@@ -391,7 +391,7 @@ static/
**Request**: **Request**:
```http ```http
GET /shop/products HTTP/1.1 GET /storefront/products HTTP/1.1
Host: customdomain.com Host: customdomain.com
``` ```
@@ -404,8 +404,8 @@ Host: customdomain.com
- Sets: request.state.store = <Store 1> - Sets: request.state.store = <Store 1>
2. ContextDetectionMiddleware 2. ContextDetectionMiddleware
- Analyzes: path = "/shop/products" - Analyzes: path = "/storefront/products"
- Sets: context_type = SHOP - Sets: context_type = STOREFRONT
3. ThemeContextMiddleware 3. ThemeContextMiddleware
- Queries: store_themes WHERE store_id = 1 - Queries: store_themes WHERE store_id = 1
@@ -420,7 +420,7 @@ Host: customdomain.com
**Request**: **Request**:
```http ```http
GET /shop/products HTTP/1.1 GET /storefront/products HTTP/1.1
Host: orion.myplatform.com Host: orion.myplatform.com
``` ```
@@ -439,22 +439,22 @@ Host: orion.myplatform.com
**Request**: **Request**:
```http ```http
GET /stores/ORION/shop/products HTTP/1.1 GET /storefront/ORION/products HTTP/1.1
Host: myplatform.com Host: myplatform.com
``` ```
**Processing**: **Processing**:
``` ```
1. StoreContextMiddleware 1. StoreContextMiddleware
- Checks: path starts with "/store/" - Checks: path starts with "/storefront/"
- Extracts: code = "ORION" - Extracts: code = "ORION"
- Queries: stores WHERE code = "ORION" - Queries: stores WHERE code = "ORION"
- Sets: request.state.store = <Store> - Sets: request.state.store = <Store>
- Sets: request.state.clean_path = "/shop/products" - Sets: request.state.clean_path = "/storefront/products"
2. FastAPI Router 2. FastAPI Router
- Routes registered with prefix: /stores/{store_code}/shop - Routes registered with prefix: /storefront/{store_code}
- Matches: /stores/ORION/shop/products - Matches: /storefront/ORION/products
- store_code path parameter = "ORION" - store_code path parameter = "ORION"
3-4. Same as previous examples (Context, Theme middleware) 3-4. Same as previous examples (Context, Theme middleware)
@@ -487,17 +487,17 @@ def test_store_detection_subdomain():
### Integration Tests ### Integration Tests
```python ```python
def test_shop_page_multi_tenant(client): def test_storefront_page_multi_tenant(client):
# Test subdomain routing # Test subdomain routing
response = client.get( response = client.get(
"/shop/products", "/storefront/products",
headers={"Host": "orion.platform.com"} headers={"Host": "orion.platform.com"}
) )
assert "Orion" in response.text assert "Orion" in response.text
# Test different store # Test different store
response = client.get( response = client.get(
"/shop/products", "/storefront/products",
headers={"Host": "techstore.platform.com"} headers={"Host": "techstore.platform.com"}
) )
assert "Tech Store" in response.text assert "Tech Store" in response.text

View File

@@ -49,8 +49,8 @@ admin.platform.com → Admin Interface
#### Path-Based Mode (Development Only) #### Path-Based Mode (Development Only)
``` ```
platform.com/stores/store1/shop → Store 1 Shop platform.com/storefront/store1 → Store 1 Shop
platform.com/stores/store2/shop → Store 2 Shop platform.com/storefront/store2 → Store 2 Shop
platform.com/admin → Admin Interface platform.com/admin → Admin Interface
``` ```
@@ -124,7 +124,7 @@ graph TB
F -->|API /api/*| G[API Router] F -->|API /api/*| G[API Router]
F -->|Admin /admin/*| H[Admin Page Router] F -->|Admin /admin/*| H[Admin Page Router]
F -->|Store /store/*| I[Store Page Router] F -->|Store /store/*| I[Store Page Router]
F -->|Shop /shop/*| J[Shop Page Router] F -->|Shop /storefront/*| J[Shop Page Router]
G --> K[JSON Response] G --> K[JSON Response]
H --> L[Admin HTML] H --> L[Admin HTML]
@@ -163,7 +163,7 @@ graph TB
**Access**: Store users (owners and team members) **Access**: Store users (owners and team members)
### Shop Interface (`/shop/*` or custom domains) ### Shop Interface (`/storefront/*` or custom domains)
**Purpose**: Customer-facing storefront **Purpose**: Customer-facing storefront

View File

@@ -42,7 +42,7 @@ graph TB
```http ```http
# Shop page request (subdomain mode) # Shop page request (subdomain mode)
GET https://orion.platform.com/shop/products GET https://orion.platform.com/storefront/products
Host: orion.platform.com Host: orion.platform.com
# API request # API request
@@ -69,7 +69,7 @@ Host: platform.com
start_time = time.time() start_time = time.time()
# Log entry # Log entry
logger.info(f"Request: GET /shop/products from 192.168.1.100") logger.info(f"Request: GET /storefront/products from 192.168.1.100")
``` ```
**Output**: Nothing added to `request.state` yet **Output**: Nothing added to `request.state` yet
@@ -87,7 +87,7 @@ logger.info(f"Request: GET /shop/products from 192.168.1.100")
```python ```python
# Input # Input
host = "orion.platform.com" host = "orion.platform.com"
path = "/shop/products" path = "/storefront/products"
# Detection logic # Detection logic
if host != settings.platform_domain: if host != settings.platform_domain:
@@ -102,14 +102,14 @@ if host != settings.platform_domain:
# Set request state # Set request state
request.state.store = store request.state.store = store
request.state.store_id = store.id request.state.store_id = store.id
request.state.clean_path = "/shop/products" # Already clean request.state.clean_path = "/storefront/products" # Already clean
``` ```
**Request State After**: **Request State After**:
```python ```python
request.state.store = <Store: Orion> request.state.store = <Store: Orion>
request.state.store_id = 1 request.state.store_id = 1
request.state.clean_path = "/shop/products" request.state.clean_path = "/storefront/products"
``` ```
### 4. Router Matching (FastAPI Native) ### 4. Router Matching (FastAPI Native)
@@ -117,18 +117,18 @@ request.state.clean_path = "/shop/products"
**What happens**: **What happens**:
- FastAPI matches the request path against registered routes - FastAPI matches the request path against registered routes
- For path-based development mode, routes are registered with two prefixes: - For path-based development mode, routes are registered with two prefixes:
- `/shop/*` for subdomain/custom domain - `/storefront/*` for subdomain/custom domain
- `/stores/{store_code}/shop/*` for path-based development - `/stores/{store_code}/storefront/*` for path-based development
**Example** (Path-Based Mode): **Example** (Path-Based Mode):
```python ```python
# In main.py - Double router mounting # In main.py - Double router mounting
app.include_router(shop_pages.router, prefix="/shop") app.include_router(storefront_pages.router, prefix="/storefront")
app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") app.include_router(storefront_pages.router, prefix="/stores/{store_code}/storefront")
# Request: /stores/ORION/shop/products # Request: /stores/ORION/storefront/products
# Matches: Second router (/stores/{store_code}/shop) # Matches: Second router (/stores/{store_code}/storefront)
# Route: @router.get("/products") # Route: @router.get("/products")
# store_code available as path parameter = "ORION" # store_code available as path parameter = "ORION"
``` ```
@@ -148,7 +148,7 @@ app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop")
```python ```python
host = request.headers.get("host", "") host = request.headers.get("host", "")
path = request.state.clean_path # "/shop/products" path = request.state.clean_path # "/storefront/products"
has_store = hasattr(request.state, 'store') and request.state.store has_store = hasattr(request.state, 'store') and request.state.store
# FrontendDetector handles all detection logic centrally # FrontendDetector handles all detection logic centrally
@@ -210,11 +210,11 @@ request.state.theme = {
**Route Matching**: **Route Matching**:
```python ```python
# Request path (after rewrite): "/shop/products" # Request path (after rewrite): "/storefront/products"
# Matches this route # Matches this route
@app.get("/shop/products") @app.get("/storefront/products")
async def get_shop_products(request: Request): async def get_storefront_products(request: Request):
# Handler code # Handler code
pass pass
``` ```
@@ -254,7 +254,7 @@ async def shop_products_page(
### 9. Template Rendering (Jinja2) ### 9. Template Rendering (Jinja2)
**Template** (`templates/shop/products.html`): **Template** (`templates/storefront/products.html`):
```jinja2 ```jinja2
<!DOCTYPE html> <!DOCTYPE html>
@@ -322,7 +322,7 @@ async def shop_products_page(
duration = time.time() - start_time # 0.143 seconds duration = time.time() - start_time # 0.143 seconds
logger.info( logger.info(
f"Response: 200 for GET /shop/products (0.143s)" f"Response: 200 for GET /storefront/products (0.143s)"
) )
# Add header # Add header
@@ -397,7 +397,7 @@ sequenceDiagram
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Client participant Client
participant Logging participant Logging
participant Store participant Store
participant Path participant Path
@@ -412,7 +412,7 @@ sequenceDiagram
Logging->>Store: Pass request Logging->>Store: Pass request
Store->>DB: Query store by subdomain Store->>DB: Query store by subdomain
DB-->>Store: Store object DB-->>Store: Store object
Note over Store: Set store, store_id, clean_path Note over Store: Set store, store_id, clean_path
Store->>Path: Pass request Store->>Path: Pass request
Note over Path: Path already clean Note over Path: Path already clean
Path->>Context: Pass request Path->>Context: Pass request
@@ -420,7 +420,7 @@ sequenceDiagram
Note over Context: frontend_type = STOREFRONT Note over Context: frontend_type = STOREFRONT
Context->>Theme: Pass request Context->>Theme: Pass request
Theme->>DB: Query theme Theme->>DB: Query theme
DB-->>Theme: Theme config DB-->>Theme: Theme config
Note over Theme: Set theme in request.state Note over Theme: Set theme in request.state
Theme->>Router: Route request Theme->>Router: Route request
Router->>Handler: Call handler Router->>Handler: Call handler
@@ -431,7 +431,7 @@ sequenceDiagram
``` ```
## Request State Timeline ## Request State Timeline
Showing how `request.state` is built up through the middleware stack: Showing how `request.state` is built up through the middleware stack:
``` ```
@@ -445,14 +445,14 @@ After StoreContextMiddleware:
} }
After FrontendTypeMiddleware: After FrontendTypeMiddleware:
{ {
store: <Store: Orion>, store: <Store: Orion>,
store_id: 1, store_id: 1,
clean_path: "/storefront/products", clean_path: "/storefront/products",
frontend_type: FrontendType.STOREFRONT frontend_type: FrontendType.STOREFRONT
} }
After ThemeContextMiddleware: After ThemeContextMiddleware:
{ {
store: <Store: Orion>, store: <Store: Orion>,
store_id: 1, store_id: 1,
@@ -460,7 +460,7 @@ After ThemeContextMiddleware:
frontend_type: FrontendType.STOREFRONT, frontend_type: FrontendType.STOREFRONT,
theme: { theme: {
primary_color: "#3B82F6", primary_color: "#3B82F6",
secondary_color: "#10B981", secondary_color: "#10B981",
logo_url: "/static/stores/orion/logo.png", logo_url: "/static/stores/orion/logo.png",
custom_css: "..." custom_css: "..."
} }

View File

@@ -161,7 +161,7 @@ app.middleware("http")(theme_context_middleware)
### Step 5: Create Shop Base Template ### Step 5: Create Shop Base Template
File: `app/templates/shop/base.html` File: `app/templates/storefront/base.html`
See complete template in `/home/claude/shop_base_template.html` See complete template in `/home/claude/shop_base_template.html`
@@ -184,7 +184,7 @@ See complete template in `/home/claude/shop_base_template.html`
### Step 6: Create Shop Layout JavaScript ### Step 6: Create Shop Layout JavaScript
File: `static/shop/js/shop-layout.js` File: `static/storefront/js/shop-layout.js`
See complete code in `/home/claude/shop_layout.js` See complete code in `/home/claude/shop_layout.js`

View File

@@ -42,6 +42,61 @@ http://localhost:8000/platforms/loyalty/storefront/TECHPRO/cart
--- ---
## Development URL Quick Reference
All development URLs use `http://localhost:8000` as the base.
### Login Pages
| Panel | URL | Description |
|-------|-----|-------------|
| Admin | `/admin/login` | Platform-wide admin panel |
| Merchant | `/merchants/login` | Merchant management panel |
| Store Dashboard | `/platforms/{platform_code}/store/{store_code}/login` | Store staff login |
| Storefront | `/platforms/{platform_code}/storefront/{store_code}/account/login` | Customer login |
### Key Entry Points
| Panel | URL | Description |
|-------|-----|-------------|
| Admin Dashboard | `/admin/` | Admin panel home |
| Merchant Dashboard | `/merchants/dashboard` | Merchant panel home |
| Store Dashboard | `/platforms/{platform_code}/store/{store_code}/dashboard` | Store management |
| Storefront Homepage | `/platforms/{platform_code}/storefront/{store_code}/` | Customer-facing store |
| Platform Homepage | `/platforms/{platform_code}/` | Platform marketing site |
### Full Example (OMS Platform, Store Code "ACME")
```
Admin login: http://localhost:8000/admin/login
Merchant login: http://localhost:8000/merchants/login
Store login: http://localhost:8000/platforms/oms/store/ACME/login
Store dashboard: http://localhost:8000/platforms/oms/store/ACME/dashboard
Storefront login: http://localhost:8000/platforms/oms/storefront/ACME/account/login
Storefront homepage: http://localhost:8000/platforms/oms/storefront/ACME/
Storefront products: http://localhost:8000/platforms/oms/storefront/ACME/products
Storefront cart: http://localhost:8000/platforms/oms/storefront/ACME/cart
Storefront checkout: http://localhost:8000/platforms/oms/storefront/ACME/checkout
Storefront account: http://localhost:8000/platforms/oms/storefront/ACME/account/dashboard
```
### API Endpoints
```
Admin API: http://localhost:8000/api/v1/admin/...
Store API: http://localhost:8000/api/v1/store/...
Storefront API: http://localhost:8000/api/v1/storefront/...
```
### Notes
- **Admin and Merchant** panels are global — no platform prefix needed.
- **Store and Storefront** panels require the `/platforms/{platform_code}/` prefix in development. This prefix is stripped by `PlatformContextMiddleware` before routing.
- In **production**, storefronts are accessed via subdomain (`acme.omsflow.lu/`) or custom domain. The root path `/` is the storefront.
- The storefront router is **double-mounted** at `/storefront/` and `/storefront/{store_code}/` to support both production and development modes transparently.
---
## Multi-Platform URL Routing ## Multi-Platform URL Routing
Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its own marketing site and store ecosystem. Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its own marketing site and store ecosystem.

View File

@@ -92,7 +92,7 @@ Centralized class for detecting which frontend a request targets based on URL pa
2. Path-based detection: 2. Path-based detection:
- `/admin/*`, `/api/v1/admin/*` → ADMIN - `/admin/*`, `/api/v1/admin/*` → ADMIN
- `/store/*`, `/api/v1/store/*` → STORE - `/store/*`, `/api/v1/store/*` → STORE
- `/storefront/*`, `/shop/*`, `/stores/*` → STOREFRONT - `/storefront/*`, `/stores/*` → STOREFRONT
- `/api/v1/platform/*` → PLATFORM - `/api/v1/platform/*` → PLATFORM
3. Store subdomain → STOREFRONT 3. Store subdomain → STOREFRONT
4. Store context set → STOREFRONT 4. Store context set → STOREFRONT
@@ -210,17 +210,17 @@ Middleware for request/response logging and performance monitoring.
**Modern Approach: Double Router Mounting** **Modern Approach: Double Router Mounting**
Instead of using middleware to rewrite paths, the application registers shop routes **twice** with different prefixes: Instead of using middleware to rewrite paths, the application registers storefront routes **twice** with different prefixes:
```python ```python
# In main.py # In main.py
app.include_router(shop_pages.router, prefix="/shop") app.include_router(storefront_pages.router, prefix="/storefront")
app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") app.include_router(storefront_pages.router, prefix="/stores/{store_code}/storefront")
``` ```
**How It Works:** **How It Works:**
- **Subdomain/Custom Domain Mode**: Routes match `/shop/*` prefix - **Subdomain/Custom Domain Mode**: Routes match `/storefront/*` prefix
- **Path-Based Development Mode**: Routes match `/stores/{store_code}/shop/*` prefix - **Path-Based Development Mode**: Routes match `/stores/{store_code}/storefront/*` prefix
- FastAPI handles routing naturally without path manipulation - FastAPI handles routing naturally without path manipulation
- Store code is available as a path parameter when needed - Store code is available as a path parameter when needed

View File

@@ -14,13 +14,13 @@ app/
│ ├── v1/ # Version 1 API │ ├── v1/ # Version 1 API
│ │ ├── admin/ # Admin API endpoints │ │ ├── admin/ # Admin API endpoints
│ │ ├── store/ # Store API endpoints │ │ ├── store/ # Store API endpoints
│ │ └── shop/ # Shop API endpoints │ │ └── storefront/ # Storefront API endpoints
│ └── main.py # API router configuration │ └── main.py # API router configuration
├── routes/ # Page routes (HTML) ├── routes/ # Page routes (HTML)
│ ├── admin_pages.py # Admin page routes │ ├── admin_pages.py # Admin page routes
│ ├── store_pages.py # Store page routes │ ├── store_pages.py # Store page routes
│ └── shop_pages.py # Shop page routes │ └── storefront_pages.py # Storefront page routes
├── services/ # Business logic layer ├── services/ # Business logic layer
│ ├── admin_service.py │ ├── admin_service.py
@@ -84,7 +84,7 @@ class ProductService:
product_service = ProductService() product_service = ProductService()
# Step 3: API Route (app/api/v1/shop/products.py) # Step 3: API Route (app/api/v1/storefront/products.py)
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db

View File

@@ -365,12 +365,12 @@ The following permission dependencies now use token-based store context:
- `require_all_store_permissions()` - Gets store from token, sets `request.state.store` - `require_all_store_permissions()` - Gets store from token, sets `request.state.store`
- `get_user_permissions` - Gets store from token, sets `request.state.store` - `get_user_permissions` - Gets store from token, sets `request.state.store`
### Shop Endpoints ### Storefront Endpoints
Shop endpoints (public, no authentication) still use `require_store_context()`: Storefront endpoints (public, no authentication) still use `require_store_context()`:
- `app/api/v1/shop/products.py` - Uses URL/subdomain/domain detection - `app/api/v1/storefront/products.py` - Uses URL/subdomain/domain detection
- `app/api/v1/shop/cart.py` - Uses URL/subdomain/domain detection - `app/api/v1/storefront/cart.py` - Uses URL/subdomain/domain detection
This is correct behavior - shop endpoints need to detect store from the request URL, not from JWT token. This is correct behavior - storefront endpoints need to detect store from the request URL, not from JWT token.
## Benefits of Store-in-Token ## Benefits of Store-in-Token

View File

@@ -215,7 +215,7 @@ COPY . .
# Build Tailwind CSS # Build Tailwind CSS
RUN tailwindcss -i ./static/admin/css/tailwind.css -o ./static/admin/css/tailwind.output.css --minify \ RUN tailwindcss -i ./static/admin/css/tailwind.css -o ./static/admin/css/tailwind.output.css --minify \
&& tailwindcss -i ./static/store/css/tailwind.css -o ./static/store/css/tailwind.output.css --minify \ && tailwindcss -i ./static/store/css/tailwind.css -o ./static/store/css/tailwind.output.css --minify \
&& tailwindcss -i ./static/shop/css/tailwind.css -o ./static/shop/css/tailwind.output.css --minify \ && tailwindcss -i ./static/storefront/css/tailwind.css -o ./static/storefront/css/tailwind.output.css --minify \
&& tailwindcss -i ./static/public/css/tailwind.css -o ./static/public/css/tailwind.output.css --minify && tailwindcss -i ./static/public/css/tailwind.css -o ./static/public/css/tailwind.output.css --minify
# Create non-root user # Create non-root user

View File

@@ -524,7 +524,7 @@ async def stripe_webhook(
### Checkout Process ### Checkout Process
```javascript ```javascript
// frontend/js/shop/checkout.js // frontend/js/storefront/checkout.js
class CheckoutManager { class CheckoutManager {
constructor(storeId) { constructor(storeId) {
this.storeId = storeId; this.storeId = storeId;
@@ -562,7 +562,7 @@ class CheckoutManager {
const { error } = await this.stripe.confirmPayment({ const { error } = await this.stripe.confirmPayment({
elements: this.elements, elements: this.elements,
confirmParams: { confirmParams: {
return_url: `${window.location.origin}/shop/order-confirmation`, return_url: `${window.location.origin}/storefront/order-confirmation`,
receipt_email: orderData.customerEmail receipt_email: orderData.customerEmail
} }
}); });

View File

@@ -157,7 +157,7 @@ def get_categories(...):
### 1.3 Shop Auth API (Password Reset) ### 1.3 Shop Auth API (Password Reset)
**File:** `app/api/v1/shop/auth.py` **File:** `app/api/v1/storefront/auth.py`
**Problem:** Business logic in `forgot_password()` and `reset_password()` endpoints. **Problem:** Business logic in `forgot_password()` and `reset_password()` endpoints.
@@ -391,7 +391,7 @@ async init() {
### 5.2 Shop Base Template ### 5.2 Shop Base Template
**File:** `app/templates/shop/base.html` **File:** `app/templates/storefront/base.html`
**Problem:** `request.state.language` used without default (LANG-009 violation). **Problem:** `request.state.language` used without default (LANG-009 violation).
@@ -442,12 +442,12 @@ if in_language_names_block and stripped in ("}", "]"):
| Rule ID | Description | Fixed Files | | Rule ID | Description | Fixed Files |
|---------|-------------|-------------| |---------|-------------|-------------|
| API-001 | Endpoint must use Pydantic models | admin/email_templates.py | | API-001 | Endpoint must use Pydantic models | admin/email_templates.py |
| API-003 | Endpoint must NOT contain business logic | store/settings.py, shop/auth.py | | API-003 | Endpoint must NOT contain business logic | store/settings.py, storefront/auth.py |
| JS-001 | Must use apiClient for API calls | email-templates.js (admin & store) | | JS-001 | Must use apiClient for API calls | email-templates.js (admin & store) |
| JS-002 | Must use Utils.showToast() for notifications | email-templates.js (admin & store) | | JS-002 | Must use Utils.showToast() for notifications | email-templates.js (admin & store) |
| JS-003 | Must call parent init for Alpine components | store/email-templates.js | | JS-003 | Must call parent init for Alpine components | store/email-templates.js |
| TPL-015 | Must use correct block names | admin/email-templates.html | | TPL-015 | Must use correct block names | admin/email-templates.html |
| LANG-009 | Must provide language default | shop/base.html | | LANG-009 | Must provide language default | storefront/base.html |
--- ---

View File

@@ -26,8 +26,8 @@ FastAPI routes use different authentication dependencies based on whether they s
| Dependency | Use Case | Accepts | Description | | Dependency | Use Case | Accepts | Description |
|------------|----------|---------|-------------| |------------|----------|---------|-------------|
| `get_current_customer_from_cookie_or_header` | HTML pages | Cookie OR Header | For shop account pages like `/shop/account/dashboard` | | `get_current_customer_from_cookie_or_header` | HTML pages | Cookie OR Header | For storefront account pages like `/storefront/account/dashboard` |
| `get_current_customer_api` | API endpoints | Header ONLY | For shop API routes like `/api/v1/shop/cart` | | `get_current_customer_api` | API endpoints | Header ONLY | For storefront API routes like `/api/v1/storefront/cart` |
| `get_current_customer_optional` | Login pages | Cookie OR Header | Returns `None` instead of raising exception | | `get_current_customer_optional` | Login pages | Cookie OR Header | Returns `None` instead of raising exception |
## When to Use Which? ## When to Use Which?

View File

@@ -16,16 +16,16 @@
## Key Files ## Key Files
### Created ### Created
- `app/templates/shop/account/login.html` - `app/templates/storefront/account/login.html`
- `app/templates/shop/account/register.html` - `app/templates/storefront/account/register.html`
- `app/templates/shop/account/forgot-password.html` - `app/templates/storefront/account/forgot-password.html`
- `app/templates/shop/account/dashboard.html` - `app/templates/storefront/account/dashboard.html`
### Modified ### Modified
- `app/api/v1/shop/auth.py` - Dynamic cookie paths - `app/api/v1/storefront/auth.py` - Dynamic cookie paths
- `app/api/deps.py` - Customer authentication dependency - `app/api/deps.py` - Customer authentication dependency
- `app/services/customer_service.py` - Direct JWT token creation - `app/services/customer_service.py` - Direct JWT token creation
- `app/routes/shop_pages.py` - Customer type hints - `app/routes/storefront_pages.py` - Customer type hints
- `middleware/store_context.py` - Harmonized detection methods - `middleware/store_context.py` - Harmonized detection methods
## Critical Architecture Decision ## Critical Architecture Decision
@@ -41,10 +41,10 @@ JWT tokens have `type: "customer"` to distinguish them.
```python ```python
# Domain/Subdomain access # Domain/Subdomain access
cookie_path = "/shop" cookie_path = "/storefront"
# Path-based access (/stores/orion/shop) # Path-based access (/storefront/orion)
cookie_path = f"/stores/{store_code}/shop" cookie_path = f"/storefront/{store_code}"
``` ```
## Authentication Flow ## Authentication Flow
@@ -58,7 +58,7 @@ cookie_path = f"/stores/{store_code}/shop"
## Logout Flow ## Logout Flow
1. User clicks "Logout" button → Custom Tailwind modal appears 1. User clicks "Logout" button → Custom Tailwind modal appears
2. User confirms → API call to `/api/v1/shop/auth/logout` 2. User confirms → API call to `/api/v1/storefront/auth/logout`
3. Cookie deleted, localStorage cleared 3. Cookie deleted, localStorage cleared
4. Success toast shown, redirect to login page 4. Success toast shown, redirect to login page
@@ -68,9 +68,9 @@ cookie_path = f"/stores/{store_code}/shop"
``` ```
# Path-based access # Path-based access
http://localhost:8000/stores/orion/shop/account/login http://localhost:8000/storefront/orion/account/login
http://localhost:8000/stores/orion/shop/account/register http://localhost:8000/storefront/orion/account/register
http://localhost:8000/stores/orion/shop/account/dashboard http://localhost:8000/storefront/orion/account/dashboard
``` ```
## Next Steps (TODO) ## Next Steps (TODO)

View File

@@ -5,11 +5,11 @@
## Overview ## Overview
This document describes the implementation of customer authentication for the shop frontend, including login, registration, and account management pages. This work creates a complete separation between customer authentication and admin/store authentication systems. This document describes the implementation of customer authentication for the storefront frontend, including login, registration, and account management pages. This work creates a complete separation between customer authentication and admin/store authentication systems.
## Problem Statement ## Problem Statement
The shop frontend needed proper authentication pages (login, registration, forgot password) and a working customer authentication system. The initial implementation had several issues: The storefront frontend needed proper authentication pages (login, registration, forgot password) and a working customer authentication system. The initial implementation had several issues:
1. No styled authentication pages for customers 1. No styled authentication pages for customers
2. Customer authentication was incorrectly trying to use the User model (admins/stores) 2. Customer authentication was incorrectly trying to use the User model (admins/stores)
@@ -27,7 +27,7 @@ The shop frontend needed proper authentication pages (login, registration, forgo
- Have `username` field - Have `username` field
- Managed via `app/services/auth_service.py` - Managed via `app/services/auth_service.py`
- **Customers** (`models/database/customer.py`): Shop customers - **Customers** (`models/database/customer.py`): Storefront customers
- Store-scoped (each store has independent customers) - Store-scoped (each store has independent customers)
- No `role` or `username` fields - No `role` or `username` fields
- Have `customer_number`, `total_orders`, store relationship - Have `customer_number`, `total_orders`, store relationship
@@ -56,9 +56,9 @@ Cookies must be set with paths that match how the store is accessed:
| Access Method | Example URL | Cookie Path | | Access Method | Example URL | Cookie Path |
|--------------|-------------|-------------| |--------------|-------------|-------------|
| Domain | `orion.lu/shop/account/login` | `/shop` | | Domain | `orion.lu/storefront/account/login` | `/storefront` |
| Subdomain | `orion.localhost/shop/account/login` | `/shop` | | Subdomain | `orion.localhost/storefront/account/login` | `/storefront` |
| Path-based | `localhost/stores/orion/shop/account/login` | `/stores/orion/shop` | | Path-based | `localhost/stores/orion/storefront/account/login` | `/storefront/orion` |
This ensures cookies are only sent to the correct store's routes. This ensures cookies are only sent to the correct store's routes.
@@ -66,32 +66,32 @@ This ensures cookies are only sent to the correct store's routes.
### Files Created ### Files Created
1. **`app/templates/shop/account/login.html`** 1. **`app/templates/storefront/account/login.html`**
- Customer login page - Customer login page
- Extends `shop/base.html` (follows design pattern) - Extends `storefront/base.html` (follows design pattern)
- Uses Tailwind CSS and Alpine.js - Uses Tailwind CSS and Alpine.js
- Theme-aware styling with CSS variables - Theme-aware styling with CSS variables
- Two-column layout (branding + form) - Two-column layout (branding + form)
- Form validation and error handling - Form validation and error handling
2. **`app/templates/shop/account/register.html`** 2. **`app/templates/storefront/account/register.html`**
- Customer registration page - Customer registration page
- Fields: first_name, last_name, email, phone (optional), password - Fields: first_name, last_name, email, phone (optional), password
- Client-side validation - Client-side validation
- Marketing consent checkbox - Marketing consent checkbox
- Theme integration - Theme integration
3. **`app/templates/shop/account/forgot-password.html`** 3. **`app/templates/storefront/account/forgot-password.html`**
- Password reset request page - Password reset request page
- Two-state UI (form → success) - Two-state UI (form → success)
- Email validation - Email validation
4. **`app/templates/shop/account/dashboard.html`** 4. **`app/templates/storefront/account/dashboard.html`**
- Customer account dashboard - Customer account dashboard
- Displays account summary, order statistics - Displays account summary, order statistics
- Quick links to orders, profile, addresses - Quick links to orders, profile, addresses
- Logout functionality - Logout functionality
- Follows shop design pattern (extends base.html) - Follows storefront design pattern (extends base.html)
### Important Schema Change ### Important Schema Change
@@ -117,7 +117,7 @@ class UserLogin(BaseModel):
**Impact**: This change affects all login endpoints: **Impact**: This change affects all login endpoints:
- Admin login: `/api/v1/admin/auth/login` - Admin login: `/api/v1/admin/auth/login`
- Store login: `/api/v1/store/auth/login` - Store login: `/api/v1/store/auth/login`
- Customer login: `/api/v1/shop/auth/login` - Customer login: `/api/v1/storefront/auth/login`
**Updated Files**: **Updated Files**:
- `app/services/auth_service.py` - Changed `user_credentials.username` to `user_credentials.email_or_username` - `app/services/auth_service.py` - Changed `user_credentials.username` to `user_credentials.email_or_username`
@@ -127,7 +127,7 @@ class UserLogin(BaseModel):
### Files Modified ### Files Modified
#### 1. `app/api/v1/shop/auth.py` #### 1. `app/api/v1/storefront/auth.py`
**Changes**: **Changes**:
- Added `CustomerLoginResponse` model (uses `CustomerResponse` instead of `UserResponse`) - Added `CustomerLoginResponse` model (uses `CustomerResponse` instead of `UserResponse`)
@@ -143,11 +143,11 @@ class UserLogin(BaseModel):
store_context = getattr(request.state, 'store_context', None) store_context = getattr(request.state, 'store_context', None)
access_method = store_context.get('detection_method', 'unknown') if store_context else 'unknown' access_method = store_context.get('detection_method', 'unknown') if store_context else 'unknown'
cookie_path = "/shop" # Default for domain/subdomain access cookie_path = "/storefront" # Default for domain/subdomain access
if access_method == "path": if access_method == "path":
# For path-based access like /stores/orion/shop # For path-based access like /storefront/orion
full_prefix = store_context.get('full_prefix', '/store/') if store_context else '/store/' full_prefix = store_context.get('full_prefix', '/store/') if store_context else '/store/'
cookie_path = f"{full_prefix}{store.subdomain}/shop" cookie_path = f"/storefront/{store.subdomain}"
response.set_cookie( response.set_cookie(
key="customer_token", key="customer_token",
@@ -232,7 +232,7 @@ def get_current_customer_from_cookie_or_header(
return customer # Returns Customer, not User return customer # Returns Customer, not User
``` ```
#### 4. `app/routes/shop_pages.py` #### 4. `app/routes/storefront_pages.py`
**Changes**: **Changes**:
- Changed import from `User` to `Customer` - Changed import from `User` to `Customer`
@@ -300,35 +300,35 @@ The implementation properly supports all three store access methods:
#### Domain-based Access #### Domain-based Access
``` ```
URL: https://orion.lu/shop/account/login URL: https://orion.lu/storefront/account/login
Cookie Path: /shop Cookie Path: /storefront
Cookie Sent To: https://orion.lu/shop/* Cookie Sent To: https://orion.lu/storefront/*
``` ```
#### Subdomain-based Access #### Subdomain-based Access
``` ```
URL: https://orion.myplatform.com/shop/account/login URL: https://orion.myplatform.com/storefront/account/login
Cookie Path: /shop Cookie Path: /storefront
Cookie Sent To: https://orion.myplatform.com/shop/* Cookie Sent To: https://orion.myplatform.com/storefront/*
``` ```
#### Path-based Access #### Path-based Access
``` ```
URL: https://myplatform.com/stores/orion/shop/account/login URL: https://myplatform.com/storefront/orion/account/login
Cookie Path: /stores/orion/shop Cookie Path: /storefront/orion
Cookie Sent To: https://myplatform.com/stores/orion/shop/* Cookie Sent To: https://myplatform.com/storefront/orion/*
``` ```
## Authentication Flow ## Authentication Flow
### Login Flow ### Login Flow
1. **User loads login page**`GET /stores/orion/shop/account/login` 1. **User loads login page**`GET /storefront/orion/account/login`
- Middleware detects store from path - Middleware detects store from path
- Sets `detection_method = "path"` in store_context - Sets `detection_method = "path"` in store_context
- Renders login template - Renders login template
2. **User submits credentials**`POST /api/v1/shop/auth/login` 2. **User submits credentials**`POST /api/v1/storefront/auth/login`
- Middleware detects store from Referer header - Middleware detects store from Referer header
- Sets `detection_method = "path"` (harmonized!) - Sets `detection_method = "path"` (harmonized!)
- Validates credentials via `customer_service.login_customer()` - Validates credentials via `customer_service.login_customer()`
@@ -337,7 +337,7 @@ Cookie Sent To: https://myplatform.com/stores/orion/shop/*
- Sets `customer_token` cookie with correct path - Sets `customer_token` cookie with correct path
- Returns token + customer data - Returns token + customer data
3. **Browser redirects to dashboard**`GET /stores/orion/shop/account/dashboard` 3. **Browser redirects to dashboard**`GET /storefront/orion/account/dashboard`
- Browser sends `customer_token` cookie (path matches!) - Browser sends `customer_token` cookie (path matches!)
- Dependency `get_current_customer_from_cookie_or_header` extracts token - Dependency `get_current_customer_from_cookie_or_header` extracts token
- Decodes JWT, validates `type == "customer"` - Decodes JWT, validates `type == "customer"`
@@ -352,7 +352,7 @@ Cookie Sent To: https://myplatform.com/stores/orion/shop/*
- Smooth animations with transitions - Smooth animations with transitions
- Dark mode support - Dark mode support
2. **User confirms logout**`POST /api/v1/shop/auth/logout` 2. **User confirms logout**`POST /api/v1/storefront/auth/logout`
- Calculates cookie path (same logic as login) - Calculates cookie path (same logic as login)
- Deletes cookie with matching path - Deletes cookie with matching path
- Returns success message - Returns success message
@@ -374,7 +374,7 @@ response.set_cookie(
secure=True, # HTTPS only (production/staging) secure=True, # HTTPS only (production/staging)
samesite="lax", # CSRF protection samesite="lax", # CSRF protection
max_age=1800, # 30 minutes (matches JWT expiry) max_age=1800, # 30 minutes (matches JWT expiry)
path=cookie_path, # Restricted to store's shop routes path=cookie_path, # Restricted to store's storefront routes
) )
``` ```
@@ -395,10 +395,10 @@ response.set_cookie(
### Frontend Templates ### Frontend Templates
All authentication pages follow the shop template pattern: All authentication pages follow the storefront template pattern:
```jinja2 ```jinja2
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
{% block title %}Page Title{% endblock %} {% block title %}Page Title{% endblock %}
@@ -540,9 +540,9 @@ function accountDashboard() {
3. **Session Management**: No refresh tokens, single JWT with 30-minute expiry 3. **Session Management**: No refresh tokens, single JWT with 30-minute expiry
4. **Account Pages Are Placeholders**: 4. **Account Pages Are Placeholders**:
- `/account/orders` - needs order history implementation - `/storefront/account/orders` - needs order history implementation
- `/account/profile` - needs profile editing implementation - `/storefront/account/profile` - needs profile editing implementation
- `/account/addresses` - needs address management implementation - `/storefront/account/addresses` - needs address management implementation
## Future Enhancements ## Future Enhancements
@@ -580,11 +580,11 @@ function accountDashboard() {
- **Customer Model**: `models/database/customer.py` - **Customer Model**: `models/database/customer.py`
- **Customer Service**: `app/services/customer_service.py` - **Customer Service**: `app/services/customer_service.py`
- **Auth Endpoints**: `app/api/v1/shop/auth.py` - **Auth Endpoints**: `app/api/v1/storefront/auth.py`
- **Auth Dependencies**: `app/api/deps.py` - **Auth Dependencies**: `app/api/deps.py`
- **Shop Routes**: `app/routes/shop_pages.py` - **Storefront Routes**: `app/routes/storefront_pages.py`
- **Store Context**: `middleware/store_context.py` - **Store Context**: `middleware/store_context.py`
- **Templates**: `app/templates/shop/account/` - **Templates**: `app/templates/storefront/account/`
## Deployment Notes ## Deployment Notes
@@ -604,10 +604,10 @@ No migrations required. Uses existing `customer` table.
Ensure these files exist: Ensure these files exist:
- `static/shared/js/log-config.js` - `static/shared/js/log-config.js`
- `static/shared/js/icons.js` - `static/shared/js/icons.js`
- `static/shop/js/shop-layout.js` - `static/storefront/js/storefront-layout.js`
- `static/shared/js/utils.js` - `static/shared/js/utils.js`
- `static/shared/js/api-client.js` - `static/shared/js/api-client.js`
- `static/shop/css/shop.css` - `static/storefront/css/storefront.css`
## Troubleshooting ## Troubleshooting

View File

@@ -217,10 +217,10 @@ python scripts/seed_database.py [--reset] [--minimal]
- Username: `admin` - Username: `admin`
- Password: `admin123` - Password: `admin123`
### Store Shops ### Store Storefronts
- ORION: `http://localhost:8000/shop/ORION` - ORION: `http://localhost:8000/storefront/ORION`
- FASHIONHUB: `http://localhost:8000/shop/FASHIONHUB` - FASHIONHUB: `http://localhost:8000/storefront/FASHIONHUB`
- BOOKSTORE: `http://localhost:8000/shop/BOOKSTORE` - BOOKSTORE: `http://localhost:8000/storefront/BOOKSTORE`
### Theme Editors ### Theme Editors
- ORION Theme: `http://localhost:8000/admin/stores/ORION/theme` - ORION Theme: `http://localhost:8000/admin/stores/ORION/theme`
@@ -383,7 +383,7 @@ This creates 7 platform default pages that all stores inherit:
- **Platform Defaults**: Created with `store_id=NULL`, available to all stores - **Platform Defaults**: Created with `store_id=NULL`, available to all stores
- **Store Overrides**: Stores can create custom versions with the same slug - **Store Overrides**: Stores can create custom versions with the same slug
- **Automatic Fallback**: System checks store override first, falls back to platform default - **Automatic Fallback**: System checks store override first, falls back to platform default
- **Navigation**: Pages marked with `show_in_footer` appear in shop footer automatically - **Navigation**: Pages marked with `show_in_footer` appear in storefront footer automatically
- **Idempotent**: Script skips pages that already exist - **Idempotent**: Script skips pages that already exist
**Access URLs:** **Access URLs:**

View File

@@ -2,7 +2,7 @@
**Version:** 1.0.0 **Version:** 1.0.0
**Last Updated:** 2025 **Last Updated:** 2025
**Status:** Phase 1 Complete (Admin), Phase 2-3 Pending (Store, Shop) **Status:** Phase 1 Complete (Admin), Phase 2-3 Pending (Store, Storefront)
--- ---
@@ -27,16 +27,16 @@
### Purpose ### Purpose
The error handling system provides context-aware error responses throughout the application. It automatically determines whether to return JSON (for API calls) or HTML error pages (for browser requests), and renders appropriate error pages based on the request context (Admin, Store Dashboard, or Shop). The error handling system provides context-aware error responses throughout the application. It automatically determines whether to return JSON (for API calls) or HTML error pages (for browser requests), and renders appropriate error pages based on the request context (Admin, Store Dashboard, or Storefront).
### Key Features ### Key Features
- **Context-Aware**: Different error pages for Admin, Store, and Shop areas - **Context-Aware**: Different error pages for Admin, Store, and Storefront areas
- **Automatic Detection**: Distinguishes between API and HTML page requests - **Automatic Detection**: Distinguishes between API and HTML page requests
- **Consistent JSON API**: API endpoints always return standardized JSON errors - **Consistent JSON API**: API endpoints always return standardized JSON errors
- **Fallback Mechanism**: Gracefully handles missing templates - **Fallback Mechanism**: Gracefully handles missing templates
- **Debug Mode**: Shows technical details to admin users only - **Debug Mode**: Shows technical details to admin users only
- **Theme Integration**: Shop error pages support store theming (Phase 3) - **Theme Integration**: Storefront error pages support store theming (Phase 3)
- **Security**: 401 errors automatically redirect to appropriate login pages - **Security**: 401 errors automatically redirect to appropriate login pages
### Design Principles ### Design Principles
@@ -58,7 +58,7 @@ HTTP Request
Store Context Middleware (detects store from domain/subdomain/path) Store Context Middleware (detects store from domain/subdomain/path)
Context Detection Middleware (detects API/Admin/Store/Shop) Context Detection Middleware (detects API/Admin/Store/Storefront)
Route Handler (processes request, may throw exception) Route Handler (processes request, may throw exception)
@@ -84,7 +84,7 @@ app/exceptions/
app/templates/ app/templates/
├── admin/errors/ # Admin error pages (Phase 1 - COMPLETE) ├── admin/errors/ # Admin error pages (Phase 1 - COMPLETE)
├── store/errors/ # Store error pages (Phase 2 - PENDING) ├── store/errors/ # Store error pages (Phase 2 - PENDING)
├── shop/errors/ # Shop error pages (Phase 3 - PENDING) ├── storefront/errors/ # Storefront error pages (Phase 3 - PENDING)
└── shared/ # Shared fallback error pages (COMPLETE) └── shared/ # Shared fallback error pages (COMPLETE)
``` ```
@@ -169,8 +169,8 @@ ErrorPageRenderer.render_error_page(
"show_debug": bool, # Whether to show debug info "show_debug": bool, # Whether to show debug info
"context_type": str, # Request context type "context_type": str, # Request context type
"path": str, # Request path "path": str, # Request path
"store": dict, # Store info (shop context only) "store": dict, # Store info (storefront context only)
"theme": dict, # Theme data (shop context only) "theme": dict, # Theme data (storefront context only)
} }
``` ```
@@ -223,7 +223,7 @@ if path.startswith("/admin") or host.startswith("admin."):
if path.startswith("/store/"): if path.startswith("/store/"):
return RequestContext.STORE_DASHBOARD return RequestContext.STORE_DASHBOARD
# Priority 4: Shop Context # Priority 4: Storefront Context
if hasattr(request.state, 'store') and request.state.store: if hasattr(request.state, 'store') and request.state.store:
return RequestContext.SHOP return RequestContext.SHOP
@@ -255,7 +255,7 @@ admin.platform.com/dashboard → ADMIN context
store1.platform.com/store/dashboard → STORE_DASHBOARD context store1.platform.com/store/dashboard → STORE_DASHBOARD context
``` ```
**Shop Access:** **Storefront Access:**
``` ```
store1.platform.com/ → SHOP context store1.platform.com/ → SHOP context
customdomain.com/ → SHOP context (if store verified) customdomain.com/ → SHOP context (if store verified)
@@ -320,7 +320,7 @@ API endpoints MUST always return JSON, even if the client sends `Accept: text/ht
|---------|-------------| |---------|-------------|
| ADMIN | `/admin/login` | | ADMIN | `/admin/login` |
| STORE_DASHBOARD | `/store/login` | | STORE_DASHBOARD | `/store/login` |
| SHOP | `/shop/login` | | SHOP | `/storefront/login` |
| FALLBACK | `/admin/login` | | FALLBACK | `/admin/login` |
--- ---
@@ -350,7 +350,7 @@ app/templates/
│ └── errors/ │ └── errors/
│ └── (same structure as admin) │ └── (same structure as admin)
├── shop/ ├── storefront/
│ └── errors/ │ └── errors/
│ └── (same structure as admin) │ └── (same structure as admin)
@@ -446,7 +446,7 @@ Each context has its own `base.html` template:
- Primary: "Go to Dashboard" → `/store/dashboard` - Primary: "Go to Dashboard" → `/store/dashboard`
- Secondary: "Go Back" → `javascript:history.back()` - Secondary: "Go Back" → `javascript:history.back()`
#### Shop Error Pages #### Storefront Error Pages
**Purpose:** Error pages for customers on store storefronts **Purpose:** Error pages for customers on store storefronts
@@ -454,16 +454,16 @@ Each context has its own `base.html` template:
- **Uses store theme** (colors, logo, fonts) - **Uses store theme** (colors, logo, fonts)
- Customer-friendly language - Customer-friendly language
- No technical jargon - No technical jargon
- Links to shop homepage - Links to storefront homepage
- Customer support contact - Customer support contact
**Action Buttons:** **Action Buttons:**
- Primary: "Continue Shopping" → Shop homepage - Primary: "Continue Shopping" → Storefront homepage
- Secondary: "Contact Support" → Store support page - Secondary: "Contact Support" → Store support page
**Theme Integration:** **Theme Integration:**
```html ```html
<!-- Shop error pages use store theme variables --> <!-- Storefront error pages use store theme variables -->
<style> <style>
:root { :root {
--color-primary: {{ theme.colors.primary }}; --color-primary: {{ theme.colors.primary }};
@@ -517,12 +517,12 @@ Each context has its own `base.html` template:
**Priority:** Medium (stores currently see fallback pages) **Priority:** Medium (stores currently see fallback pages)
### Phase 3: Shop Error Handling ⏳ PENDING ### Phase 3: Storefront Error Handling ⏳ PENDING
**Status:** Not yet implemented **Status:** Not yet implemented
**Required Tasks:** **Required Tasks:**
1. Create `/app/templates/shop/errors/` directory 1. Create `/app/templates/storefront/errors/` directory
2. Create customer-facing error templates: 2. Create customer-facing error templates:
- Use customer-friendly language - Use customer-friendly language
- Integrate store theme variables - Integrate store theme variables
@@ -675,7 +675,7 @@ raise ValidationException(
Determine which context needs the new error page: Determine which context needs the new error page:
- Admin Portal → `admin/errors/` - Admin Portal → `admin/errors/`
- Store Dashboard → `store/errors/` - Store Dashboard → `store/errors/`
- Customer Shop → `shop/errors/` - Customer Storefront → `storefront/errors/`
### Step 2: Choose Template Type ### Step 2: Choose Template Type
@@ -734,7 +734,7 @@ Determine which context needs the new error page:
**Option B: Standalone Template** (For custom designs) **Option B: Standalone Template** (For custom designs)
```html ```html
<!-- app/templates/shop/errors/custom.html --> <!-- app/templates/storefront/errors/custom.html -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -930,16 +930,16 @@ curl -H "Accept: text/html" http://localhost:8000/store/dashboard
# Expected: 302 redirect to /store/login # Expected: 302 redirect to /store/login
``` ```
**Shop Context (Phase 3):** **Storefront Context (Phase 3):**
```bash ```bash
# Test 404 on store subdomain # Test 404 on store subdomain
curl -H "Accept: text/html" http://store1.localhost:8000/nonexistent curl -H "Accept: text/html" http://store1.localhost:8000/nonexistent
# Expected: Shop 404 page with store theme # Expected: Storefront 404 page with store theme
# Test 500 error # Test 500 error
# Trigger server error on shop # Trigger server error on storefront
# Expected: Shop 500 page with store branding # Expected: Storefront 500 page with store branding
``` ```
### Performance Testing ### Performance Testing
@@ -1089,7 +1089,7 @@ templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
### Issue: Wrong Context Detected ### Issue: Wrong Context Detected
**Symptoms:** **Symptoms:**
Admin page shows shop error page, or vice versa Admin page shows storefront error page, or vice versa
**Diagnosis:** **Diagnosis:**
Add context logging: Add context logging:
@@ -1154,10 +1154,10 @@ Ensure all conditions for HTML page request are met:
- Accept header includes `text/html` - Accept header includes `text/html`
- NOT already on login page - NOT already on login page
### Issue: Store Theme Not Applied to Shop Errors ### Issue: Store Theme Not Applied to Storefront Errors
**Symptoms:** **Symptoms:**
Shop error pages don't use store colors/branding (Phase 3 issue) Storefront error pages don't use store colors/branding (Phase 3 issue)
**Diagnosis:** **Diagnosis:**
1. Check if theme is in request state: 1. Check if theme is in request state:

View File

@@ -91,7 +91,7 @@
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Admin │ │ Store │ │ Shop │ Admin │ │ Store │ │ Storefront
│ Error │ │ Error │ │ Error │ │ Error │ │ Error │ │ Error │
│ Page │ │ Page │ │ Page │ │ Page │ │ Page │ │ Page │
│ │ │ │ │ (Themed) │ │ │ │ │ │ (Themed) │
@@ -159,8 +159,8 @@ For a 404 error in ADMIN context:
For a 429 error in SHOP context (not created yet): For a 429 error in SHOP context (not created yet):
``` ```
1. Check: app/templates/shop/errors/429.html ✗ Missing 1. Check: app/templates/storefront/errors/429.html ✗ Missing
2. Check: app/templates/shop/errors/generic.html ✗ Missing 2. Check: app/templates/storefront/errors/generic.html ✗ Missing
3. Check: app/templates/shared/429-fallback.html ✗ Missing 3. Check: app/templates/shared/429-fallback.html ✗ Missing
4. Check: app/templates/shared/generic-fallback.html ✓ EXISTS → USE THIS 4. Check: app/templates/shared/generic-fallback.html ✓ EXISTS → USE THIS
``` ```
@@ -189,7 +189,7 @@ For a 429 error in SHOP context (not created yet):
│ error_code│ │ │ │ Location: │ │ error_code│ │ │ │ Location: │
│ message │ │ Context- │ │ /admin/login│ │ message │ │ Context- │ │ /admin/login│
│ status │ │ aware │ │ /store/login│ │ status │ │ aware │ │ /store/login│
│ details │ │ template │ │ /shop/login │ │ details │ │ template │ │ /storefront/login │
│ } │ │ │ │ │ │ } │ │ │ │ │
└────────────┘ └────────────┘ └──────────────┘ └────────────┘ └────────────┘ └──────────────┘
``` ```
@@ -213,12 +213,12 @@ Context: ADMIN
Result: HTML admin/errors/404.html Result: HTML admin/errors/404.html
``` ```
### Scenario 3: Shop Page 500 Error ### Scenario 3: Storefront Page 500 Error
``` ```
Request: GET /products/123 (on store1.platform.com) Request: GET /products/123 (on store1.platform.com)
Accept: text/html Accept: text/html
Context: SHOP (store detected) Context: SHOP (store detected)
Result: HTML shop/errors/500.html (with store theme) Result: HTML storefront/errors/500.html (with store theme)
``` ```
### Scenario 4: Unauthorized Access to Admin ### Scenario 4: Unauthorized Access to Admin
@@ -245,7 +245,7 @@ Result: 302 Redirect to /admin/login
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Admin User │ │ Other Users │ │ Admin User │ │ Other Users │
│ (Context: │ │ (Store, │ │ (Context: │ │ (Store, │
│ ADMIN) │ │ Shop) │ ADMIN) │ │ Storefront)
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
▼ ▼ ▼ ▼
@@ -280,8 +280,8 @@ app/
│ ├── store/ │ ├── store/
│ │ └── errors/ # TODO: Store error pages │ │ └── errors/ # TODO: Store error pages
│ │ │ │
│ ├── shop/ │ ├── storefront/
│ │ └── errors/ # TODO: Shop error pages (themed) │ │ └── errors/ # TODO: Storefront error pages (themed)
│ │ │ │
│ └── shared/ │ └── shared/
│ └── *-fallback.html # Shared fallback error pages │ └── *-fallback.html # Shared fallback error pages
@@ -303,13 +303,13 @@ middleware/
**Professional**: Polished error pages matching area design **Professional**: Polished error pages matching area design
**Flexible**: Fallback mechanism ensures errors always render **Flexible**: Fallback mechanism ensures errors always render
**Secure**: Debug info only shown to admins **Secure**: Debug info only shown to admins
**Themed**: Shop errors can use store branding (Phase 3) **Themed**: Storefront errors can use store branding (Phase 3)
--- ---
This flow ensures that: This flow ensures that:
1. API calls ALWAYS get JSON responses 1. API calls ALWAYS get JSON responses
2. HTML page requests get appropriate error pages 2. HTML page requests get appropriate error pages
3. Each context (admin/store/shop) has its own error design 3. Each context (admin/store/storefront) has its own error design
4. Fallback mechanism prevents broken error pages 4. Fallback mechanism prevents broken error pages
5. 401 errors redirect to appropriate login pages 5. 401 errors redirect to appropriate login pages

View File

@@ -10,7 +10,7 @@ This guide shows you how to implement the Content Management System for static p
**Service Layer**: `app/services/content_page_service.py` **Service Layer**: `app/services/content_page_service.py`
**Admin API**: `app/api/v1/admin/content_pages.py` **Admin API**: `app/api/v1/admin/content_pages.py`
**Store API**: `app/api/v1/store/content_pages.py` **Store API**: `app/api/v1/store/content_pages.py`
**Shop API**: `app/api/v1/shop/content_pages.py` **Storefront API**: `app/api/v1/storefront/content_pages.py`
**Documentation**: Full CMS documentation in `docs/features/content-management-system.md` **Documentation**: Full CMS documentation in `docs/features/content-management-system.md`
## Next Steps to Activate ## Next Steps to Activate
@@ -65,20 +65,20 @@ api_router.include_router(
) )
``` ```
**Shop Router** (`app/api/v1/shop/__init__.py` or create if doesn't exist): **Storefront Router** (`app/api/v1/storefront/__init__.py` or create if doesn't exist):
```python ```python
from app.api.v1.shop import content_pages from app.api.v1.storefront import content_pages
api_router.include_router( api_router.include_router(
content_pages.router, content_pages.router,
prefix="/content-pages", prefix="/content-pages",
tags=["shop-content-pages"] tags=["storefront-content-pages"]
) )
``` ```
### 4. Update Shop Routes to Use CMS ### 4. Update Storefront Routes to Use CMS
Edit `app/routes/shop_pages.py` to add a generic content page handler: Edit `app/routes/storefront_pages.py` to add a generic content page handler:
```python ```python
from app.services.content_page_service import content_page_service from app.services.content_page_service import content_page_service
@@ -107,18 +107,18 @@ async def generic_content_page(
raise HTTPException(status_code=404, detail=f"Page not found: {slug}") raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
return templates.TemplateResponse( return templates.TemplateResponse(
"shop/content-page.html", "storefront/content-page.html",
get_shop_context(request, page=page) get_storefront_context(request, page=page)
) )
``` ```
### 5. Create Generic Content Page Template ### 5. Create Generic Content Page Template
Create `app/templates/shop/content-page.html`: Create `app/templates/storefront/content-page.html`:
```jinja2 ```jinja2
{# app/templates/shop/content-page.html #} {# app/templates/storefront/content-page.html #}
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
{% block title %}{{ page.title }}{% endblock %} {% block title %}{{ page.title }}{% endblock %}
@@ -159,14 +159,14 @@ Create `app/templates/shop/content-page.html`:
### 6. Update Footer to Load Navigation Dynamically ### 6. Update Footer to Load Navigation Dynamically
Edit `app/templates/shop/base.html` to load navigation from database. Edit `app/templates/storefront/base.html` to load navigation from database.
First, update the context helper to include footer pages: First, update the context helper to include footer pages:
```python ```python
# app/routes/shop_pages.py # app/routes/storefront_pages.py
def get_shop_context(request: Request, **extra_context) -> dict: def get_storefront_context(request: Request, **extra_context) -> dict:
# ... existing code ... # ... existing code ...
# Load footer navigation pages # Load footer navigation pages
@@ -198,7 +198,7 @@ def get_shop_context(request: Request, **extra_context) -> dict:
Then update the footer template: Then update the footer template:
```jinja2 ```jinja2
{# app/templates/shop/base.html - Footer section #} {# app/templates/storefront/base.html - Footer section #}
<div> <div>
<h4 class="font-semibold mb-4">Quick Links</h4> <h4 class="font-semibold mb-4">Quick Links</h4>
@@ -367,7 +367,7 @@ curl -X POST http://localhost:8000/api/v1/admin/content-pages/platform \
"show_in_footer": true "show_in_footer": true
}' }'
# View in shop # View in storefront
curl http://localhost:8000/store/orion/about curl http://localhost:8000/store/orion/about
``` ```
@@ -385,7 +385,7 @@ curl -X POST http://localhost:8000/api/v1/store/orion/content-pages/ \
"is_published": true "is_published": true
}' }'
# View in shop (should show store content) # View in storefront (should show store content)
curl http://localhost:8000/store/orion/about curl http://localhost:8000/store/orion/about
``` ```
@@ -396,7 +396,7 @@ curl http://localhost:8000/store/orion/about
curl -X DELETE http://localhost:8000/api/v1/store/orion/content-pages/{id} \ curl -X DELETE http://localhost:8000/api/v1/store/orion/content-pages/{id} \
-H "Authorization: Bearer <store_token>" -H "Authorization: Bearer <store_token>"
# View in shop (should fall back to platform default) # View in storefront (should fall back to platform default)
curl http://localhost:8000/store/orion/about curl http://localhost:8000/store/orion/about
``` ```

View File

@@ -204,12 +204,12 @@ DELETE /api/v1/store/{code}/content-pages/15
After deletion, platform default will be shown again. After deletion, platform default will be shown again.
### Shop Frontend (Public) ### Storefront (Public)
**1. Get Page Content** **1. Get Page Content**
```bash ```bash
GET /api/v1/shop/content-pages/about GET /api/v1/storefront/content-pages/about
``` ```
Automatically uses store context from middleware: Automatically uses store context from middleware:
@@ -221,12 +221,12 @@ Automatically uses store context from middleware:
```bash ```bash
# Get all navigation pages # Get all navigation pages
GET /api/v1/shop/content-pages/navigation GET /api/v1/storefront/content-pages/navigation
# Filter by placement # Filter by placement
GET /api/v1/shop/content-pages/navigation?header_only=true GET /api/v1/storefront/content-pages/navigation?header_only=true
GET /api/v1/shop/content-pages/navigation?footer_only=true GET /api/v1/storefront/content-pages/navigation?footer_only=true
GET /api/v1/shop/content-pages/navigation?legal_only=true GET /api/v1/storefront/content-pages/navigation?legal_only=true
``` ```
Returns published pages filtered by navigation placement. Returns published pages filtered by navigation placement.
@@ -246,10 +246,10 @@ app/
│ │ └── content_pages.py ← Admin API endpoints │ │ └── content_pages.py ← Admin API endpoints
│ ├── store/ │ ├── store/
│ │ └── content_pages.py ← Store API endpoints │ │ └── content_pages.py ← Store API endpoints
│ └── shop/ │ └── storefront/
│ └── content_pages.py ← Public API endpoints │ └── content_pages.py ← Public API endpoints
└── templates/shop/ └── templates/storefront/
├── about.html ← Content page template ├── about.html ← Content page template
├── faq.html ├── faq.html
├── contact.html ├── contact.html
@@ -263,8 +263,8 @@ app/
Create a reusable template for all content pages: Create a reusable template for all content pages:
```jinja2 ```jinja2
{# app/templates/shop/content-page.html #} {# app/templates/storefront/content-page.html #}
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
{% block title %}{{ page.title }}{% endblock %} {% block title %}{{ page.title }}{% endblock %}
@@ -310,7 +310,7 @@ Create a reusable template for all content pages:
### Route Handler ### Route Handler
```python ```python
# app/routes/shop_pages.py # app/routes/storefront_pages.py
from app.services.content_page_service import content_page_service from app.services.content_page_service import content_page_service
@@ -339,8 +339,8 @@ async def content_page(
raise HTTPException(status_code=404, detail=f"Page not found: {slug}") raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
return templates.TemplateResponse( return templates.TemplateResponse(
"shop/content-page.html", "storefront/content-page.html",
get_shop_context(request, page=page) get_storefront_context(request, page=page)
) )
``` ```
@@ -349,7 +349,7 @@ async def content_page(
Update footer to load links from database: Update footer to load links from database:
```jinja2 ```jinja2
{# app/templates/shop/base.html #} {# app/templates/storefront/base.html #}
<footer> <footer>
<div class="grid grid-cols-3"> <div class="grid grid-cols-3">
@@ -552,11 +552,11 @@ PUT /api/v1/store/{code}/content-pages/{id} # Update store page
DELETE /api/v1/store/{code}/content-pages/{id} # Delete store page DELETE /api/v1/store/{code}/content-pages/{id} # Delete store page
``` ```
### Shop (Public) Endpoints ### Storefront (Public) Endpoints
``` ```
GET /api/v1/shop/content-pages/navigation # Get navigation links GET /api/v1/storefront/content-pages/navigation # Get navigation links
GET /api/v1/shop/content-pages/{slug} # Get page content GET /api/v1/storefront/content-pages/{slug} # Get page content
``` ```
## Example: Complete Workflow ## Example: Complete Workflow

View File

@@ -497,12 +497,12 @@ content_page_service.create_page(
**Get page content:** **Get page content:**
```bash ```bash
GET /api/v1/shop/content-pages/about GET /api/v1/storefront/content-pages/about
``` ```
**Get navigation:** **Get navigation:**
```bash ```bash
GET /api/v1/shop/content-pages/navigation GET /api/v1/storefront/content-pages/navigation
``` ```
--- ---
@@ -681,11 +681,11 @@ PUT /api/v1/admin/content-pages/{id} # Update page
DELETE /api/v1/admin/content-pages/{id} # Delete page DELETE /api/v1/admin/content-pages/{id} # Delete page
``` ```
### Shop (Public) Endpoints ### Storefront (Public) Endpoints
``` ```
GET /api/v1/shop/content-pages/navigation # Get nav links GET /api/v1/storefront/content-pages/navigation # Get nav links
GET /api/v1/shop/content-pages/{slug} # Get page content GET /api/v1/storefront/content-pages/{slug} # Get page content
``` ```
--- ---

View File

@@ -4,7 +4,7 @@ Complete guide to creating custom landing pages for store storefronts.
## Overview ## Overview
Each store can have a custom landing page at their root URL with different design templates. This landing page serves as the store's homepage, separate from the e-commerce shop section. Each store can have a custom landing page at their root URL with different design templates. This landing page serves as the store's homepage, separate from the e-commerce storefront section.
### URL Structure ### URL Structure
@@ -14,10 +14,10 @@ Root Landing Page:
- Subdomain: https://orion.platform.com/ → Landing Page - Subdomain: https://orion.platform.com/ → Landing Page
- Path-based: http://localhost:8000/stores/orion/ → Landing Page - Path-based: http://localhost:8000/stores/orion/ → Landing Page
E-commerce Shop: E-commerce Storefront:
- Custom Domain: https://customdomain.com/shop/ → Shop Homepage - Custom Domain: https://customdomain.com/storefront/ → Storefront Homepage
- Subdomain: https://orion.platform.com/shop/ → Shop Homepage - Subdomain: https://orion.platform.com/storefront/ → Storefront Homepage
- Path-based: http://localhost:8000/stores/orion/shop/ → Shop Homepage - Path-based: http://localhost:8000/storefront/orion/ → Storefront Homepage
``` ```
## Features ## Features
@@ -25,7 +25,7 @@ E-commerce Shop:
**Multiple Templates**: Choose from 4 different landing page designs **Multiple Templates**: Choose from 4 different landing page designs
**CMS-Powered**: Content managed through ContentPage model **CMS-Powered**: Content managed through ContentPage model
**Per-Store Customization**: Each store can have unique design **Per-Store Customization**: Each store can have unique design
**Auto-Fallback**: Redirects to shop if no landing page exists **Auto-Fallback**: Redirects to storefront if no landing page exists
**Theme-Aware**: Uses store's theme colors and branding **Theme-Aware**: Uses store's theme colors and branding
## Available Templates ## Available Templates
@@ -112,7 +112,7 @@ Set `is_published=True` to make the landing page live.
If no landing page exists: If no landing page exists:
1. System checks for `slug="landing"` 1. System checks for `slug="landing"`
2. If not found, checks for `slug="home"` 2. If not found, checks for `slug="home"`
3. If neither exists, **redirects to `/shop/`** 3. If neither exists, **redirects to `/storefront/`**
This ensures stores always have a working homepage even without a landing page. This ensures stores always have a working homepage even without a landing page.
@@ -169,7 +169,7 @@ ADD COLUMN template VARCHAR(50) NOT NULL DEFAULT 'default';
### Get Landing Page ### Get Landing Page
```http ```http
GET /api/v1/shop/content-pages/landing GET /api/v1/storefront/content-pages/landing
Referer: https://orion.platform.com/ Referer: https://orion.platform.com/
Response: Response:
@@ -195,7 +195,7 @@ Response:
### Test Fallback ### Test Fallback
1. Delete or unpublish landing page 1. Delete or unpublish landing page
2. Access store root URL 2. Access store root URL
3. Should redirect to `/shop/` 3. Should redirect to `/storefront/`
## Customization Guide ## Customization Guide
@@ -206,9 +206,9 @@ Response:
app/templates/store/landing-{name}.html app/templates/store/landing-{name}.html
``` ```
2. Extend shop base: 2. Extend storefront base:
```jinja2 ```jinja2
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
``` ```
3. Use template variables as needed 3. Use template variables as needed
@@ -238,7 +238,7 @@ Edit directly and changes apply immediately (no rebuild needed).
2. **Keep Content Concise**: Landing pages should be scannable 2. **Keep Content Concise**: Landing pages should be scannable
3. **Strong CTAs**: Always link to `/shop/` for e-commerce 3. **Strong CTAs**: Always link to `/storefront/` for e-commerce
4. **Use Theme Colors**: Templates automatically use store theme 4. **Use Theme Colors**: Templates automatically use store theme

View File

@@ -189,7 +189,7 @@ app/templates/shared/
### Shop Frontend ### Shop Frontend
**Template:** `app/templates/shop/base.html` **Template:** `app/templates/storefront/base.html`
```html ```html
{# Lines 41-42: Tailwind CSS fallback #} {# Lines 41-42: Tailwind CSS fallback #}

View File

@@ -300,7 +300,7 @@ The system automatically detects which frontend based on URL:
|----------|----------|--------| |----------|----------|--------|
| `/admin/*` | admin | `ADMIN:` | | `/admin/*` | admin | `ADMIN:` |
| `/store/*` | store | `STORE:` | | `/store/*` | store | `STORE:` |
| `/shop/*` | shop | `SHOP:` | | `/storefront/*` | storefront | `SHOP:` |
--- ---

View File

@@ -327,13 +327,13 @@ Tab navigation components for switching between content sections.
--- ---
## E-commerce Components (Shop Frontend) ## E-commerce Components (Storefront Frontend)
Reusable macros for shop/storefront functionality. Located in `app/templates/shared/macros/shop/`. Reusable macros for storefront functionality. Located in `app/templates/shared/macros/storefront/`.
### 🛍️ Product Card ### 🛍️ Product Card
```html ```html
{% from 'shared/macros/shop/product-card.html' import product_card %} {% from 'shared/macros/storefront/product-card.html' import product_card %}
{# Basic product card #} {# Basic product card #}
{{ product_card(product_var='product') }} {{ product_card(product_var='product') }}
@@ -360,7 +360,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🛍️ Product Grid ### 🛍️ Product Grid
```html ```html
{% from 'shared/macros/shop/product-grid.html' import product_grid %} {% from 'shared/macros/storefront/product-grid.html' import product_grid %}
{# Basic grid #} {# Basic grid #}
{{ product_grid(products_var='products', loading_var='loading') }} {{ product_grid(products_var='products', loading_var='loading') }}
@@ -381,7 +381,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🛒 Add to Cart ### 🛒 Add to Cart
```html ```html
{% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_button, add_to_cart_form, buy_now_button %} {% from 'shared/macros/storefront/add-to-cart.html' import add_to_cart_button, add_to_cart_form, buy_now_button %}
{# Simple button #} {# Simple button #}
{{ add_to_cart_button(action='addToCart()') }} {{ add_to_cart_button(action='addToCart()') }}
@@ -397,11 +397,11 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
- `add_to_cart_button()` - Simple button with loading state - `add_to_cart_button()` - Simple button with loading state
- `add_to_cart_form()` - Form with quantity selector + button - `add_to_cart_form()` - Form with quantity selector + button
- `buy_now_button()` - Direct checkout button - `buy_now_button()` - Direct checkout button
- `shop_quantity_selector()` - Stock-aware quantity input - `storefront_quantity_selector()` - Stock-aware quantity input
### 🛒 Mini Cart (Header) ### 🛒 Mini Cart (Header)
```html ```html
{% from 'shared/macros/shop/mini-cart.html' import mini_cart, mini_cart_icon %} {% from 'shared/macros/storefront/mini-cart.html' import mini_cart, mini_cart_icon %}
{# Complete mini cart with dropdown #} {# Complete mini cart with dropdown #}
{{ mini_cart(cart_var='cart', show_var='showCart') }} {{ mini_cart(cart_var='cart', show_var='showCart') }}
@@ -419,7 +419,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🖼️ Product Gallery (Priority 3) ### 🖼️ Product Gallery (Priority 3)
```html ```html
{% from 'shared/macros/shop/product-gallery.html' import product_gallery %} {% from 'shared/macros/storefront/product-gallery.html' import product_gallery %}
{# Full gallery with thumbnails, zoom, and lightbox #} {# Full gallery with thumbnails, zoom, and lightbox #}
{{ product_gallery(images_var='product.images', enable_zoom=true, enable_lightbox=true) }} {{ product_gallery(images_var='product.images', enable_zoom=true, enable_lightbox=true) }}
@@ -436,7 +436,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🎨 Variant Selector (Priority 3) ### 🎨 Variant Selector (Priority 3)
```html ```html
{% from 'shared/macros/shop/variant-selector.html' import size_selector, color_swatches %} {% from 'shared/macros/storefront/variant-selector.html' import size_selector, color_swatches %}
{# Size buttons with size guide link #} {# Size buttons with size guide link #}
{{ size_selector(sizes_var='product.sizes', show_guide=true) }} {{ size_selector(sizes_var='product.sizes', show_guide=true) }}
@@ -453,7 +453,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 📄 Product Info (Priority 3) ### 📄 Product Info (Priority 3)
```html ```html
{% from 'shared/macros/shop/product-info.html' import product_info, product_price %} {% from 'shared/macros/storefront/product-info.html' import product_info, product_price %}
{# Complete info block #} {# Complete info block #}
{{ product_info(product_var='product', show_store=true, show_rating=true) }} {{ product_info(product_var='product', show_store=true, show_rating=true) }}
@@ -473,7 +473,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 📑 Product Tabs (Priority 3) ### 📑 Product Tabs (Priority 3)
```html ```html
{% from 'shared/macros/shop/product-tabs.html' import product_tabs %} {% from 'shared/macros/storefront/product-tabs.html' import product_tabs %}
{# Tabbed product information #} {# Tabbed product information #}
{{ product_tabs( {{ product_tabs(
@@ -486,7 +486,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 📂 Category Navigation (Priority 4) ### 📂 Category Navigation (Priority 4)
```html ```html
{% from 'shared/macros/shop/category-nav.html' import category_nav, category_tree, category_menu %} {% from 'shared/macros/storefront/category-nav.html' import category_nav, category_tree, category_menu %}
{# Sidebar with nested categories #} {# Sidebar with nested categories #}
{{ category_nav(categories_var='categories', current_var='currentCategory', show_count=true) }} {{ category_nav(categories_var='categories', current_var='currentCategory', show_count=true) }}
@@ -502,10 +502,10 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🍞 Breadcrumbs (Priority 4) ### 🍞 Breadcrumbs (Priority 4)
```html ```html
{% from 'shared/macros/shop/breadcrumbs.html' import shop_breadcrumbs, compact_breadcrumbs %} {% from 'shared/macros/storefront/breadcrumbs.html' import storefront_breadcrumbs, compact_breadcrumbs %}
{# Static breadcrumbs #} {# Static breadcrumbs #}
{{ shop_breadcrumbs(items=[ {{ storefront_breadcrumbs(items=[
{'label': 'Electronics', 'url': '/electronics'}, {'label': 'Electronics', 'url': '/electronics'},
{'label': 'Audio', 'url': '/audio'}, {'label': 'Audio', 'url': '/audio'},
{'label': 'Headphones'} {'label': 'Headphones'}
@@ -515,11 +515,11 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
{{ compact_breadcrumbs(parent={'label': 'Audio', 'url': '/audio'}, current='Headphones') }} {{ compact_breadcrumbs(parent={'label': 'Audio', 'url': '/audio'}, current='Headphones') }}
``` ```
**Macros:** `shop_breadcrumbs()`, `auto_breadcrumbs()`, `compact_breadcrumbs()` **Macros:** `storefront_breadcrumbs()`, `auto_breadcrumbs()`, `compact_breadcrumbs()`
### 🔍 Search Bar (Priority 4) ### 🔍 Search Bar (Priority 4)
```html ```html
{% from 'shared/macros/shop/search-bar.html' import search_bar, search_autocomplete %} {% from 'shared/macros/storefront/search-bar.html' import search_bar, search_autocomplete %}
{# Basic search #} {# Basic search #}
{{ search_bar(placeholder='Search products...') }} {{ search_bar(placeholder='Search products...') }}
@@ -535,7 +535,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🎛️ Filter Sidebar (Priority 4) ### 🎛️ Filter Sidebar (Priority 4)
```html ```html
{% from 'shared/macros/shop/filter-sidebar.html' import filter_sidebar, price_filter, sort_dropdown %} {% from 'shared/macros/storefront/filter-sidebar.html' import filter_sidebar, price_filter, sort_dropdown %}
{# Complete filter panel #} {# Complete filter panel #}
{{ filter_sidebar(filters_var='filters', active_filters_var='activeFilters', on_change='filterProducts()') }} {{ filter_sidebar(filters_var='filters', active_filters_var='activeFilters', on_change='filterProducts()') }}
@@ -551,7 +551,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### ⭐ Star Rating (Priority 5) ### ⭐ Star Rating (Priority 5)
```html ```html
{% from 'shared/macros/shop/star-rating.html' import star_rating, rating_input, rating_summary, compact_rating %} {% from 'shared/macros/storefront/star-rating.html' import star_rating, rating_input, rating_summary, compact_rating %}
{# Static star rating with half-star support #} {# Static star rating with half-star support #}
{{ star_rating(rating=4.5, show_value=true, show_count=true, count=127) }} {{ star_rating(rating=4.5, show_value=true, show_count=true, count=127) }}
@@ -573,7 +573,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 💬 Reviews (Priority 5) ### 💬 Reviews (Priority 5)
```html ```html
{% from 'shared/macros/shop/reviews.html' import review_card, review_list, review_form, review_summary_section %} {% from 'shared/macros/storefront/reviews.html' import review_card, review_list, review_form, review_summary_section %}
{# Single review card with helpful buttons #} {# Single review card with helpful buttons #}
{{ review_card(review_var='review', on_helpful='markHelpful(review.id)') }} {{ review_card(review_var='review', on_helpful='markHelpful(review.id)') }}
@@ -592,7 +592,7 @@ Reusable macros for shop/storefront functionality. Located in `app/templates/sha
### 🛡️ Trust Badges (Priority 5) ### 🛡️ Trust Badges (Priority 5)
```html ```html
{% from 'shared/macros/shop/trust-badges.html' import trust_badges, trust_banner, payment_icons, guarantee_badge, security_seals, checkout_trust_section %} {% from 'shared/macros/storefront/trust-badges.html' import trust_badges, trust_banner, payment_icons, guarantee_badge, security_seals, checkout_trust_section %}
{# Trust badges grid #} {# Trust badges grid #}
{{ trust_badges(badges=['secure_payment', 'free_shipping', 'easy_returns', 'support_24_7'], layout='grid') }} {{ trust_badges(badges=['secure_payment', 'free_shipping', 'easy_returns', 'support_24_7'], layout='grid') }}

View File

@@ -1,12 +1,12 @@
╔══════════════════════════════════════════════════════════════════╗ ╔══════════════════════════════════════════════════════════════════╗
║ SHOP FRONTEND ARCHITECTURE OVERVIEW ║ ║ STOREFRONT FRONTEND ARCHITECTURE OVERVIEW ║
║ Alpine.js + Jinja2 + Tailwind CSS + Multi-Theme ║ ║ Alpine.js + Jinja2 + Tailwind CSS + Multi-Theme ║
╚══════════════════════════════════════════════════════════════════╝ ╚══════════════════════════════════════════════════════════════════╝
📦 WHAT IS THIS? 📦 WHAT IS THIS?
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
Customer-facing shop frontend provides visitors with a branded Customer-facing storefront frontend provides visitors with a branded
e-commerce experience unique to each store. Built with: e-commerce experience unique to each store. Built with:
✅ Jinja2 Templates (server-side rendering) ✅ Jinja2 Templates (server-side rendering)
✅ Alpine.js (client-side reactivity) ✅ Alpine.js (client-side reactivity)
@@ -46,7 +46,7 @@ e-commerce experience unique to each store. Built with:
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
app/ app/
├── templates/shop/ ├── templates/storefront/
│ ├── base.html ← ✅ Base template (layout + theme) │ ├── base.html ← ✅ Base template (layout + theme)
│ ├── home.html ← ✅ Homepage / featured products │ ├── home.html ← ✅ Homepage / featured products
│ ├── products.html ← ✅ Product catalog with filters │ ├── products.html ← ✅ Product catalog with filters
@@ -67,11 +67,11 @@ app/
│ ├── 404.html │ ├── 404.html
│ └── 500.html │ └── 500.html
├── static/shop/ ├── static/storefront/
│ ├── css/ │ ├── css/
│ │ └── shop.css ← ✅ Shop-specific styles (IMPLEMENTED) │ │ └── storefront.css ← ✅ Storefront-specific styles (IMPLEMENTED)
│ ├── js/ │ ├── js/
│ │ └── shop-layout.js ← ✅ Base shop functionality (IMPLEMENTED) │ │ └── storefront-layout.js ← ✅ Base storefront functionality (IMPLEMENTED)
│ └── img/ │ └── img/
│ └── (placeholder images) │ └── (placeholder images)
@@ -85,7 +85,7 @@ app/
│ └── (shared styles if needed) │ └── (shared styles if needed)
└── routes/ └── routes/
└── shop_pages.py ← ✅ Route handlers (IMPLEMENTED) └── storefront_pages.py ← ✅ Route handlers (IMPLEMENTED)
🏗️ ARCHITECTURE LAYERS 🏗️ ARCHITECTURE LAYERS
@@ -107,37 +107,37 @@ Layer 6: Database
Layer 1: ROUTES (FastAPI) Layer 1: ROUTES (FastAPI)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Purpose: Store Detection + Template Rendering Purpose: Store Detection + Template Rendering
Location: app/routes/shop_pages.py Location: app/routes/storefront_pages.py
⚠️ ROUTE REGISTRATION (main.py): ⚠️ ROUTE REGISTRATION (main.py):
The shop router is mounted at TWO prefixes to support both access methods: The storefront router is mounted at TWO prefixes to support both access methods:
# main.py # main.py
app.include_router(shop_pages.router, prefix="/shop", ...) # Domain/subdomain app.include_router(storefront_pages.router, prefix="/storefront", ...) # Domain/subdomain
app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop", ...) # Path-based app.include_router(storefront_pages.router, prefix="/storefront/{store_code}", ...) # Path-based
This means routes defined WITHOUT /shop prefix in shop_pages.py: This means routes defined WITHOUT /storefront prefix in storefront_pages.py:
@router.get("/products") → /shop/products OR /stores/{code}/shop/products @router.get("/products") → /storefront/products OR /storefront/{code}/products
❌ COMMON MISTAKE: Don't add /shop prefix in route definitions! ❌ COMMON MISTAKE: Don't add /storefront prefix in route definitions!
@router.get("/shop/products") ❌ WRONG - creates /shop/shop/products @router.get("/storefront/products") ❌ WRONG - creates /storefront/storefront/products
@router.get("/products") ✅ CORRECT - creates /shop/products @router.get("/products") ✅ CORRECT - creates /storefront/products
Example Route Handler: Example Route Handler:
@router.get("/", response_class=HTMLResponse, include_in_schema=False) @router.get("/", response_class=HTMLResponse, include_in_schema=False)
@router.get("/products", response_class=HTMLResponse, include_in_schema=False) @router.get("/products", response_class=HTMLResponse, include_in_schema=False)
async def shop_products_page(request: Request): async def storefront_products_page(request: Request):
""" """
Render shop homepage / product catalog. Render storefront homepage / product catalog.
Store and theme are auto-injected by middleware. Store and theme are auto-injected by middleware.
""" """
return templates.TemplateResponse( return templates.TemplateResponse(
"shop/products.html", "storefront/products.html",
get_shop_context(request) # Helper function get_storefront_context(request) # Helper function
) )
Helper Function: Helper Function:
def get_shop_context(request: Request, **extra_context) -> dict: def get_storefront_context(request: Request, **extra_context) -> dict:
"""Build template context with store/theme from middleware""" """Build template context with store/theme from middleware"""
store = getattr(request.state, 'store', None) store = getattr(request.state, 'store', None)
theme = getattr(request.state, 'theme', None) theme = getattr(request.state, 'theme', None)
@@ -176,39 +176,39 @@ Responsibilities:
⭐ MULTI-ACCESS ROUTING (Domain, Subdomain, Path-Based) ⭐ MULTI-ACCESS ROUTING (Domain, Subdomain, Path-Based)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
The shop frontend supports THREE access methods: The storefront frontend supports THREE access methods:
1. **Custom Domain** (Production) 1. **Custom Domain** (Production)
URL: https://customdomain.com/shop/products URL: https://customdomain.com/storefront/products
- Store has their own domain - Store has their own domain
- base_url = "/" - base_url = "/"
- Links: /shop/products, /shop/about, /shop/contact - Links: /storefront/products, /storefront/about, /storefront/contact
2. **Subdomain** (Production) 2. **Subdomain** (Production)
URL: https://orion.letzshop.com/shop/products URL: https://orion.letzshop.com/storefront/products
- Store uses platform subdomain - Store uses platform subdomain
- base_url = "/" - base_url = "/"
- Links: /shop/products, /shop/about, /shop/contact - Links: /storefront/products, /storefront/about, /storefront/contact
3. **Path-Based** (Development/Testing) 3. **Path-Based** (Development/Testing)
URL: http://localhost:8000/stores/orion/shop/products URL: http://localhost:8000/storefront/orion/products
- Store accessed via path prefix - Store accessed via path prefix
- base_url = "/stores/orion/" - base_url = "/storefront/orion/"
- Links: /stores/orion/shop/products, /stores/orion/shop/about - Links: /storefront/orion/products, /storefront/orion/about
⚠️ CRITICAL: All template links MUST use {{ base_url }}shop/ prefix ⚠️ CRITICAL: All template links MUST use {{ base_url }}storefront/ prefix
Example: Example:
❌ BAD: <a href="/products">Products</a> ❌ BAD: <a href="/products">Products</a>
❌ BAD: <a href="{{ base_url }}products">Products</a> ❌ BAD: <a href="{{ base_url }}products">Products</a>
✅ GOOD: <a href="{{ base_url }}shop/products">Products</a> ✅ GOOD: <a href="{{ base_url }}storefront/products">Products</a>
Note: The router is mounted at /shop prefix in main.py, so all links need shop/ after base_url Note: The router is mounted at /storefront prefix in main.py, so all links need storefront/ after base_url
How It Works: How It Works:
1. StoreContextMiddleware detects access method 1. StoreContextMiddleware detects access method
2. Sets request.state.store_context with detection_method 2. Sets request.state.store_context with detection_method
3. get_shop_context() calculates base_url from detection_method 3. get_storefront_context() calculates base_url from detection_method
4. Templates use {{ base_url }} for all internal links 4. Templates use {{ base_url }} for all internal links
5. Links work correctly regardless of access method 5. Links work correctly regardless of access method
@@ -243,7 +243,7 @@ Detection Methods:
Layer 3: TEMPLATES (Jinja2) Layer 3: TEMPLATES (Jinja2)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Purpose: HTML Structure + Store Branding Purpose: HTML Structure + Store Branding
Location: app/templates/shop/ Location: app/templates/storefront/
Template Hierarchy: Template Hierarchy:
base.html (layout + theme injection) base.html (layout + theme injection)
@@ -253,7 +253,7 @@ Template Hierarchy:
partials/product-card.html (components) partials/product-card.html (components)
Example: Example:
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
{% block title %}{{ store.name }}{% endblock %} {% block title %}{{ store.name }}{% endblock %}
{% block alpine_data %}shopHome(){% endblock %} {% block alpine_data %}shopHome(){% endblock %}
{% block content %} {% block content %}
@@ -276,7 +276,7 @@ Key Features:
Layer 4: JAVASCRIPT (Alpine.js) Layer 4: JAVASCRIPT (Alpine.js)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Purpose: Client-Side Interactivity + Cart + Search Purpose: Client-Side Interactivity + Cart + Search
Location: app/static/shop/js/ Location: app/static/storefront/js/
⚠️ CRITICAL: JavaScript Loading Order ⚠️ CRITICAL: JavaScript Loading Order
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
@@ -284,14 +284,14 @@ Scripts MUST load in this exact order (see base.html):
1. log-config.js ← Logging system (loads first) 1. log-config.js ← Logging system (loads first)
2. icons.js ← Icon registry 2. icons.js ← Icon registry
3. shop-layout.js ← Alpine component (before Alpine!) 3. storefront-layout.js ← Alpine component (before Alpine!)
4. utils.js ← Utility functions 4. utils.js ← Utility functions
5. api-client.js ← API wrapper 5. api-client.js ← API wrapper
6. Alpine.js (deferred) ← Loads last 6. Alpine.js (deferred) ← Loads last
7. Page-specific JS ← Optional page scripts 7. Page-specific JS ← Optional page scripts
Why This Order Matters: Why This Order Matters:
• shop-layout.js defines storefrontLayoutData() BEFORE Alpine initializes • storefront-layout.js defines storefrontLayoutData() BEFORE Alpine initializes
• Alpine.js defers to ensure DOM is ready • Alpine.js defers to ensure DOM is ready
• Shared utilities available to all scripts • Shared utilities available to all scripts
• Icons and logging available immediately • Icons and logging available immediately
@@ -299,7 +299,7 @@ Why This Order Matters:
Example from base.html: Example from base.html:
<script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script> <script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
<script src="{{ url_for('static', path='shared/js/icons.js') }}"></script> <script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
<script src="{{ url_for('static', path='shop/js/shop-layout.js') }}"></script> <script src="{{ url_for('static', path='storefront/js/storefront-layout.js') }}"></script>
<script src="{{ url_for('static', path='shared/js/utils.js') }}"></script> <script src="{{ url_for('static', path='shared/js/utils.js') }}"></script>
<script src="{{ url_for('static', path='shared/js/api-client.js') }}"></script> <script src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@@ -307,14 +307,14 @@ Example from base.html:
Alpine.js Component Architecture: Alpine.js Component Architecture:
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
⭐ BASE COMPONENT (shop-layout.js): ⭐ BASE COMPONENT (storefront-layout.js):
Provides shared functionality for all shop pages: Provides shared functionality for all storefront pages:
function storefrontLayoutData() { function storefrontLayoutData() {
return { return {
// Theme state // Theme state
dark: localStorage.getItem('shop-theme') === 'dark', dark: localStorage.getItem('storefront-theme') === 'dark',
// UI state // UI state
mobileMenuOpen: false, mobileMenuOpen: false,
@@ -325,12 +325,12 @@ Provides shared functionality for all shop pages:
cart: [], cart: [],
init() { init() {
shopLog.info('Shop layout initializing...'); shopLog.info('Storefront layout initializing...');
this.loadCart(); this.loadCart();
window.addEventListener('cart-updated', () => { window.addEventListener('cart-updated', () => {
this.loadCart(); this.loadCart();
}); });
shopLog.info('Shop layout initialized'); shopLog.info('Storefront layout initialized');
}, },
addToCart(product, quantity = 1) { addToCart(product, quantity = 1) {
@@ -356,7 +356,7 @@ Provides shared functionality for all shop pages:
toggleTheme() { toggleTheme() {
this.dark = !this.dark; this.dark = !this.dark;
localStorage.setItem('shop-theme', localStorage.setItem('storefront-theme',
this.dark ? 'dark' : 'light'); this.dark ? 'dark' : 'light');
shopLog.debug('Theme toggled:', this.dark ? 'dark' : 'light'); shopLog.debug('Theme toggled:', this.dark ? 'dark' : 'light');
}, },
@@ -393,7 +393,7 @@ Each page extends storefrontLayoutData() for page-specific functionality:
// Page-specific methods // Page-specific methods
async loadProducts() { async loadProducts() {
const response = await fetch('/api/v1/shop/products'); const response = await fetch('/api/v1/storefront/products');
const data = await response.json(); const data = await response.json();
this.products = data.products; this.products = data.products;
this.loading = false; this.loading = false;
@@ -454,30 +454,30 @@ Responsibilities:
Layer 5: API (REST) Layer 5: API (REST)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Purpose: Product Data + Cart + Orders Purpose: Product Data + Cart + Orders
Location: app/api/v1/shop/*.py Location: app/api/v1/storefront/*.py
⭐ NEW API STRUCTURE (as of 2025-11-22): ⭐ NEW API STRUCTURE (as of 2025-11-22):
All shop endpoints use middleware-based store context. All storefront endpoints use middleware-based store context.
NO store_id or store_code in URLs! NO store_id or store_code in URLs!
Example Endpoints: Example Endpoints:
GET /api/v1/shop/products ← Product catalog GET /api/v1/storefront/products ← Product catalog
GET /api/v1/shop/products/{id} ← Product details GET /api/v1/storefront/products/{id} ← Product details
GET /api/v1/shop/products?search=... ← Search products GET /api/v1/storefront/products?search=... ← Search products
GET /api/v1/shop/cart/{session_id} ← Get cart GET /api/v1/storefront/cart/{session_id} ← Get cart
POST /api/v1/shop/cart/{session_id}/items ← Add to cart POST /api/v1/storefront/cart/{session_id}/items ← Add to cart
PUT /api/v1/shop/cart/{session_id}/items/{product_id} ← Update item PUT /api/v1/storefront/cart/{session_id}/items/{product_id} ← Update item
DELETE /api/v1/shop/cart/{session_id}/items/{product_id} ← Remove item DELETE /api/v1/storefront/cart/{session_id}/items/{product_id} ← Remove item
POST /api/v1/shop/orders ← Place order (auth required) POST /api/v1/storefront/orders ← Place order (auth required)
GET /api/v1/shop/orders ← Order history (auth required) GET /api/v1/storefront/orders ← Order history (auth required)
POST /api/v1/shop/auth/login ← Customer login POST /api/v1/storefront/auth/login ← Customer login
POST /api/v1/shop/auth/register ← Customer registration POST /api/v1/storefront/auth/register ← Customer registration
GET /api/v1/shop/content-pages/navigation ← CMS navigation GET /api/v1/storefront/content-pages/navigation ← CMS navigation
GET /api/v1/shop/content-pages/{slug} ← CMS page content GET /api/v1/storefront/content-pages/{slug} ← CMS page content
How Store Context Works: How Store Context Works:
1. Browser makes API call from shop page (e.g., /stores/orion/shop/products) 1. Browser makes API call from storefront page (e.g., /storefront/orion/products)
2. Browser automatically sends Referer header: http://localhost:8000/stores/orion/shop/products 2. Browser automatically sends Referer header: http://localhost:8000/storefront/orion/products
3. StoreContextMiddleware extracts store from Referer header 3. StoreContextMiddleware extracts store from Referer header
4. Middleware sets request.state.store = <Store: orion> 4. Middleware sets request.state.store = <Store: orion>
5. API endpoint accesses store: store = request.state.store 5. API endpoint accesses store: store = request.state.store
@@ -489,13 +489,13 @@ How Store Context Works:
Page Load Flow: Page Load Flow:
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
1. Customer → visits acme-shop.com (or /stores/acme/shop/products) 1. Customer → visits acme-shop.com (or /storefront/acme/products)
2. Store Middleware → Identifies "ACME" store from domain/path 2. Store Middleware → Identifies "ACME" store from domain/path
3. Theme Middleware → Loads ACME's theme config 3. Theme Middleware → Loads ACME's theme config
4. FastAPI → Renders shop/products.html 4. FastAPI → Renders storefront/products.html
5. Browser → Receives HTML with theme CSS variables 5. Browser → Receives HTML with theme CSS variables
6. Alpine.js → init() executes 6. Alpine.js → init() executes
7. JavaScript → GET /api/v1/shop/products (with Referer header) 7. JavaScript → GET /api/v1/storefront/products (with Referer header)
8. Middleware → Extracts store from Referer, injects into request.state 8. Middleware → Extracts store from Referer, injects into request.state
9. API → Returns product list JSON for ACME store 9. API → Returns product list JSON for ACME store
10. Alpine.js → Updates products array 10. Alpine.js → Updates products array
@@ -516,7 +516,7 @@ Checkout Flow:
1. Customer → Goes to /cart 1. Customer → Goes to /cart
2. Page → Loads cart from localStorage 2. Page → Loads cart from localStorage
3. Customer → Fills checkout form 3. Customer → Fills checkout form
4. Alpine.js → POST /api/v1/shop/orders (with Referer header) 4. Alpine.js → POST /api/v1/storefront/orders (with Referer header)
5. Middleware → Extracts store from Referer 5. Middleware → Extracts store from Referer
6. API → Creates order + payment intent for store 6. API → Creates order + payment intent for store
7. Alpine.js → Redirects to payment 7. Alpine.js → Redirects to payment
@@ -621,7 +621,7 @@ Key Functions:
Cart Persistence: Cart Persistence:
• Survives page refresh • Survives page refresh
• Shared across shop pages • Shared across storefront pages
• Cleared on checkout completion • Cleared on checkout completion
• Synced across tabs (optional) • Synced across tabs (optional)
@@ -637,7 +637,7 @@ Search System:
• Keyboard shortcuts (Cmd+K) • Keyboard shortcuts (Cmd+K)
2. Search API 2. Search API
POST /api/v1/shop/{store_code}/search POST /api/v1/storefront/{store_code}/search
{ {
"query": "laptop", "query": "laptop",
"category": "electronics", "category": "electronics",
@@ -730,10 +730,10 @@ Account Features:
✅ Profile management ✅ Profile management
Auth Flow: Auth Flow:
1. Login/Register → POST /api/v1/shop/auth/login (with Referer header) 1. Login/Register → POST /api/v1/storefront/auth/login (with Referer header)
2. Middleware → Extracts store from Referer 2. Middleware → Extracts store from Referer
3. API → Validates credentials for store's customers 3. API → Validates credentials for store's customers
4. API → Returns JWT token + sets cookie (path=/shop) 4. API → Returns JWT token + sets cookie (path=/storefront)
5. JavaScript → Store token in localStorage 5. JavaScript → Store token in localStorage
6. API Client → Add token to authenticated requests 6. API Client → Add token to authenticated requests
7. Optional → Use account features (orders, profile, etc.) 7. Optional → Use account features (orders, profile, etc.)
@@ -745,9 +745,9 @@ Auth Flow:
All authentication pages use Tailwind CSS, Alpine.js, and theme integration All authentication pages use Tailwind CSS, Alpine.js, and theme integration
for a consistent, branded experience across all stores. for a consistent, branded experience across all stores.
✅ Login Page (app/templates/shop/account/login.html) ✅ Login Page (app/templates/storefront/account/login.html)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Route: /shop/account/login Route: /storefront/account/login
Features: Features:
• Two-column layout (branding + form) • Two-column layout (branding + form)
@@ -773,7 +773,7 @@ Alpine.js Component:
dark: false, dark: false,
async handleLogin() { async handleLogin() {
// POST /api/v1/shop/auth/login // POST /api/v1/storefront/auth/login
// Store token in localStorage // Store token in localStorage
// Redirect to account or return URL // Redirect to account or return URL
} }
@@ -781,13 +781,13 @@ Alpine.js Component:
} }
API Endpoint: API Endpoint:
POST /api/v1/shop/auth/login POST /api/v1/storefront/auth/login
Body: { email_or_username, password } Body: { email_or_username, password }
Returns: { access_token, user } Returns: { access_token, user }
✅ Register Page (app/templates/shop/account/register.html) ✅ Register Page (app/templates/storefront/account/register.html)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Route: /shop/account/register Route: /storefront/account/register
Features: Features:
• Two-column layout with store branding • Two-column layout with store branding
@@ -824,20 +824,20 @@ Alpine.js Component:
}, },
async handleRegister() { async handleRegister() {
// POST /api/v1/shop/auth/register // POST /api/v1/storefront/auth/register
// Redirect to login?registered=true // Redirect to login?registered=true
} }
} }
} }
API Endpoint: API Endpoint:
POST /api/v1/shop/auth/register POST /api/v1/storefront/auth/register
Body: { first_name, last_name, email, phone?, password, marketing_consent } Body: { first_name, last_name, email, phone?, password, marketing_consent }
Returns: { message } Returns: { message }
✅ Forgot Password Page (app/templates/shop/account/forgot-password.html) ✅ Forgot Password Page (app/templates/storefront/account/forgot-password.html)
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
Route: /shop/account/forgot-password Route: /storefront/account/forgot-password
Features: Features:
• Two-column layout with store branding • Two-column layout with store branding
@@ -848,7 +848,7 @@ Features:
• Success state with checkmark icon • Success state with checkmark icon
• Option to retry if email not received • Option to retry if email not received
• Theme-aware styling • Theme-aware styling
• Links back to login and shop • Links back to login and storefront
• Dark mode support • Dark mode support
Alpine.js Component: Alpine.js Component:
@@ -859,7 +859,7 @@ Alpine.js Component:
loading: false, loading: false,
async handleSubmit() { async handleSubmit() {
// POST /api/v1/shop/auth/forgot-password // POST /api/v1/storefront/auth/forgot-password
// Show success message // Show success message
// emailSent = true // emailSent = true
} }
@@ -867,7 +867,7 @@ Alpine.js Component:
} }
API Endpoint: API Endpoint:
POST /api/v1/shop/auth/forgot-password POST /api/v1/storefront/auth/forgot-password
Body: { email } Body: { email }
Returns: { message } Returns: { message }
@@ -907,7 +907,7 @@ Key Theme Elements:
Benefits: Benefits:
✅ Each store's auth pages match their brand ✅ Each store's auth pages match their brand
✅ Consistent with main shop design ✅ Consistent with main storefront design
✅ Dark mode adapts to store colors ✅ Dark mode adapts to store colors
✅ Professional, polished appearance ✅ Professional, polished appearance
@@ -959,17 +959,17 @@ No store_code needed! Store extracted from Referer header automatically.
Usage: Usage:
// Product catalog // Product catalog
const products = await fetch('/api/v1/shop/products'); const products = await fetch('/api/v1/storefront/products');
// Add to cart // Add to cart
const response = await fetch('/api/v1/shop/cart/session123/items', { const response = await fetch('/api/v1/storefront/cart/session123/items', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ product_id: 1, quantity: 2 }) body: JSON.stringify({ product_id: 1, quantity: 2 })
}); });
// Place order // Place order
const order = await fetch('/api/v1/shop/orders', { const order = await fetch('/api/v1/storefront/orders', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData) body: JSON.stringify(orderData)
@@ -987,7 +987,7 @@ Features:
Location: app/static/shared/js/log-config.js Location: app/static/shared/js/log-config.js
Shop-Specific Logging: Storefront-Specific Logging:
shopLog.info('Product added to cart', product); shopLog.info('Product added to cart', product);
shopLog.error('Checkout failed', error); shopLog.error('Checkout failed', error);
shopLog.debug('Search query', { query, results }); shopLog.debug('Search query', { query, results });
@@ -1008,7 +1008,7 @@ Usage:
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span> <span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
<span x-html="$icon('search', 'w-6 h-6 text-primary')"></span> <span x-html="$icon('search', 'w-6 h-6 text-primary')"></span>
Shop Icons: Storefront Icons:
• shopping-cart, shopping-bag • shopping-cart, shopping-bag
• heart (wishlist) • heart (wishlist)
• search, filter • search, filter
@@ -1066,7 +1066,7 @@ Components:
• Category cards • Category cards
• About store section • About store section
Data Sources: Data Sources:
• GET /api/v1/shop/products?is_featured=true • GET /api/v1/storefront/products?is_featured=true
/products /products
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
@@ -1077,8 +1077,8 @@ Components:
• Sort dropdown • Sort dropdown
• Pagination • Pagination
Data Sources: Data Sources:
• GET /api/v1/shop/products?skip=0&limit=20 • GET /api/v1/storefront/products?skip=0&limit=20
• GET /api/v1/shop/products?search=query • GET /api/v1/storefront/products?search=query
• Filters applied client-side or server-side • Filters applied client-side or server-side
/products/{product_id} /products/{product_id}
@@ -1091,8 +1091,8 @@ Components:
• Related products • Related products
• Reviews (optional) • Reviews (optional)
Data Sources: Data Sources:
• GET /api/v1/shop/products/{id} • GET /api/v1/storefront/products/{id}
• GET /api/v1/shop/products?limit=4 (related products) • GET /api/v1/storefront/products?limit=4 (related products)
/cart /cart
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
@@ -1116,7 +1116,7 @@ Components:
• Order summary • Order summary
• Submit button • Submit button
Data Sources: Data Sources:
• POST /api/v1/shop/orders • POST /api/v1/storefront/orders
• Stripe/PayPal integration • Stripe/PayPal integration
/search /search
@@ -1128,7 +1128,7 @@ Components:
• Filter options • Filter options
• Sort options • Sort options
Data Sources: Data Sources:
• GET /api/v1/shop/products?search=query • GET /api/v1/storefront/products?search=query
/category/{category_slug} /category/{category_slug}
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
@@ -1139,7 +1139,7 @@ Components:
• Subcategories • Subcategories
• Filters • Filters
Data Sources: Data Sources:
• GET /api/v1/shop/products?category={slug} • GET /api/v1/storefront/products?category={slug}
/about /about
────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────
@@ -1162,7 +1162,7 @@ Components:
• Business hours • Business hours
• Social links • Social links
Data Sources: Data Sources:
• CMS content page (GET /api/v1/shop/content-pages/contact) • CMS content page (GET /api/v1/storefront/content-pages/contact)
• Form submission to store email • Form submission to store email
@@ -1179,7 +1179,7 @@ For New Developers:
2. Study Existing Page (2 hours) 2. Study Existing Page (2 hours)
→ Open home.html → Open home.html
→ Open shop-layout.js → Open storefront-layout.js
→ Trace product loading flow → Trace product loading flow
→ Examine cart management → Examine cart management
@@ -1228,20 +1228,20 @@ Before Deploying:
Multi-Access Aware Error Pages: Multi-Access Aware Error Pages:
All shop error pages (404, 500, etc.) are store-context aware and display All storefront error pages (404, 500, etc.) are store-context aware and display
correct links based on the access method (domain, subdomain, or path-based). correct links based on the access method (domain, subdomain, or path-based).
Error Page Templates: Error Page Templates:
• app/templates/shop/errors/404.html - Not Found • app/templates/storefront/errors/404.html - Not Found
• app/templates/shop/errors/400.html - Bad Request • app/templates/storefront/errors/400.html - Bad Request
• app/templates/shop/errors/401.html - Unauthorized • app/templates/storefront/errors/401.html - Unauthorized
• app/templates/shop/errors/403.html - Forbidden • app/templates/storefront/errors/403.html - Forbidden
• app/templates/shop/errors/422.html - Validation Error • app/templates/storefront/errors/422.html - Validation Error
• app/templates/shop/errors/429.html - Rate Limited • app/templates/storefront/errors/429.html - Rate Limited
• app/templates/shop/errors/500.html - Server Error • app/templates/storefront/errors/500.html - Server Error
• app/templates/shop/errors/502.html - Bad Gateway • app/templates/storefront/errors/502.html - Bad Gateway
• app/templates/shop/errors/base.html - Base error template • app/templates/storefront/errors/base.html - Base error template
• app/templates/shop/errors/generic.html - Generic error • app/templates/storefront/errors/generic.html - Generic error
Error Renderer (app/exceptions/error_renderer.py): Error Renderer (app/exceptions/error_renderer.py):
@@ -1252,7 +1252,7 @@ Calculates base_url dynamically based on store access method:
access_method = getattr(request.state, "access_method", None) access_method = getattr(request.state, "access_method", None)
store_context = getattr(request.state, "store_context", None) store_context = getattr(request.state, "store_context", None)
# Calculate base_url for shop links # Calculate base_url for storefront links
base_url = "/" base_url = "/"
if access_method == "path" and store: if access_method == "path" and store:
full_prefix = store_context.get('full_prefix', '/store/') full_prefix = store_context.get('full_prefix', '/store/')
@@ -1280,13 +1280,13 @@ All error page links use {{ base_url }} prefix for correct routing:
How It Works: How It Works:
1. Error occurs (404, 500, etc.) 1. Error occurs (404, 500, etc.)
2. Exception handler detects shop context 2. Exception handler detects storefront context
3. error_renderer.py calculates base_url from store_context 3. error_renderer.py calculates base_url from store_context
4. Error template renders with correct base_url 4. Error template renders with correct base_url
5. Links work for all access methods: 5. Links work for all access methods:
- Domain: customshop.com → base_url = "/" - Domain: customshop.com → base_url = "/"
- Subdomain: orion.platform.com → base_url = "/" - Subdomain: orion.platform.com → base_url = "/"
- Path: localhost/stores/orion/ → base_url = "/stores/orion/" - Path: localhost/storefront/orion/ → base_url = "/storefront/orion/"
Benefits: Benefits:
✅ Error pages work correctly regardless of access method ✅ Error pages work correctly regardless of access method
@@ -1333,13 +1333,13 @@ Documentation:
• FastAPI: https://fastapi.tiangolo.com/ • FastAPI: https://fastapi.tiangolo.com/
Internal Docs: Internal Docs:
• Page Template Guide: FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md • Page Template Guide: FRONTEND_STOREFRONT_ALPINE_PAGE_TEMPLATE.md
• Multi-Theme Guide: MULTI_THEME_SHOP_GUIDE.md • Multi-Theme Guide: MULTI_THEME_STOREFRONT_GUIDE.md
• API Documentation: API_REFERENCE.md • API Documentation: API_REFERENCE.md
• Database Schema: DATABASE_SCHEMA.md • Database Schema: DATABASE_SCHEMA.md
══════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════
SHOP FRONTEND ARCHITECTURE STOREFRONT FRONTEND ARCHITECTURE
Theme-Driven, Customer-Focused, Brand-Consistent Theme-Driven, Customer-Focused, Brand-Consistent
══════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════

View File

@@ -1,8 +1,8 @@
# Shop Authentication Pages # Storefront Authentication Pages
## Overview ## Overview
This document details the implementation of customer authentication pages in the shop frontend. All pages use Tailwind CSS, Alpine.js, and integrate with the multi-theme system for a branded, consistent experience across all stores. This document details the implementation of customer authentication pages in the storefront frontend. All pages use Tailwind CSS, Alpine.js, and integrate with the multi-theme system for a branded, consistent experience across all stores.
## Implementation Date ## Implementation Date
2025-11-24 2025-11-24
@@ -12,8 +12,8 @@ This document details the implementation of customer authentication pages in the
## 📄 Available Pages ## 📄 Available Pages
### 1. Login Page ### 1. Login Page
**Location:** `app/templates/shop/account/login.html` **Location:** `app/templates/storefront/account/login.html`
**Route:** `/shop/account/login` **Route:** `/storefront/account/login`
#### Features #### Features
- Two-column layout with store branding on the left - Two-column layout with store branding on the left
@@ -41,7 +41,7 @@ function customerLogin() {
async handleLogin() { async handleLogin() {
// Validates input // Validates input
// Calls POST /api/v1/shop/auth/login // Calls POST /api/v1/storefront/auth/login
// Stores token in localStorage // Stores token in localStorage
// Redirects to account page or return URL // Redirects to account page or return URL
} }
@@ -50,15 +50,15 @@ function customerLogin() {
``` ```
#### API Integration #### API Integration
- **Endpoint:** `POST /api/v1/shop/auth/login` - **Endpoint:** `POST /api/v1/storefront/auth/login`
- **Request:** `{ email_or_username: string, password: string }` - **Request:** `{ email_or_username: string, password: string }`
- **Response:** `{ access_token: string, user: object }` - **Response:** `{ access_token: string, user: object }`
--- ---
### 2. Register Page ### 2. Register Page
**Location:** `app/templates/shop/account/register.html` **Location:** `app/templates/storefront/account/register.html`
**Route:** `/shop/account/register` **Route:** `/storefront/account/register`
#### Features #### Features
- Two-column layout with store branding - Two-column layout with store branding
@@ -110,7 +110,7 @@ function customerRegistration() {
async handleRegister() { async handleRegister() {
// Validates form // Validates form
// Calls POST /api/v1/shop/auth/register // Calls POST /api/v1/storefront/auth/register
// Shows success message // Shows success message
// Redirects to login with ?registered=true // Redirects to login with ?registered=true
} }
@@ -119,15 +119,15 @@ function customerRegistration() {
``` ```
#### API Integration #### API Integration
- **Endpoint:** `POST /api/v1/shop/auth/register` - **Endpoint:** `POST /api/v1/storefront/auth/register`
- **Request:** `{ first_name: string, last_name: string, email: string, phone?: string, password: string, marketing_consent: boolean }` - **Request:** `{ first_name: string, last_name: string, email: string, phone?: string, password: string, marketing_consent: boolean }`
- **Response:** `{ message: string }` - **Response:** `{ message: string }`
--- ---
### 3. Forgot Password Page ### 3. Forgot Password Page
**Location:** `app/templates/shop/account/forgot-password.html` **Location:** `app/templates/storefront/account/forgot-password.html`
**Route:** `/shop/account/forgot-password` **Route:** `/storefront/account/forgot-password`
#### Features #### Features
- Two-column layout with store branding - Two-column layout with store branding
@@ -142,7 +142,7 @@ function customerRegistration() {
- Instructions to check inbox - Instructions to check inbox
- Option to retry if email not received - Option to retry if email not received
- Theme-aware styling - Theme-aware styling
- Links back to login and shop homepage - Links back to login and storefront homepage
- Dark mode support - Dark mode support
- Mobile responsive - Mobile responsive
@@ -159,7 +159,7 @@ function forgotPassword() {
async handleSubmit() { async handleSubmit() {
// Validates email // Validates email
// Calls POST /api/v1/shop/auth/forgot-password // Calls POST /api/v1/storefront/auth/forgot-password
// Sets emailSent = true on success // Sets emailSent = true on success
// Shows confirmation message // Shows confirmation message
} }
@@ -168,7 +168,7 @@ function forgotPassword() {
``` ```
#### API Integration #### API Integration
- **Endpoint:** `POST /api/v1/shop/auth/forgot-password` - **Endpoint:** `POST /api/v1/storefront/auth/forgot-password`
- **Request:** `{ email: string }` - **Request:** `{ email: string }`
- **Response:** `{ message: string }` - **Response:** `{ message: string }`
@@ -214,7 +214,7 @@ All authentication pages inject the store's theme CSS variables for consistent b
### Benefits ### Benefits
- ✅ Each store's auth pages automatically match their brand - ✅ Each store's auth pages automatically match their brand
- ✅ Consistent with main shop design - ✅ Consistent with main storefront design
- ✅ Dark mode adapts to store colors - ✅ Dark mode adapts to store colors
- ✅ Professional, polished appearance - ✅ Professional, polished appearance
- ✅ No custom CSS needed per store - ✅ No custom CSS needed per store
@@ -303,7 +303,7 @@ All authentication pages inject the store's theme CSS variables for consistent b
## 🔗 Navigation Flow ## 🔗 Navigation Flow
``` ```
Shop Homepage Storefront Homepage
Login Page ←→ Register Page Login Page ←→ Register Page
↓ ↓ ↓ ↓
@@ -314,16 +314,16 @@ Check Email Account/Cart
### Link Structure ### Link Structure
- **Login Page:** - **Login Page:**
- "Forgot password?" → `/shop/account/forgot-password` - "Forgot password?" → `/storefront/account/forgot-password`
- "Create an account" → `/shop/account/register` - "Create an account" → `/storefront/account/register`
- "← Continue shopping" → `/shop/` - "← Continue shopping" → `/storefront/`
- **Register Page:** - **Register Page:**
- "Already have an account? Sign in instead" → `/shop/account/login` - "Already have an account? Sign in instead" → `/storefront/account/login`
- **Forgot Password Page:** - **Forgot Password Page:**
- "Remember your password? Sign in" → `/shop/account/login` - "Remember your password? Sign in" → `/storefront/account/login`
- "← Continue shopping" → `/shop/` - "← Continue shopping" → `/storefront/`
All links use `{{ base_url }}` for multi-access routing support. All links use `{{ base_url }}` for multi-access routing support.
@@ -386,12 +386,12 @@ No code changes needed - all controlled via theme configuration.
``` ```
app/ app/
├── templates/shop/account/ ├── templates/storefront/account/
│ ├── login.html ← Customer login page │ ├── login.html ← Customer login page
│ ├── register.html ← Customer registration page │ ├── register.html ← Customer registration page
│ └── forgot-password.html ← Password reset page │ └── forgot-password.html ← Password reset page
└── api/v1/shop/ └── api/v1/storefront/
└── auth.py ← Authentication endpoints └── auth.py ← Authentication endpoints
static/shared/css/ static/shared/css/
@@ -463,7 +463,7 @@ Possible additions:
## 📚 Related Documentation ## 📚 Related Documentation
- [Shop Frontend Architecture](./architecture.md) - [Storefront Frontend Architecture](./architecture.md)
- [Page Template Guide](./page-templates.md) - [Page Template Guide](./page-templates.md)
- [Theme System Overview](../../architecture/theme-system/overview.md) - [Theme System Overview](../../architecture/theme-system/overview.md)
- [Theme Presets](../../architecture/theme-system/presets.md) - [Theme Presets](../../architecture/theme-system/presets.md)

View File

@@ -28,7 +28,7 @@ This document proposes a comprehensive set of reusable Jinja macro components fo
### Priority 1: Core Shopping Components ### Priority 1: Core Shopping Components
#### 1.1 Product Card #### 1.1 Product Card
**File:** `app/templates/shared/macros/shop/product-card.html` **File:** `app/templates/shared/macros/storefront/product-card.html`
A versatile product card for grids, carousels, and lists. A versatile product card for grids, carousels, and lists.
@@ -63,7 +63,7 @@ A versatile product card for grids, carousels, and lists.
--- ---
#### 1.2 Product Grid #### 1.2 Product Grid
**File:** `app/templates/shared/macros/shop/product-grid.html` **File:** `app/templates/shared/macros/storefront/product-grid.html`
Responsive grid layout for product listings. Responsive grid layout for product listings.
@@ -85,7 +85,7 @@ Responsive grid layout for product listings.
--- ---
#### 1.3 Add to Cart Button #### 1.3 Add to Cart Button
**File:** `app/templates/shared/macros/shop/add-to-cart.html` **File:** `app/templates/shared/macros/storefront/add-to-cart.html`
Standardized add-to-cart functionality. Standardized add-to-cart functionality.
@@ -115,7 +115,7 @@ Standardized add-to-cart functionality.
Shop-specific wrapper with stock validation: Shop-specific wrapper with stock validation:
```jinja ```jinja
{{ shop_quantity_selector( {{ storefront_quantity_selector(
model='quantity', model='quantity',
max='product.stock', max='product.stock',
disabled_var='addingToCart', disabled_var='addingToCart',
@@ -128,7 +128,7 @@ Shop-specific wrapper with stock validation:
### Priority 2: Cart Components ### Priority 2: Cart Components
#### 2.1 Mini Cart (Header Dropdown) #### 2.1 Mini Cart (Header Dropdown)
**File:** `app/templates/shared/macros/shop/mini-cart.html` **File:** `app/templates/shared/macros/storefront/mini-cart.html`
Dropdown cart preview in the header. Dropdown cart preview in the header.
@@ -151,7 +151,7 @@ Dropdown cart preview in the header.
--- ---
#### 2.2 Cart Item Row #### 2.2 Cart Item Row
**File:** `app/templates/shared/macros/shop/cart-item.html` **File:** `app/templates/shared/macros/storefront/cart-item.html`
Individual cart line item. Individual cart line item.
@@ -177,7 +177,7 @@ Individual cart line item.
--- ---
#### 2.3 Cart Summary #### 2.3 Cart Summary
**File:** `app/templates/shared/macros/shop/cart-summary.html` **File:** `app/templates/shared/macros/storefront/cart-summary.html`
Order summary sidebar/section. Order summary sidebar/section.
@@ -205,7 +205,7 @@ Order summary sidebar/section.
### Priority 3: Product Detail Components ### Priority 3: Product Detail Components
#### 3.1 Product Gallery #### 3.1 Product Gallery
**File:** `app/templates/shared/macros/shop/product-gallery.html` **File:** `app/templates/shared/macros/storefront/product-gallery.html`
Image gallery with thumbnails and zoom. Image gallery with thumbnails and zoom.
@@ -229,7 +229,7 @@ Image gallery with thumbnails and zoom.
--- ---
#### 3.2 Variant Selector #### 3.2 Variant Selector
**File:** `app/templates/shared/macros/shop/variant-selector.html` **File:** `app/templates/shared/macros/storefront/variant-selector.html`
Product variant selection (size, color, etc.). Product variant selection (size, color, etc.).
@@ -255,7 +255,7 @@ Product variant selection (size, color, etc.).
--- ---
#### 3.3 Product Info Block #### 3.3 Product Info Block
**File:** `app/templates/shared/macros/shop/product-info.html` **File:** `app/templates/shared/macros/storefront/product-info.html`
Product details section. Product details section.
@@ -280,7 +280,7 @@ Product details section.
--- ---
#### 3.4 Product Tabs #### 3.4 Product Tabs
**File:** `app/templates/shared/macros/shop/product-tabs.html` **File:** `app/templates/shared/macros/storefront/product-tabs.html`
Tabbed product information. Tabbed product information.
@@ -303,7 +303,7 @@ Tabbed product information.
### Priority 4: Navigation & Discovery ### Priority 4: Navigation & Discovery
#### 4.1 Category Navigation #### 4.1 Category Navigation
**File:** `app/templates/shared/macros/shop/category-nav.html` **File:** `app/templates/shared/macros/storefront/category-nav.html`
Category browsing sidebar/menu. Category browsing sidebar/menu.
@@ -326,12 +326,12 @@ Category browsing sidebar/menu.
--- ---
#### 4.2 Breadcrumbs #### 4.2 Breadcrumbs
**File:** `app/templates/shared/macros/shop/breadcrumbs.html` **File:** `app/templates/shared/macros/storefront/breadcrumbs.html`
Navigation breadcrumb trail. Navigation breadcrumb trail.
```jinja ```jinja
{{ shop_breadcrumbs( {{ storefront_breadcrumbs(
items=[ items=[
{'label': 'Home', 'url': '/'}, {'label': 'Home', 'url': '/'},
{'label': 'Electronics', 'url': '/category/electronics'}, {'label': 'Electronics', 'url': '/category/electronics'},
@@ -343,7 +343,7 @@ Navigation breadcrumb trail.
--- ---
#### 4.3 Search Bar #### 4.3 Search Bar
**File:** `app/templates/shared/macros/shop/search-bar.html` **File:** `app/templates/shared/macros/storefront/search-bar.html`
Product search with autocomplete. Product search with autocomplete.
@@ -365,7 +365,7 @@ Product search with autocomplete.
--- ---
#### 4.4 Filter Sidebar #### 4.4 Filter Sidebar
**File:** `app/templates/shared/macros/shop/filter-sidebar.html` **File:** `app/templates/shared/macros/storefront/filter-sidebar.html`
Product filtering panel. Product filtering panel.
@@ -390,7 +390,7 @@ Product filtering panel.
### Priority 5: Social Proof & Trust ### Priority 5: Social Proof & Trust
#### 5.1 Star Rating #### 5.1 Star Rating
**File:** `app/templates/shared/macros/shop/star-rating.html` **File:** `app/templates/shared/macros/storefront/star-rating.html`
Reusable star rating display. Reusable star rating display.
@@ -407,7 +407,7 @@ Reusable star rating display.
--- ---
#### 5.2 Review Card #### 5.2 Review Card
**File:** `app/templates/shared/macros/shop/review-card.html` **File:** `app/templates/shared/macros/storefront/review-card.html`
Individual product review. Individual product review.
@@ -431,7 +431,7 @@ Individual product review.
--- ---
#### 5.3 Trust Badges #### 5.3 Trust Badges
**File:** `app/templates/shared/macros/shop/trust-badges.html` **File:** `app/templates/shared/macros/storefront/trust-badges.html`
Trust and security indicators. Trust and security indicators.
@@ -454,7 +454,7 @@ Trust and security indicators.
### Priority 6: Promotional Components ### Priority 6: Promotional Components
#### 6.1 Sale Banner #### 6.1 Sale Banner
**File:** `app/templates/shared/macros/shop/sale-banner.html` **File:** `app/templates/shared/macros/storefront/sale-banner.html`
Promotional banner with countdown. Promotional banner with countdown.
@@ -472,7 +472,7 @@ Promotional banner with countdown.
--- ---
#### 6.2 Product Badge #### 6.2 Product Badge
**File:** `app/templates/shared/macros/shop/product-badge.html` **File:** `app/templates/shared/macros/storefront/product-badge.html`
Product overlay badges. Product overlay badges.
@@ -486,7 +486,7 @@ Product overlay badges.
--- ---
#### 6.3 Recently Viewed #### 6.3 Recently Viewed
**File:** `app/templates/shared/macros/shop/recently-viewed.html` **File:** `app/templates/shared/macros/storefront/recently-viewed.html`
Recently viewed products carousel. Recently viewed products carousel.
@@ -578,7 +578,7 @@ All shop components will use these CSS variables set by the store theme:
## File Structure ## File Structure
``` ```
app/templates/shared/macros/shop/ app/templates/shared/macros/storefront/
├── product-card.html ├── product-card.html
├── product-grid.html ├── product-grid.html
├── add-to-cart.html ├── add-to-cart.html

View File

@@ -1,22 +1,22 @@
# Shop Navigation Flow # Storefront Navigation Flow
Complete guide to navigation structure and URL hierarchy with landing pages. Complete guide to navigation structure and URL hierarchy with landing pages.
## URL Hierarchy ## URL Hierarchy
``` ```
/ (Store Root) → Landing Page (if exists) OR redirect to /shop/ / (Store Root) → Landing Page (if exists) OR redirect to /storefront/
├── /shop/ → E-commerce Homepage (Product Catalog) ├── /storefront/ → E-commerce Homepage (Product Catalog)
│ ├── /shop/products → Product Catalog (same as /shop/) │ ├── /storefront/products → Product Catalog (same as /storefront/)
│ ├── /shop/products/{id} → Product Detail Page │ ├── /storefront/products/{id} → Product Detail Page
│ ├── /shop/cart → Shopping Cart │ ├── /storefront/cart → Shopping Cart
│ ├── /shop/checkout → Checkout Process │ ├── /storefront/checkout → Checkout Process
│ ├── /shop/account/login → Customer Login │ ├── /storefront/account/login → Customer Login
│ ├── /shop/account/register → Customer Registration │ ├── /storefront/account/register → Customer Registration
│ ├── /shop/account/dashboard → Customer Dashboard (auth required) │ ├── /storefront/account/dashboard → Customer Dashboard (auth required)
│ ├── /shop/about → CMS Content Page │ ├── /storefront/about → CMS Content Page
│ ├── /shop/contact → CMS Content Page │ ├── /storefront/contact → CMS Content Page
│ └── /shop/{slug} → Other CMS Pages │ └── /storefront/{slug} → Other CMS Pages
``` ```
## Navigation Patterns ## Navigation Patterns
@@ -26,13 +26,13 @@ Complete guide to navigation structure and URL hierarchy with landing pages.
**URL Structure:** **URL Structure:**
``` ```
customdomain.com/ → Landing Page (marketing/brand) customdomain.com/ → Landing Page (marketing/brand)
customdomain.com/shop/ → E-commerce Shop customdomain.com/storefront/ → E-commerce Storefront
customdomain.com/shop/products → Product Catalog customdomain.com/storefront/products → Product Catalog
``` ```
**Navigation Flow:** **Navigation Flow:**
1. User visits store domain → **Landing Page** 1. User visits store domain → **Landing Page**
2. Clicks "Shop Now" → **/shop/** (product catalog) 2. Clicks "Shop Now" → **/storefront/** (product catalog)
3. Clicks "Home" in breadcrumb → **/** (back to landing page) 3. Clicks "Home" in breadcrumb → **/** (back to landing page)
4. Clicks logo → **/** (back to landing page) 4. Clicks logo → **/** (back to landing page)
@@ -47,46 +47,46 @@ Home > Products
**URL Structure:** **URL Structure:**
``` ```
customdomain.com/ → Redirects to /shop/ customdomain.com/ → Redirects to /storefront/
customdomain.com/shop/ → E-commerce Shop customdomain.com/storefront/ → E-commerce Storefront
customdomain.com/shop/products → Product Catalog customdomain.com/storefront/products → Product Catalog
``` ```
**Navigation Flow:** **Navigation Flow:**
1. User visits store domain → **Redirects to /shop/** 1. User visits store domain → **Redirects to /storefront/**
2. User browses shop 2. User browses storefront
3. Clicks "Home" in breadcrumb → **/** (redirects to /shop/) 3. Clicks "Home" in breadcrumb → **/** (redirects to /storefront/)
4. Clicks logo → **/** (redirects to /shop/) 4. Clicks logo → **/** (redirects to /storefront/)
**Breadcrumb Example (on Products page):** **Breadcrumb Example (on Products page):**
``` ```
Home > Products Home > Products
/ → /shop/ (redirects) / → /storefront/ (redirects)
``` ```
## Link References ## Link References
### Base URL Calculation ### Base URL Calculation
The `base_url` variable is calculated in `shop_pages.py:get_shop_context()`: The `base_url` variable is calculated in `storefront_pages.py:get_storefront_context()`:
```python ```python
# For domain/subdomain access # For domain/subdomain access
base_url = "/" base_url = "/"
# Result: /shop/products, /shop/cart, etc. # Result: /storefront/products, /storefront/cart, etc.
# For path-based access # For path-based access
base_url = "/stores/orion/" base_url = "/storefront/orion/"
# Result: /stores/orion/shop/products, /stores/orion/shop/cart, etc. # Result: /storefront/orion/products, /storefront/orion/cart, etc.
``` ```
### Template Links ### Template Links
**Logo / Home Link (Header):** **Logo / Home Link (Header):**
```jinja2 ```jinja2
{# Points to store root (landing page or shop) #} {# Points to store root (landing page or storefront) #}
<a href="{{ base_url }}shop/">{{ store.name }}</a> <a href="{{ base_url }}storefront/">{{ store.name }}</a>
``` ```
**Breadcrumb Home Link:** **Breadcrumb Home Link:**
@@ -95,20 +95,20 @@ base_url = "/stores/orion/"
<a href="{{ base_url }}">Home</a> <a href="{{ base_url }}">Home</a>
``` ```
**Shop Links:** **Storefront Links:**
```jinja2 ```jinja2
{# All shop pages include /shop/ prefix #} {# All storefront pages include /storefront/ prefix #}
<a href="{{ base_url }}shop/products">Products</a> <a href="{{ base_url }}storefront/products">Products</a>
<a href="{{ base_url }}shop/cart">Cart</a> <a href="{{ base_url }}storefront/cart">Cart</a>
<a href="{{ base_url }}shop/checkout">Checkout</a> <a href="{{ base_url }}storefront/checkout">Checkout</a>
``` ```
**CMS Page Links:** **CMS Page Links:**
```jinja2 ```jinja2
{# CMS pages are under /shop/ #} {# CMS pages are under /storefront/ #}
<a href="{{ base_url }}shop/about">About</a> <a href="{{ base_url }}storefront/about">About</a>
<a href="{{ base_url }}shop/contact">Contact</a> <a href="{{ base_url }}storefront/contact">Contact</a>
<a href="{{ base_url }}shop/{{ page.slug }}">{{ page.title }}</a> <a href="{{ base_url }}storefront/{{ page.slug }}">{{ page.title }}</a>
``` ```
## Complete URL Examples ## Complete URL Examples
@@ -116,46 +116,46 @@ base_url = "/stores/orion/"
### Path-Based Access (Development) ### Path-Based Access (Development)
``` ```
http://localhost:8000/stores/orion/ http://localhost:8000/storefront/orion/
├── / (root) → Landing Page OR redirect to shop ├── / (root) → Landing Page OR redirect to storefront
├── /shop/ → Shop Homepage ├── /storefront/ → Storefront Homepage
├── /shop/products → Product Catalog ├── /storefront/products → Product Catalog
├── /shop/products/4 → Product Detail ├── /storefront/products/4 → Product Detail
├── /shop/cart → Shopping Cart ├── /storefront/cart → Shopping Cart
├── /shop/checkout → Checkout ├── /storefront/checkout → Checkout
├── /shop/account/login → Customer Login ├── /storefront/account/login → Customer Login
├── /shop/about → About Page (CMS) ├── /storefront/about → About Page (CMS)
└── /shop/contact → Contact Page (CMS) └── /storefront/contact → Contact Page (CMS)
``` ```
### Subdomain Access (Production) ### Subdomain Access (Production)
``` ```
https://orion.platform.com/ https://orion.platform.com/
├── / (root) → Landing Page OR redirect to shop ├── / (root) → Landing Page OR redirect to storefront
├── /shop/ → Shop Homepage ├── /storefront/ → Storefront Homepage
├── /shop/products → Product Catalog ├── /storefront/products → Product Catalog
├── /shop/products/4 → Product Detail ├── /storefront/products/4 → Product Detail
├── /shop/cart → Shopping Cart ├── /storefront/cart → Shopping Cart
├── /shop/checkout → Checkout ├── /storefront/checkout → Checkout
├── /shop/account/login → Customer Login ├── /storefront/account/login → Customer Login
├── /shop/about → About Page (CMS) ├── /storefront/about → About Page (CMS)
└── /shop/contact → Contact Page (CMS) └── /storefront/contact → Contact Page (CMS)
``` ```
### Custom Domain Access (Production) ### Custom Domain Access (Production)
``` ```
https://customdomain.com/ https://customdomain.com/
├── / (root) → Landing Page OR redirect to shop ├── / (root) → Landing Page OR redirect to storefront
├── /shop/ → Shop Homepage ├── /storefront/ → Storefront Homepage
├── /shop/products → Product Catalog ├── /storefront/products → Product Catalog
├── /shop/products/4 → Product Detail ├── /storefront/products/4 → Product Detail
├── /shop/cart → Shopping Cart ├── /storefront/cart → Shopping Cart
├── /shop/checkout → Checkout ├── /storefront/checkout → Checkout
├── /shop/account/login → Customer Login ├── /storefront/account/login → Customer Login
├── /shop/about → About Page (CMS) ├── /storefront/about → About Page (CMS)
└── /shop/contact → Contact Page (CMS) └── /storefront/contact → Contact Page (CMS)
``` ```
## User Journeys ## User Journeys
@@ -164,26 +164,26 @@ https://customdomain.com/
1. Visit `customdomain.com/`**Landing Page** 1. Visit `customdomain.com/`**Landing Page**
- Sees brand story, features, CTA - Sees brand story, features, CTA
2. Clicks "Shop Now" → `/shop/`**Product Catalog** 2. Clicks "Shop Now" → `/storefront/`**Product Catalog**
- Browses products - Browses products
3. Clicks product → `/shop/products/4`**Product Detail** 3. Clicks product → `/storefront/products/4`**Product Detail**
- Views product - Views product
4. Clicks "Home" in breadcrumb → `/`**Back to Landing Page** 4. Clicks "Home" in breadcrumb → `/`**Back to Landing Page**
5. Clicks logo → `/`**Back to Landing Page** 5. Clicks logo → `/`**Back to Landing Page**
### Journey 2: Returning Customer (Direct to Shop) ### Journey 2: Returning Customer (Direct to Storefront)
1. Visit `customdomain.com/shop/`**Product Catalog** 1. Visit `customdomain.com/storefront/`**Product Catalog**
- Already knows the brand, goes straight to shop - Already knows the brand, goes straight to storefront
2. Adds to cart → `/shop/cart`**Shopping Cart** 2. Adds to cart → `/storefront/cart`**Shopping Cart**
3. Checkout → `/shop/checkout`**Checkout** 3. Checkout → `/storefront/checkout`**Checkout**
4. Clicks "Home" → `/`**Landing Page** (brand homepage) 4. Clicks "Home" → `/`**Landing Page** (brand homepage)
### Journey 3: Customer Account Management ### Journey 3: Customer Account Management
1. Visit `customdomain.com/shop/account/login`**Login** 1. Visit `customdomain.com/storefront/account/login`**Login**
2. After login → `/shop/account/dashboard`**Dashboard** 2. After login → `/storefront/account/dashboard`**Dashboard**
3. View orders → `/shop/account/orders`**Order History** 3. View orders → `/storefront/account/orders`**Order History**
4. Clicks logo → `/`**Back to Landing Page** 4. Clicks logo → `/`**Back to Landing Page**
## Navigation Components ## Navigation Components
@@ -192,32 +192,32 @@ https://customdomain.com/
```jinja2 ```jinja2
{# Logo - always points to store root #} {# Logo - always points to store root #}
<a href="{{ base_url }}shop/"> <a href="{{ base_url }}storefront/">
<img src="{{ theme.branding.logo }}" alt="{{ store.name }}"> <img src="{{ theme.branding.logo }}" alt="{{ store.name }}">
</a> </a>
{# Main Navigation #} {# Main Navigation #}
<nav> <nav>
<a href="{{ base_url }}shop/">Home</a> <a href="{{ base_url }}storefront/">Home</a>
<a href="{{ base_url }}shop/products">Products</a> <a href="{{ base_url }}storefront/products">Products</a>
<a href="{{ base_url }}shop/about">About</a> <a href="{{ base_url }}storefront/about">About</a>
<a href="{{ base_url }}shop/contact">Contact</a> <a href="{{ base_url }}storefront/contact">Contact</a>
</nav> </nav>
{# Actions #} {# Actions #}
<a href="{{ base_url }}shop/cart">Cart</a> <a href="{{ base_url }}storefront/cart">Cart</a>
<a href="{{ base_url }}shop/account">Account</a> <a href="{{ base_url }}storefront/account">Account</a>
``` ```
### Footer Navigation (base.html) ### Footer Navigation (base.html)
```jinja2 ```jinja2
{# Quick Links #} {# Quick Links #}
<a href="{{ base_url }}shop/products">Products</a> <a href="{{ base_url }}storefront/products">Products</a>
{# CMS Pages (dynamic) #} {# CMS Pages (dynamic) #}
{% for page in footer_pages %} {% for page in footer_pages %}
<a href="{{ base_url }}shop/{{ page.slug }}">{{ page.title }}</a> <a href="{{ base_url }}storefront/{{ page.slug }}">{{ page.title }}</a>
{% endfor %} {% endfor %}
``` ```
@@ -233,39 +233,39 @@ https://customdomain.com/
### ✅ DO: ### ✅ DO:
1. **Use Landing Pages**: Create engaging landing pages at store root 1. **Use Landing Pages**: Create engaging landing pages at store root
2. **Clear Navigation**: Make it easy to get from landing to shop and back 2. **Clear Navigation**: Make it easy to get from landing to storefront and back
3. **Consistent "Home"**: Logo and "Home" breadcrumb both point to `/` (landing) 3. **Consistent "Home"**: Logo and "Home" breadcrumb both point to `/` (landing)
4. **Shop Links**: All shop-related links include `/shop/` prefix 4. **Storefront Links**: All storefront-related links include `/storefront/` prefix
5. **CMS Under Shop**: Keep CMS pages under `/shop/` for consistency 5. **CMS Under Storefront**: Keep CMS pages under `/storefront/` for consistency
### ❌ DON'T: ### ❌ DON'T:
1. **Hardcode URLs**: Always use `{{ base_url }}` for store-aware links 1. **Hardcode URLs**: Always use `{{ base_url }}` for store-aware links
2. **Skip /shop/**: Don't link directly to `/products`, use `/shop/products` 2. **Skip /storefront/**: Don't link directly to `/products`, use `/storefront/products`
3. **Mix Landing & Shop**: Keep landing page separate from shop catalog 3. **Mix Landing & Storefront**: Keep landing page separate from storefront catalog
4. **Forget Breadcrumbs**: Always provide "Home" link to go back 4. **Forget Breadcrumbs**: Always provide "Home" link to go back
## Migration Notes ## Migration Notes
### Before Landing Pages ### Before Landing Pages
All links pointed to `/shop/`: All links pointed to `/storefront/`:
```jinja2 ```jinja2
<a href="{{ base_url }}shop/">Home</a> {# Logo #} <a href="{{ base_url }}storefront/">Home</a> {# Logo #}
<a href="{{ base_url }}shop/">Home</a> {# Breadcrumb #} <a href="{{ base_url }}storefront/">Home</a> {# Breadcrumb #}
``` ```
### After Landing Pages ### After Landing Pages
Separation of concerns: Separation of concerns:
```jinja2 ```jinja2
<a href="{{ base_url }}shop/">Home</a> {# Logo - still goes to shop #} <a href="{{ base_url }}storefront/">Home</a> {# Logo - still goes to storefront #}
<a href="{{ base_url }}">Home</a> {# Breadcrumb - goes to landing #} <a href="{{ base_url }}">Home</a> {# Breadcrumb - goes to landing #}
``` ```
This allows: This allows:
- Landing page at `/` for marketing/branding - Landing page at `/` for marketing/branding
- Shop catalog at `/shop/` for e-commerce - Storefront catalog at `/storefront/` for e-commerce
- Clean navigation between the two - Clean navigation between the two
## Technical Implementation ## Technical Implementation
@@ -273,27 +273,27 @@ This allows:
### Route Handlers (main.py) ### Route Handlers (main.py)
```python ```python
# Store root - serves landing page or redirects to shop # Store root - serves landing page or redirects to storefront
@app.get("/") @app.get("/")
@app.get("/stores/{store_code}/") @app.get("/storefront/{store_code}/")
async def root(request: Request): async def root(request: Request):
if has_landing_page(): if has_landing_page():
return render_landing_page() return render_landing_page()
else: else:
return redirect_to_shop() return redirect_to_storefront()
# Shop routes # Storefront routes
@app.include_router(shop_pages.router, prefix="/shop") @app.include_router(storefront_pages.router, prefix="/storefront")
@app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") @app.include_router(storefront_pages.router, prefix="/storefront/{store_code}")
``` ```
### Context Calculation (shop_pages.py) ### Context Calculation (storefront_pages.py)
```python ```python
def get_shop_context(request: Request): def get_storefront_context(request: Request):
base_url = "/" base_url = "/"
if access_method == "path": if access_method == "path":
base_url = f"/stores/{store.subdomain}/" base_url = f"/storefront/{store.subdomain}/"
return { return {
"base_url": base_url, "base_url": base_url,
@@ -308,11 +308,11 @@ def get_shop_context(request: Request):
The navigation system creates a **two-tier structure**: The navigation system creates a **two-tier structure**:
1. **Landing Page** (`/`) - Marketing, branding, store story 1. **Landing Page** (`/`) - Marketing, branding, store story
2. **Shop** (`/shop/`) - E-commerce, products, cart, checkout 2. **Storefront** (`/storefront/`) - E-commerce, products, cart, checkout
This gives stores flexibility to: This gives stores flexibility to:
- Have a marketing homepage separate from their store - Have a marketing homepage separate from their store
- Choose different landing page designs (minimal, modern, full) - Choose different landing page designs (minimal, modern, full)
- Or skip the landing page and go straight to the shop - Or skip the landing page and go straight to the storefront
All while maintaining clean, consistent navigation throughout the experience. All while maintaining clean, consistent navigation throughout the experience.

View File

@@ -1,8 +1,8 @@
# Shop Frontend - Alpine.js/Jinja2 Page Template Guide # Storefront Frontend - Alpine.js/Jinja2 Page Template Guide
## 📋 Overview ## 📋 Overview
This guide provides complete templates for creating new customer-facing shop pages using the established Alpine.js + Jinja2 + Multi-Theme architecture. Follow these patterns to ensure consistency across all store shops while maintaining unique branding. This guide provides complete templates for creating new customer-facing storefront pages using the established Alpine.js + Jinja2 + Multi-Theme architecture. Follow these patterns to ensure consistency across all store storefronts while maintaining unique branding.
--- ---
@@ -10,9 +10,9 @@ This guide provides complete templates for creating new customer-facing shop pag
Three fully-implemented authentication pages are available for reference: Three fully-implemented authentication pages are available for reference:
- **Login** (`app/templates/shop/account/login.html`) - Customer sign-in with email/password - **Login** (`app/templates/storefront/account/login.html`) - Customer sign-in with email/password
- **Register** (`app/templates/shop/account/register.html`) - New customer account creation - **Register** (`app/templates/storefront/account/register.html`) - New customer account creation
- **Forgot Password** (`app/templates/shop/account/forgot-password.html`) - Password reset flow - **Forgot Password** (`app/templates/storefront/account/forgot-password.html`) - Password reset flow
All authentication pages feature: All authentication pages feature:
- ✅ Tailwind CSS styling - ✅ Tailwind CSS styling
@@ -24,7 +24,7 @@ All authentication pages feature:
- ✅ Loading states - ✅ Loading states
- ✅ Error handling - ✅ Error handling
See the [Shop Architecture Documentation](./architecture.md) (Authentication Pages section) for complete details. See the [Storefront Architecture Documentation](./architecture.md) (Authentication Pages section) for complete details.
--- ---
@@ -33,16 +33,16 @@ See the [Shop Architecture Documentation](./architecture.md) (Authentication Pag
### File Structure for New Page ### File Structure for New Page
``` ```
app/ app/
├── templates/shop/ ├── templates/storefront/
│ └── [page-name].html # Jinja2 template │ └── [page-name].html # Jinja2 template
├── static/shop/js/ ├── static/storefront/js/
│ └── [page-name].js # Alpine.js component │ └── [page-name].js # Alpine.js component
└── api/v1/shop/ └── api/v1/storefront/
└── pages.py # Route registration └── pages.py # Route registration
``` ```
### Checklist for New Page ### Checklist for New Page
- [ ] Create Jinja2 template extending shop/base.html - [ ] Create Jinja2 template extending storefront/base.html
- [ ] Create Alpine.js JavaScript component - [ ] Create Alpine.js JavaScript component
- [ ] Register route in pages.py - [ ] Register route in pages.py
- [ ] Test with multiple store themes - [ ] Test with multiple store themes
@@ -58,11 +58,11 @@ app/
### 1. Jinja2 Template ### 1. Jinja2 Template
**File:** `app/templates/shop/[page-name].html` **File:** `app/templates/storefront/[page-name].html`
```jinja2 ```jinja2
{# app/templates/shop/[page-name].html #} {# app/templates/storefront/[page-name].html #}
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
{# Page title for browser tab - includes store name #} {# Page title for browser tab - includes store name #}
{% block title %}[Page Name] - {{ store.name }}{% endblock %} {% block title %}[Page Name] - {{ store.name }}{% endblock %}
@@ -174,7 +174,7 @@ app/
<!-- Item Image --> <!-- Item Image -->
<div class="aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-t-lg bg-gray-100 dark:bg-gray-700"> <div class="aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-t-lg bg-gray-100 dark:bg-gray-700">
<img :src="item.image || '/static/shop/img/placeholder-product.png'" <img :src="item.image || '/static/storefront/img/placeholder-product.png'"
:alt="item.name" :alt="item.name"
class="w-full h-full object-cover object-center hover:scale-105 transition-transform" class="w-full h-full object-cover object-center hover:scale-105 transition-transform"
loading="lazy"> loading="lazy">
@@ -247,7 +247,7 @@ app/
{# Page-specific JavaScript #} {# Page-specific JavaScript #}
{% block extra_scripts %} {% block extra_scripts %}
<script src="{{ url_for('static', path='shop/js/[page-name].js') }}"></script> <script src="{{ url_for('static', path='storefront/js/[page-name].js') }}"></script>
{% endblock %} {% endblock %}
``` ```
@@ -255,10 +255,10 @@ app/
### 2. Alpine.js Component ### 2. Alpine.js Component
**File:** `app/static/shop/js/[page-name].js` **File:** `app/static/storefront/js/[page-name].js`
```javascript ```javascript
// static/shop/js/[page-name].js // static/storefront/js/[page-name].js
/** /**
* [Page Name] Component * [Page Name] Component
* Handles [describe functionality] * Handles [describe functionality]
@@ -333,7 +333,7 @@ function shop[PageName]() {
}); });
const response = await fetch( const response = await fetch(
`/api/v1/shop/${this.storeCode}/items?${params}` `/api/v1/storefront/${this.storeCode}/items?${params}`
); );
if (!response.ok) { if (!response.ok) {
@@ -444,14 +444,14 @@ function shop[PageName]() {
addToCart(item, quantity = 1) { addToCart(item, quantity = 1) {
pageLog.info('Adding to cart:', item.name); pageLog.info('Adding to cart:', item.name);
// Get cart from shop layout // Get cart from storefront layout
const shopLayout = Alpine.store('shop') || window.storefrontLayoutData(); const shopLayout = Alpine.store('shop') || window.storefrontLayoutData();
if (shopLayout && typeof shopLayout.addToCart === 'function') { if (shopLayout && typeof shopLayout.addToCart === 'function') {
shopLayout.addToCart(item, quantity); shopLayout.addToCart(item, quantity);
this.showToast(`${item.name} added to cart`, 'success'); this.showToast(`${item.name} added to cart`, 'success');
} else { } else {
pageLog.error('Shop layout not available'); pageLog.error('Storefront layout not available');
} }
}, },
@@ -513,7 +513,7 @@ pageLog.info('[PageName] module loaded');
### 3. Route Registration ### 3. Route Registration
**File:** `app/api/v1/shop/pages.py` **File:** `app/api/v1/storefront/pages.py`
```python ```python
from fastapi import APIRouter, Request, Depends from fastapi import APIRouter, Request, Depends
@@ -536,7 +536,7 @@ async def [page_name]_page(
theme = request.state.theme theme = request.state.theme
return templates.TemplateResponse( return templates.TemplateResponse(
"shop/[page-name].html", "storefront/[page-name].html",
{ {
"request": request, "request": request,
"store": store, "store": store,
@@ -562,7 +562,7 @@ async loadProducts() {
this.loading = true; this.loading = true;
try { try {
const response = await fetch( const response = await fetch(
`/api/v1/shop/${this.storeCode}/products?category=${this.category}` `/api/v1/storefront/${this.storeCode}/products?category=${this.category}`
); );
const data = await response.json(); const data = await response.json();
this.products = data.products || []; this.products = data.products || [];
@@ -578,7 +578,7 @@ async loadProducts() {
```html ```html
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<template x-for="product in products" :key="product.id"> <template x-for="product in products" :key="product.id">
{% include 'shop/partials/product-card.html' %} {% include 'storefront/partials/product-card.html' %}
</template> </template>
</div> </div>
``` ```
@@ -598,7 +598,7 @@ async init() {
async loadProduct(id) { async loadProduct(id) {
const product = await fetch( const product = await fetch(
`/api/v1/shop/${this.storeCode}/products/${id}` `/api/v1/storefront/${this.storeCode}/products/${id}`
).then(r => r.json()); ).then(r => r.json());
this.product = product; this.product = product;
@@ -713,7 +713,7 @@ async performSearch() {
this.loading = true; this.loading = true;
try { try {
const response = await fetch( const response = await fetch(
`/api/v1/shop/${this.storeCode}/search`, `/api/v1/storefront/${this.storeCode}/search`,
{ {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -750,10 +750,10 @@ Always use CSS variables for store colors:
### 2. Cart Integration ### 2. Cart Integration
Always use the shop layout's cart methods: Always use the storefront layout's cart methods:
```javascript ```javascript
// ✅ GOOD: Uses shop layout // ✅ GOOD: Uses storefront layout
const shopLayout = window.storefrontLayoutData(); const shopLayout = window.storefrontLayoutData();
shopLayout.addToCart(product, quantity); shopLayout.addToCart(product, quantity);
@@ -881,7 +881,7 @@ Add proper ARIA labels and keyboard navigation:
### Reusable Partials ### Reusable Partials
Create reusable components in `templates/shop/partials/`: Create reusable components in `templates/storefront/partials/`:
**product-card.html:** **product-card.html:**
```html ```html
@@ -945,12 +945,12 @@ Create reusable components in `templates/shop/partials/`:
```bash ```bash
# Create new page files # Create new page files
touch app/templates/shop/new-page.html touch app/templates/storefront/new-page.html
touch app/static/shop/js/new-page.js touch app/static/storefront/js/new-page.js
# Copy templates # Copy templates
cp template.html app/templates/shop/new-page.html cp template.html app/templates/storefront/new-page.html
cp template.js app/static/shop/js/new-page.js cp template.js app/static/storefront/js/new-page.js
# Update placeholders: # Update placeholders:
# - Replace [page-name] with actual name # - Replace [page-name] with actual name
@@ -969,7 +969,7 @@ cp template.js app/static/shop/js/new-page.js
- **Logo**: Available in both light and dark versions - **Logo**: Available in both light and dark versions
- **Custom CSS**: Store-specific styles automatically injected - **Custom CSS**: Store-specific styles automatically injected
### Shop Layout Functions ### Storefront Layout Functions
- `addToCart(product, quantity)`: Add item to cart - `addToCart(product, quantity)`: Add item to cart
- `showToast(message, type)`: Show notification - `showToast(message, type)`: Show notification
- `formatPrice(amount)`: Format as currency - `formatPrice(amount)`: Format as currency
@@ -991,4 +991,4 @@ await apiClient.post('/endpoint', { data });
--- ---
This template provides a complete, theme-aware pattern for building shop pages with consistent structure, store branding, cart integration, and excellent user experience across all devices. This template provides a complete, theme-aware pattern for building storefront pages with consistent structure, store branding, cart integration, and excellent user experience across all devices.

View File

@@ -21,7 +21,7 @@ Tailwind Standalone CLI (single binary, no npm)
├── static/admin/css/tailwind.css → tailwind.output.css (Admin) ├── static/admin/css/tailwind.css → tailwind.output.css (Admin)
├── static/store/css/tailwind.css → tailwind.output.css (Store) ├── static/store/css/tailwind.css → tailwind.output.css (Store)
├── static/shop/css/tailwind.css → tailwind.output.css (Shop) ├── static/storefront/css/tailwind.css → tailwind.output.css (Shop)
└── static/public/css/tailwind.css → tailwind.output.css (Platform) └── static/public/css/tailwind.css → tailwind.output.css (Platform)
``` ```

View File

@@ -93,7 +93,7 @@ POST /api/v1/store/{store_code}/content-pages
**Public - Retrieve Page:** **Public - Retrieve Page:**
```bash ```bash
GET /api/v1/shop/content-pages/{slug} GET /api/v1/storefront/content-pages/{slug}
``` ```
## How the Two-Tier System Works ## How the Two-Tier System Works

View File

@@ -292,7 +292,7 @@ python scripts/create_landing_page.py
make dev make dev
# 6. Test the shop # 6. Test the shop
# Visit: http://localhost:8000/stores/orion/shop/ # Visit: http://localhost:8000/storefront/orion
``` ```
### Common Issues ### Common Issues
@@ -307,7 +307,7 @@ python scripts/create_inventory.py
```bash ```bash
# Solution: Create landing page for the store # Solution: Create landing page for the store
python scripts/create_landing_page.py python scripts/create_landing_page.py
# OR the store will auto-redirect to /shop/ # OR the store will auto-redirect to /storefront/
``` ```
**Problem: No products showing in shop** **Problem: No products showing in shop**

View File

@@ -145,9 +145,9 @@ The attachment size limit is configurable via platform settings:
- **Default:** 10 - **Default:** 10
- **Category:** messaging - **Category:** messaging
## Shop (Customer) Interface ## Storefront (Customer) Interface
### API Endpoints (`/api/v1/shop/messages`) ### API Endpoints (`/api/v1/storefront/messages`)
| Endpoint | Method | Description | | Endpoint | Method | Description |
|----------|--------|-------------| |----------|--------|-------------|
@@ -160,8 +160,8 @@ The attachment size limit is configurable via platform settings:
### Frontend ### Frontend
- **Template:** `app/templates/shop/account/messages.html` - **Template:** `app/templates/storefront/account/messages.html`
- **Page Route:** `/shop/account/messages` and `/shop/account/messages/{conversation_id}` - **Page Route:** `/storefront/account/messages` and `/storefront/account/messages/{conversation_id}`
Features: Features:
- Conversation list with unread badges - Conversation list with unread badges
@@ -236,7 +236,7 @@ Messages is available under "Platform Administration" section.
### Store Sidebar ### Store Sidebar
Messages is available under "Sales" section. Messages is available under "Sales" section.
### Shop Account Dashboard ### Storefront Account Dashboard
Messages card is available on the customer account dashboard with unread count badge. Messages card is available on the customer account dashboard with unread count badge.
### Header Badge ### Header Badge

View File

@@ -63,9 +63,9 @@ class PasswordResetToken(Base):
### API Endpoints ### API Endpoints
**File:** `app/api/v1/shop/auth.py` **File:** `app/api/v1/storefront/auth.py`
#### POST /api/v1/shop/auth/forgot-password #### POST /api/v1/storefront/auth/forgot-password
Request a password reset link. Request a password reset link.
@@ -97,7 +97,7 @@ def forgot_password(request: Request, email: str, db: Session = Depends(get_db))
if customer: if customer:
# Generate token and send email # Generate token and send email
plaintext_token = PasswordResetToken.create_for_customer(db, customer.id) plaintext_token = PasswordResetToken.create_for_customer(db, customer.id)
reset_link = f"{scheme}://{host}/shop/account/reset-password?token={plaintext_token}" reset_link = f"{scheme}://{host}/storefront/account/reset-password?token={plaintext_token}"
email_service.send_template( email_service.send_template(
template_code="password_reset", template_code="password_reset",
@@ -119,7 +119,7 @@ def forgot_password(request: Request, email: str, db: Session = Depends(get_db))
--- ---
#### POST /api/v1/shop/auth/reset-password #### POST /api/v1/storefront/auth/reset-password
Reset password using token from email. Reset password using token from email.
@@ -279,8 +279,8 @@ class PasswordTooShortException(ValidationException):
#### Forgot Password Page #### Forgot Password Page
**Template:** `app/templates/shop/account/forgot-password.html` **Template:** `app/templates/storefront/account/forgot-password.html`
**Route:** `/shop/account/forgot-password` **Route:** `/storefront/account/forgot-password`
Features: Features:
- Email input form - Email input form
@@ -290,8 +290,8 @@ Features:
#### Reset Password Page #### Reset Password Page
**Template:** `app/templates/shop/account/reset-password.html` **Template:** `app/templates/storefront/account/reset-password.html`
**Route:** `/shop/account/reset-password?token=...` **Route:** `/storefront/account/reset-password?token=...`
Features: Features:
- New password input - New password input
@@ -379,15 +379,15 @@ If you didn't request this, you can safely ignore this email.
├── alembic/versions/ ├── alembic/versions/
│ └── t8b9c0d1e2f3_add_password_reset_tokens.py │ └── t8b9c0d1e2f3_add_password_reset_tokens.py
├── app/ ├── app/
│ ├── api/v1/shop/ │ ├── api/v1/storefront/
│ │ └── auth.py │ │ └── auth.py
│ ├── exceptions/ │ ├── exceptions/
│ │ └── customer.py │ │ └── customer.py
│ ├── routes/ │ ├── routes/
│ │ └── shop_pages.py │ │ └── storefront_pages.py
│ ├── services/ │ ├── services/
│ │ └── customer_service.py │ │ └── customer_service.py
│ └── templates/shop/account/ │ └── templates/storefront/account/
│ ├── forgot-password.html │ ├── forgot-password.html
│ └── reset-password.html │ └── reset-password.html
├── models/ ├── models/

View File

@@ -68,8 +68,8 @@ admin.platform.com → Admin Interface
**3. Path-Based Mode** (Development Only) **3. Path-Based Mode** (Development Only)
``` ```
platform.com/stores/store1/shop → Store 1 Shop platform.com/storefront/store1 → Store 1 Shop
platform.com/stores/store2/shop → Store 2 Shop platform.com/storefront/store2 → Store 2 Shop
platform.com/admin → Admin Interface platform.com/admin → Admin Interface
``` ```

View File

@@ -1,4 +1,4 @@
# Shop Frontend Features - Testing Checklist # Storefront Frontend Features - Testing Checklist
**Last Updated:** 2026-01-08 **Last Updated:** 2026-01-08
**Total Pages:** 15+ **Total Pages:** 15+
@@ -11,7 +11,7 @@
### Homepage Display ### Homepage Display
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Load homepage | `/shop/` | GET | Working | | Load homepage | `/storefront/` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Hero section with store branding - [ ] Hero section with store branding
@@ -30,10 +30,10 @@
### Products List ### Products List
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Browse all products | `/shop/products` | GET | Working | | Browse all products | `/storefront/products` | GET | Working |
| API: List products | `/api/v1/shop/products` | GET | Working | | API: List products | `/api/v1/storefront/products` | GET | Working |
| Filter featured products | `/api/v1/shop/products?is_featured=true` | GET | Working | | Filter featured products | `/api/v1/storefront/products?is_featured=true` | GET | Working |
| Pagination | `/api/v1/shop/products?skip=&limit=` | GET | Working | | Pagination | `/api/v1/storefront/products?skip=&limit=` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Product grid display (2-4 columns) - [ ] Product grid display (2-4 columns)
@@ -58,8 +58,8 @@
### Product Detail Page ### Product Detail Page
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| View product details | `/shop/products/{product_id}` | GET | Working | | View product details | `/storefront/products/{product_id}` | GET | Working |
| API: Get product | `/api/v1/shop/products/{product_id}` | GET | Working | | API: Get product | `/api/v1/storefront/products/{product_id}` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Main product image - [ ] Main product image
@@ -85,9 +85,9 @@
### Product Search ### Product Search
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Search page | `/shop/search` | GET | Working | | Search page | `/storefront/search` | GET | Working |
| Search with query | `/shop/search?q=` | GET | Working | | Search with query | `/storefront/search?q=` | GET | Working |
| API: Search products | `/api/v1/shop/products/search?q=` | GET | Working | | API: Search products | `/api/v1/storefront/products/search?q=` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Search input field - [ ] Search input field
@@ -118,12 +118,12 @@
### Cart Management ### Cart Management
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| View cart page | `/shop/cart` | GET | Working | | View cart page | `/storefront/cart` | GET | Working |
| API: Get cart | `/api/v1/shop/cart/{session_id}` | GET | Working | | API: Get cart | `/api/v1/storefront/cart/{session_id}` | GET | Working |
| API: Add to cart | `/api/v1/shop/cart/{session_id}/items` | POST | Working | | API: Add to cart | `/api/v1/storefront/cart/{session_id}/items` | POST | Working |
| API: Update quantity | `/api/v1/shop/cart/{session_id}/items/{product_id}` | PUT | Working | | API: Update quantity | `/api/v1/storefront/cart/{session_id}/items/{product_id}` | PUT | Working |
| API: Remove item | `/api/v1/shop/cart/{session_id}/items/{product_id}` | DELETE | Working | | API: Remove item | `/api/v1/storefront/cart/{session_id}/items/{product_id}` | DELETE | Working |
| API: Clear cart | `/api/v1/shop/cart/{session_id}` | DELETE | Working | | API: Clear cart | `/api/v1/storefront/cart/{session_id}` | DELETE | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Cart item list - [ ] Cart item list
@@ -162,8 +162,8 @@
### Checkout Process ### Checkout Process
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Checkout page | `/shop/checkout` | GET | Working | | Checkout page | `/storefront/checkout` | GET | Working |
| API: Place order | `/api/v1/shop/orders` | POST | Working | | API: Place order | `/api/v1/storefront/orders` | POST | Working |
### Step 1: Contact & Shipping ### Step 1: Contact & Shipping
**Form Fields:** **Form Fields:**
@@ -225,8 +225,8 @@
### Login ### Login
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Login page | `/shop/account/login` | GET | Working | | Login page | `/storefront/account/login` | GET | Working |
| API: Login | `/api/v1/shop/auth/login` | POST | Working | | API: Login | `/api/v1/storefront/auth/login` | POST | Working |
**Form Fields:** **Form Fields:**
- [ ] Email/username (required) - [ ] Email/username (required)
@@ -251,8 +251,8 @@
### Registration ### Registration
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Register page | `/shop/account/register` | GET | Working | | Register page | `/storefront/account/register` | GET | Working |
| API: Register | `/api/v1/shop/auth/register` | POST | Working | | API: Register | `/api/v1/storefront/auth/register` | POST | Working |
**Form Fields:** **Form Fields:**
- [ ] First name (required) - [ ] First name (required)
@@ -283,8 +283,8 @@
### Forgot Password ### Forgot Password
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Forgot password page | `/shop/account/forgot-password` | GET | Working | | Forgot password page | `/storefront/account/forgot-password` | GET | Working |
| API: Request reset | `/api/v1/shop/auth/forgot-password` | POST | Working | | API: Request reset | `/api/v1/storefront/auth/forgot-password` | POST | Working |
**Form Fields:** **Form Fields:**
- [ ] Email (required) - [ ] Email (required)
@@ -303,8 +303,8 @@
### Reset Password ### Reset Password
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Reset password page | `/shop/account/reset-password` | GET | Working | | Reset password page | `/storefront/account/reset-password` | GET | Working |
| API: Reset password | `/api/v1/shop/auth/reset-password` | POST | Working | | API: Reset password | `/api/v1/storefront/auth/reset-password` | POST | Working |
**Form Fields:** **Form Fields:**
- [ ] Reset token (auto-filled from URL) - [ ] Reset token (auto-filled from URL)
@@ -327,7 +327,7 @@
### Logout ### Logout
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| API: Logout | `/api/v1/shop/auth/logout` | POST | Working | | API: Logout | `/api/v1/storefront/auth/logout` | POST | Working |
--- ---
@@ -336,8 +336,8 @@
### Account Dashboard ### Account Dashboard
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Dashboard page | `/shop/account/dashboard` | GET | Working | | Dashboard page | `/storefront/account/dashboard` | GET | Working |
| API: Get profile | `/api/v1/shop/profile` | GET | Working | | API: Get profile | `/api/v1/storefront/profile` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Welcome message with name - [ ] Welcome message with name
@@ -354,9 +354,9 @@
### Profile Management ### Profile Management
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Profile page | `/shop/account/profile` | GET | Working | | Profile page | `/storefront/account/profile` | GET | Working |
| API: Get profile | `/api/v1/shop/profile` | GET | Working | | API: Get profile | `/api/v1/storefront/profile` | GET | Working |
| API: Update profile | `/api/v1/shop/profile` | PUT | Working | | API: Update profile | `/api/v1/storefront/profile` | PUT | Working |
**Profile Form Fields:** **Profile Form Fields:**
- [ ] First name - [ ] First name
@@ -382,7 +382,7 @@
### Change Password ### Change Password
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| API: Change password | `/api/v1/shop/profile/password` | PUT | Working | | API: Change password | `/api/v1/storefront/profile/password` | PUT | Working |
**Password Change Form:** **Password Change Form:**
- [ ] Current password (required) - [ ] Current password (required)
@@ -406,8 +406,8 @@
### Address List ### Address List
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Addresses page | `/shop/account/addresses` | GET | Working | | Addresses page | `/storefront/account/addresses` | GET | Working |
| API: List addresses | `/api/v1/shop/addresses` | GET | Working | | API: List addresses | `/api/v1/storefront/addresses` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Address cards grid - [ ] Address cards grid
@@ -421,11 +421,11 @@
### Address CRUD ### Address CRUD
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| API: Get single address | `/api/v1/shop/addresses/{address_id}` | GET | Working | | API: Get single address | `/api/v1/storefront/addresses/{address_id}` | GET | Working |
| API: Create address | `/api/v1/shop/addresses` | POST | Working | | API: Create address | `/api/v1/storefront/addresses` | POST | Working |
| API: Update address | `/api/v1/shop/addresses/{address_id}` | PUT | Working | | API: Update address | `/api/v1/storefront/addresses/{address_id}` | PUT | Working |
| API: Set as default | `/api/v1/shop/addresses/{address_id}/default` | PUT | Working | | API: Set as default | `/api/v1/storefront/addresses/{address_id}/default` | PUT | Working |
| API: Delete address | `/api/v1/shop/addresses/{address_id}` | DELETE | Working | | API: Delete address | `/api/v1/storefront/addresses/{address_id}` | DELETE | Working |
**Address Form Fields:** **Address Form Fields:**
- [ ] Address type (shipping/billing) - [ ] Address type (shipping/billing)
@@ -466,8 +466,8 @@
### Orders List ### Orders List
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Orders page | `/shop/account/orders` | GET | Working | | Orders page | `/storefront/account/orders` | GET | Working |
| API: List orders | `/api/v1/shop/orders` | GET | Working | | API: List orders | `/api/v1/storefront/orders` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Order cards list - [ ] Order cards list
@@ -491,8 +491,8 @@
### Order Detail ### Order Detail
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Order detail page | `/shop/account/orders/{order_id}` | GET | Working | | Order detail page | `/storefront/account/orders/{order_id}` | GET | Working |
| API: Get order | `/api/v1/shop/orders/{order_id}` | GET | Working | | API: Get order | `/api/v1/storefront/orders/{order_id}` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Order header (number, date, status) - [ ] Order header (number, date, status)
@@ -508,7 +508,7 @@
### Invoice Download ### Invoice Download
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| API: Download invoice | `/api/v1/shop/orders/{order_id}/invoice` | GET | Working | | API: Download invoice | `/api/v1/storefront/orders/{order_id}/invoice` | GET | Working |
**Note:** Only available for orders with status: processing, partially_shipped, shipped, delivered, completed **Note:** Only available for orders with status: processing, partially_shipped, shipped, delivered, completed
@@ -519,9 +519,9 @@
### Messages List ### Messages List
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| Messages page | `/shop/account/messages` | GET | Working | | Messages page | `/storefront/account/messages` | GET | Working |
| API: List conversations | `/api/v1/shop/messages` | GET | Working | | API: List conversations | `/api/v1/storefront/messages` | GET | Working |
| API: Get unread count | `/api/v1/shop/messages/unread-count` | GET | Working | | API: Get unread count | `/api/v1/storefront/messages/unread-count` | GET | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Conversation list - [ ] Conversation list
@@ -536,9 +536,9 @@
### Conversation Detail ### Conversation Detail
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| API: Get conversation | `/api/v1/shop/messages/{conversation_id}` | GET | Working | | API: Get conversation | `/api/v1/storefront/messages/{conversation_id}` | GET | Working |
| API: Send message | `/api/v1/shop/messages/{conversation_id}/messages` | POST | Working | | API: Send message | `/api/v1/storefront/messages/{conversation_id}/messages` | POST | Working |
| API: Mark as read | `/api/v1/shop/messages/{conversation_id}/read` | PUT | Working | | API: Mark as read | `/api/v1/storefront/messages/{conversation_id}/read` | PUT | Working |
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Message thread display - [ ] Message thread display
@@ -562,8 +562,8 @@
### Attachment Download ### Attachment Download
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| API: Download attachment | `/api/v1/shop/messages/{conversation_id}/attachments/{attachment_id}` | GET | Working | | API: Download attachment | `/api/v1/storefront/messages/{conversation_id}/attachments/{attachment_id}` | GET | Working |
| API: Get thumbnail | `/api/v1/shop/messages/{conversation_id}/attachments/{attachment_id}/thumbnail` | GET | Working | | API: Get thumbnail | `/api/v1/storefront/messages/{conversation_id}/attachments/{attachment_id}/thumbnail` | GET | Working |
--- ---
@@ -572,17 +572,17 @@
### CMS Pages ### CMS Pages
| Test Case | Route | Method | Status | | Test Case | Route | Method | Status |
|-----------|-------|--------|--------| |-----------|-------|--------|--------|
| View content page | `/shop/page/{slug}` | GET | Working | | View content page | `/storefront/page/{slug}` | GET | Working |
| API: Get navigation | `/api/v1/shop/content-pages/navigation` | GET | Working | | API: Get navigation | `/api/v1/storefront/content-pages/navigation` | GET | Working |
| API: Get page content | `/api/v1/shop/content-pages/{slug}` | GET | Working | | API: Get page content | `/api/v1/storefront/content-pages/{slug}` | GET | Working |
**Common Pages to Test:** **Common Pages to Test:**
- [ ] About Us (`/shop/about`) - [ ] About Us (`/storefront/about`)
- [ ] Contact (`/shop/contact`) - [ ] Contact (`/storefront/contact`)
- [ ] Terms & Conditions (`/shop/terms`) - [ ] Terms & Conditions (`/storefront/terms`)
- [ ] Privacy Policy (`/shop/privacy`) - [ ] Privacy Policy (`/storefront/privacy`)
- [ ] Return Policy (`/shop/returns`) - [ ] Return Policy (`/storefront/returns`)
- [ ] FAQ (`/shop/faq`) - [ ] FAQ (`/storefront/faq`)
**UI Elements to Test:** **UI Elements to Test:**
- [ ] Page title - [ ] Page title

View File

@@ -1,6 +1,6 @@
# Shop Frontend Troubleshooting # Storefront Troubleshooting
Common issues and solutions for the store shop frontend. Common issues and solutions for the store storefront.
## Cart and Product Issues ## Cart and Product Issues
@@ -78,7 +78,7 @@ async init() {
**Verify Fix:** **Verify Fix:**
Open browser console and check for: Open browser console and check for:
``` ```
🔍 [SHOP] Session ID: session_1763765104510_zc866tt5d 🔍 [STOREFRONT] Session ID: session_1763765104510_zc866tt5d
``` ```
If you see `undefined`, the parent init isn't being called properly. If you see `undefined`, the parent init isn't being called properly.
@@ -95,11 +95,11 @@ If you see `undefined`, the parent init isn't being called properly.
- Page is just plain HTML - Page is just plain HTML
**Root Cause:** **Root Cause:**
Template doesn't extend `shop/base.html` and has hardcoded non-existent CSS references. Template doesn't extend `storefront/base.html` and has hardcoded non-existent CSS references.
**How it Works:** **How it Works:**
- All shop pages should extend `shop/base.html` - All storefront pages should extend `storefront/base.html`
- Base template includes Tailwind CSS and shop styles - Base template includes Tailwind CSS and storefront styles
- Standalone HTML pages with `<link>` tags won't work - Standalone HTML pages with `<link>` tags won't work
**Solution:** **Solution:**
@@ -118,7 +118,7 @@ Refactor template to extend base:
</html> </html>
{# AFTER: Extends base #} {# AFTER: Extends base #}
{% extends "shop/base.html" %} {% extends "storefront/base.html" %}
{% block title %}My Page{% endblock %} {% block title %}My Page{% endblock %}
@@ -130,16 +130,16 @@ Refactor template to extend base:
``` ```
**Templates Already Fixed:** **Templates Already Fixed:**
-`shop/product.html` - Refactored to extend base -`storefront/product.html` - Refactored to extend base
-`shop/cart.html` - Refactored to extend base -`storefront/cart.html` - Refactored to extend base
-`shop/products.html` - Already extends base -`storefront/products.html` - Already extends base
-`shop/home.html` - Already extends base -`storefront/home.html` - Already extends base
### Images Not Loading / Placeholder Not Showing ### Images Not Loading / Placeholder Not Showing
**Symptoms:** **Symptoms:**
- Product images show broken image icon - Product images show broken image icon
- Console shows 404: `/static/shop/img/placeholder.jpg` - Console shows 404: `/static/storefront/img/placeholder.jpg`
- OR images point to fake URLs like `https://orion.example.com/images/product-1.jpg` - OR images point to fake URLs like `https://orion.example.com/images/product-1.jpg`
**Root Cause:** **Root Cause:**
@@ -148,18 +148,18 @@ Refactor template to extend base:
**How it Works:** **How it Works:**
- Products have `marketplace_product.image_link` URLs - Products have `marketplace_product.image_link` URLs
- Template uses fallback: `:src="image || '/static/shop/img/placeholder.svg'"` - Template uses fallback: `:src="image || '/static/storefront/img/placeholder.svg'"`
- `@error` handler switches to placeholder when image fails to load - `@error` handler switches to placeholder when image fails to load
- Browsers won't render SVG content from `.jpg` files - Browsers won't render SVG content from `.jpg` files
**Solution Already Implemented:** **Solution Already Implemented:**
- Created proper `/static/shop/img/placeholder.svg` - Created proper `/static/storefront/img/placeholder.svg`
- Added `@error` handlers to all image tags: - Added `@error` handlers to all image tags:
```html ```html
<img <img
:src="product.image_link || '/static/shop/img/placeholder.svg'" :src="product.image_link || '/static/storefront/img/placeholder.svg'"
@error="$el.src = '/static/shop/img/placeholder.svg'" @error="$el.src = '/static/storefront/img/placeholder.svg'"
alt="Product image" alt="Product image"
> >
``` ```
@@ -185,34 +185,34 @@ conn.commit()
**Symptoms:** **Symptoms:**
- Clicking product shows 404 error - Clicking product shows 404 error
- URL is: `/stores/orion/shop/shop/products/4` (double `/shop/`) - URL is: `/storefront/orion/storefront/products/4` (double `/storefront/`)
- Server log shows route not found - Server log shows route not found
**Root Cause:** **Root Cause:**
Duplicate `/shop` prefix in route definitions when router already has prefix. Duplicate `/storefront` prefix in route definitions when router already has prefix.
**How it Works:** **How it Works:**
```python ```python
# Router is mounted with prefix # Router is mounted with prefix
app.include_router(shop_pages.router, prefix="/shop") app.include_router(storefront_pages.router, prefix="/storefront")
# Route decorator should NOT repeat the prefix # Route decorator should NOT repeat the prefix
@router.get("/products/{id}") # ✅ Correct @router.get("/products/{id}") # ✅ Correct
@router.get("/shop/products/{id}") # ❌ Wrong - creates /shop/shop/products/{id} @router.get("/storefront/products/{id}") # ❌ Wrong - creates /storefront/storefront/products/{id}
``` ```
**Solution Already Implemented:** **Solution Already Implemented:**
All routes in `shop_pages.py` have been fixed to remove duplicate `/shop/` prefix. All routes in `storefront_pages.py` have been fixed to remove duplicate `/storefront/` prefix.
### Missing `/shop/` in Links ### Missing `/storefront/` in Links
**Symptoms:** **Symptoms:**
- Links go to `/stores/orion/products` instead of `/stores/orion/shop/products` - Links go to `/storefront/orion/products` instead of correct storefront URLs
- 404 errors on navigation - 404 errors on navigation
- Footer/header links broken - Footer/header links broken
**Root Cause:** **Root Cause:**
Template links missing `/shop/` prefix after `{{ base_url }}`. Template links missing `/storefront/` prefix after `{{ base_url }}`.
**Solution:** **Solution:**
```jinja2 ```jinja2
@@ -220,15 +220,15 @@ Template links missing `/shop/` prefix after `{{ base_url }}`.
<a href="{{ base_url }}products">Products</a> <a href="{{ base_url }}products">Products</a>
{# CORRECT #} {# CORRECT #}
<a href="{{ base_url }}shop/products">Products</a> <a href="{{ base_url }}storefront/products">Products</a>
``` ```
**All Templates Fixed:** **All Templates Fixed:**
-`shop/base.html` - Header, footer, navigation -`storefront/base.html` - Header, footer, navigation
-`shop/products.html` - Product links -`storefront/products.html` - Product links
-`shop/product.html` - Breadcrumbs, related products -`storefront/product.html` - Breadcrumbs, related products
-`shop/cart.html` - Continue shopping, checkout -`storefront/cart.html` - Continue shopping, checkout
-`shop/errors/404.html` - All fallback links -`storefront/errors/404.html` - All fallback links
## Landing Page Issues ## Landing Page Issues
@@ -247,7 +247,7 @@ No landing page created for store.
if has_landing_page(): if has_landing_page():
return render_landing_page() return render_landing_page()
else: else:
return redirect("/shop/") # Fallback return redirect("/storefront/") # Fallback
``` ```
**Solution:** **Solution:**
@@ -258,12 +258,12 @@ python scripts/create_landing_page.py
``` ```
**Or Accept Redirect:** **Or Accept Redirect:**
The system auto-redirects to `/shop/` if no landing page exists. This is normal behavior. The system auto-redirects to `/storefront/` if no landing page exists. This is normal behavior.
### Breadcrumb "Home" Points to Wrong Place ### Breadcrumb "Home" Points to Wrong Place
**Symptoms:** **Symptoms:**
- Clicking "Home" in breadcrumb goes to shop instead of landing page - Clicking "Home" in breadcrumb goes to storefront instead of landing page
- Want "Home" to point to store root (/) - Want "Home" to point to store root (/)
**Solution Already Implemented:** **Solution Already Implemented:**
@@ -271,12 +271,12 @@ The system auto-redirects to `/shop/` if no landing page exists. This is normal
{# Breadcrumb Home link points to store root #} {# Breadcrumb Home link points to store root #}
<a href="{{ base_url }}">Home</a> <a href="{{ base_url }}">Home</a>
{# Shop homepage link #} {# Storefront homepage link #}
<a href="{{ base_url }}shop/">Shop</a> <a href="{{ base_url }}storefront/">Storefront</a>
``` ```
Navigation pattern: Navigation pattern:
- **Logo click** → `/shop/` (stays in shop for convenience) - **Logo click** → `/storefront/` (stays in storefront for convenience)
- **Breadcrumb "Home"** → `/` (returns to landing page/root) - **Breadcrumb "Home"** → `/` (returns to landing page/root)
- **Header "Home" link** → `/` (returns to landing page/root) - **Header "Home" link** → `/` (returns to landing page/root)
@@ -317,7 +317,7 @@ return {
### Product ID is Undefined ### Product ID is Undefined
**Symptoms:** **Symptoms:**
- API call: `/api/v1/shop/products/undefined` - API call: `/api/v1/storefront/products/undefined`
- Console: `productId: undefined` - Console: `productId: undefined`
**Root Cause:** **Root Cause:**
@@ -346,12 +346,12 @@ Alpine.data('productDetail', () => ({
### Enable Verbose Logging ### Enable Verbose Logging
```javascript ```javascript
// In shop-layout.js, the shopLog is already configured // In storefront-layout.js, the storefrontLog is already configured
// Check browser console for: // Check browser console for:
🛒 [SHOP] Shop layout initializing... 🛒 [STOREFRONT] Storefront layout initializing...
🔍 [SHOP] Session ID: session_xxx 🔍 [STOREFRONT] Session ID: session_xxx
[SHOP] Adding to cart: {...} [STOREFRONT] Adding to cart: {...}
[SHOP] Cart loaded: 3 items [STOREFRONT] Cart loaded: 3 items
``` ```
### Check Session ID ### Check Session ID
@@ -372,10 +372,10 @@ Alpine.$data(document.querySelector('[x-data]')).product
### Check API Responses ### Check API Responses
In browser Network tab, filter by "shop" and check: In browser Network tab, filter by "storefront" and check:
- GET `/api/v1/shop/products` - Should return products array - GET `/api/v1/storefront/products` - Should return products array
- GET `/api/v1/shop/cart/{session_id}` - Should return cart items - GET `/api/v1/storefront/cart/{session_id}` - Should return cart items
- POST `/api/v1/shop/cart/{session_id}/items` - Should return success - POST `/api/v1/storefront/cart/{session_id}/items` - Should return success
## Getting Help ## Getting Help
@@ -383,7 +383,7 @@ In browser Network tab, filter by "shop" and check:
2. Check browser console for errors 2. Check browser console for errors
3. Check server logs for API errors 3. Check server logs for API errors
4. Verify database has inventory entries 4. Verify database has inventory entries
5. Ensure all templates extend `shop/base.html` 5. Ensure all templates extend `storefront/base.html`
6. Check that session ID is initialized 6. Check that session ID is initialized
If still stuck, provide: If still stuck, provide: