@@ -276,7 +276,7 @@ document.addEventListener('alpine:init', () => { try { console.log(`[SHOP] Loading product ${this.productId}...`); - const response = await fetch(`/api/v1/shop/products/${this.productId}`); + const response = await fetch(`/api/v1/storefront/products/${this.productId}`); if (!response.ok) { throw new Error('Product not found'); @@ -301,7 +301,7 @@ document.addEventListener('alpine:init', () => { this.showToast('Failed to load product', 'error'); // Redirect back to products after error setTimeout(() => { - window.location.href = '{{ base_url }}shop/products'; + window.location.href = '{{ base_url }}storefront/products'; }, 2000); } finally { this.loading = false; @@ -311,7 +311,7 @@ document.addEventListener('alpine:init', () => { // Load related products async loadRelatedProducts() { try { - const response = await fetch(`/api/v1/shop/products?limit=4`); + const response = await fetch(`/api/v1/storefront/products?limit=4`); if (response.ok) { const data = await response.json(); @@ -368,7 +368,7 @@ document.addEventListener('alpine:init', () => { this.addingToCart = true; try { - const url = `/api/v1/shop/cart/${this.sessionId}/items`; + const url = `/api/v1/storefront/cart/${this.sessionId}/items`; const payload = { product_id: parseInt(this.productId), quantity: this.quantity diff --git a/app/modules/catalog/templates/catalog/storefront/products.html b/app/modules/catalog/templates/catalog/storefront/products.html index 87ec08cc..ec531922 100644 --- a/app/modules/catalog/templates/catalog/storefront/products.html +++ b/app/modules/catalog/templates/catalog/storefront/products.html @@ -73,14 +73,14 @@
Log in to your account to view and manage your wishlist.
- + Log InSave items you like by clicking the heart icon on product pages.
- + Browse Products
@@ -84,7 +84,7 @@
{% if header_pages %}
{% for page in header_pages[:2] %}
-
đ
@@ -96,7 +96,7 @@
{% endfor %}
{% else %}
-
âšī¸
@@ -107,7 +107,7 @@
-
đ§
diff --git a/app/modules/cms/templates/cms/storefront/landing-full.html b/app/modules/cms/templates/cms/storefront/landing-full.html
index 35255635..edfeaa35 100644
--- a/app/modules/cms/templates/cms/storefront/landing-full.html
+++ b/app/modules/cms/templates/cms/storefront/landing-full.html
@@ -1,7 +1,7 @@
{# app/templates/store/landing-full.html #}
{# standalone #}
{# Full Landing Page Template - Maximum Features #}
-{% extends "shop/base.html" %}
+{% extends "storefront/base.html" %}
{% block title %}{{ store.name }}{% endblock %}
{% block meta_description %}{{ page.meta_description or store.description or store.name }}{% endblock %}
@@ -43,7 +43,7 @@
{% endif %}
-
Shop Now
@@ -174,7 +174,7 @@
Explore More
-
đī¸
@@ -190,7 +190,7 @@
{% if header_pages %}
{% for page in header_pages[:2] %}
-
đ
@@ -205,7 +205,7 @@
{% endfor %}
{% else %}
-
âšī¸
@@ -219,7 +219,7 @@
-
đ§
@@ -246,7 +246,7 @@
Join thousands of satisfied customers today
-
View All Products
diff --git a/app/modules/cms/templates/cms/storefront/landing-minimal.html b/app/modules/cms/templates/cms/storefront/landing-minimal.html
index 280d4e76..c77070a4 100644
--- a/app/modules/cms/templates/cms/storefront/landing-minimal.html
+++ b/app/modules/cms/templates/cms/storefront/landing-minimal.html
@@ -1,7 +1,7 @@
{# app/templates/store/landing-minimal.html #}
{# standalone #}
{# Minimal Landing Page Template - Ultra Clean #}
-{% extends "shop/base.html" %}
+{% extends "storefront/base.html" %}
{% block title %}{{ store.name }}{% endblock %}
{% block meta_description %}{{ page.meta_description or store.description or store.name }}{% endblock %}
@@ -37,7 +37,7 @@
{# Single CTA #}
-
Enter Shop
@@ -49,11 +49,11 @@
{% if header_pages or footer_pages %}
-
+
Products
{% for page in (header_pages or footer_pages)[:4] %}
-
+
{{ page.title }}
{% endfor %}
diff --git a/app/modules/cms/templates/cms/storefront/landing-modern.html b/app/modules/cms/templates/cms/storefront/landing-modern.html
index 7b98f241..1a4a52ee 100644
--- a/app/modules/cms/templates/cms/storefront/landing-modern.html
+++ b/app/modules/cms/templates/cms/storefront/landing-modern.html
@@ -1,7 +1,7 @@
{# app/templates/store/landing-modern.html #}
{# standalone #}
{# Modern Landing Page Template - Feature Rich #}
-{% extends "shop/base.html" %}
+{% extends "storefront/base.html" %}
{% block title %}{{ store.name }}{% endblock %}
{% block meta_description %}{{ page.meta_description or store.description or store.name }}{% endblock %}
@@ -40,7 +40,7 @@
{# CTAs #}
-
Start Shopping
@@ -137,7 +137,7 @@
Explore our collection and find what you're looking for
-
Browse Products
diff --git a/app/modules/core/utils/page_context.py b/app/modules/core/utils/page_context.py
index 7238492a..bfcc50fa 100644
--- a/app/modules/core/utils/page_context.py
+++ b/app/modules/core/utils/page_context.py
@@ -146,6 +146,29 @@ def get_context_for_frontend(
f"[CONTEXT] {len(modules_with_providers)} modules have providers but none contributed"
)
+ # Pass enabled module codes to templates for conditional rendering
+ context["enabled_modules"] = enabled_module_codes
+
+ # For storefront, build nav menu structure from module declarations
+ if frontend_type == FrontendType.STOREFRONT:
+ from app.modules.core.services.menu_discovery_service import (
+ menu_discovery_service,
+ )
+
+ platform_id = platform.id if platform else None
+ sections = menu_discovery_service.get_menu_sections_for_frontend(
+ db, FrontendType.STOREFRONT, platform_id
+ )
+ # Build dict of section_id -> list of enabled items for easy template access
+ storefront_nav: dict[str, list] = {}
+ for section in sections:
+ enabled_items = [
+ item for item in section.items if item.is_module_enabled
+ ]
+ if enabled_items:
+ storefront_nav[section.id] = enabled_items
+ context["storefront_nav"] = storefront_nav
+
# Add any extra context passed by the caller
if extra_context:
context.update(extra_context)
@@ -318,6 +341,10 @@ def get_storefront_context(
)
base_url = f"{full_prefix}{store.subdomain}/"
+ # Read subscription info set by StorefrontAccessMiddleware
+ subscription = getattr(request.state, "subscription", None)
+ subscription_tier = getattr(request.state, "subscription_tier", None)
+
# Build storefront-specific base context
storefront_base = {
"store": store,
@@ -325,6 +352,11 @@ def get_storefront_context(
"clean_path": clean_path,
"access_method": access_method,
"base_url": base_url,
+ "enabled_modules": set(),
+ "storefront_nav": {},
+ "subscription": subscription,
+ "subscription_tier": subscription_tier,
+ "tier_code": subscription_tier.code if subscription_tier else None,
}
# If no db session, return just the base context
diff --git a/app/modules/customers/definition.py b/app/modules/customers/definition.py
index 59b2109e..e77e87fb 100644
--- a/app/modules/customers/definition.py
+++ b/app/modules/customers/definition.py
@@ -132,6 +132,43 @@ customers_module = ModuleDefinition(
],
),
],
+ FrontendType.STOREFRONT: [
+ MenuSectionDefinition(
+ id="account",
+ label_key=None,
+ order=10,
+ items=[
+ MenuItemDefinition(
+ id="dashboard",
+ label_key="storefront.account.dashboard",
+ icon="home",
+ route="storefront/account/dashboard",
+ order=10,
+ ),
+ MenuItemDefinition(
+ id="profile",
+ label_key="storefront.account.profile",
+ icon="user",
+ route="storefront/account/profile",
+ order=20,
+ ),
+ MenuItemDefinition(
+ id="addresses",
+ label_key="storefront.account.addresses",
+ icon="map-pin",
+ route="storefront/account/addresses",
+ order=30,
+ ),
+ MenuItemDefinition(
+ id="settings",
+ label_key="storefront.account.settings",
+ icon="cog",
+ route="storefront/account/settings",
+ order=90,
+ ),
+ ],
+ ),
+ ],
},
is_core=True, # Customers is a core module - customer data is fundamental
# =========================================================================
diff --git a/app/modules/customers/templates/customers/storefront/addresses.html b/app/modules/customers/templates/customers/storefront/addresses.html
index 5f2fc89c..952942df 100644
--- a/app/modules/customers/templates/customers/storefront/addresses.html
+++ b/app/modules/customers/templates/customers/storefront/addresses.html
@@ -392,7 +392,7 @@ function addressesPage() {
try {
const token = localStorage.getItem('customer_token');
- const response = await fetch('/api/v1/shop/addresses', {
+ const response = await fetch('/api/v1/storefront/addresses', {
headers: {
'Authorization': token ? `Bearer ${token}` : '',
}
@@ -400,7 +400,7 @@ function addressesPage() {
if (!response.ok) {
if (response.status === 401) {
- window.location.href = '{{ base_url }}shop/account/login';
+ window.location.href = '{{ base_url }}storefront/account/login';
return;
}
throw new Error('Failed to load addresses');
@@ -461,8 +461,8 @@ function addressesPage() {
try {
const token = localStorage.getItem('customer_token');
const url = this.editingAddress
- ? `/api/v1/shop/addresses/${this.editingAddress.id}`
- : '/api/v1/shop/addresses';
+ ? `/api/v1/storefront/addresses/${this.editingAddress.id}`
+ : '/api/v1/storefront/addresses';
const method = this.editingAddress ? 'PUT' : 'POST';
const response = await fetch(url, {
@@ -500,7 +500,7 @@ function addressesPage() {
try {
const token = localStorage.getItem('customer_token');
- const response = await fetch(`/api/v1/shop/addresses/${this.deletingAddressId}`, {
+ const response = await fetch(`/api/v1/storefront/addresses/${this.deletingAddressId}`, {
method: 'DELETE',
headers: {
'Authorization': token ? `Bearer ${token}` : '',
@@ -525,7 +525,7 @@ function addressesPage() {
async setAsDefault(addressId) {
try {
const token = localStorage.getItem('customer_token');
- const response = await fetch(`/api/v1/shop/addresses/${addressId}/default`, {
+ const response = await fetch(`/api/v1/storefront/addresses/${addressId}/default`, {
method: 'PUT',
headers: {
'Authorization': token ? `Bearer ${token}` : '',
diff --git a/app/modules/customers/templates/customers/storefront/dashboard.html b/app/modules/customers/templates/customers/storefront/dashboard.html
index e11f45f2..01e321c8 100644
--- a/app/modules/customers/templates/customers/storefront/dashboard.html
+++ b/app/modules/customers/templates/customers/storefront/dashboard.html
@@ -18,7 +18,7 @@
-
@@ -36,7 +36,7 @@
-
@@ -53,7 +53,7 @@
-
@@ -67,10 +67,10 @@
-
+ x-init="fetch('/api/v1/storefront/messages/unread-count').then(r => r.json()).then(d => unreadCount = d.unread_count).catch(() => {})">
@@ -141,7 +141,7 @@ function accountDashboard() {
// Close modal
this.showLogoutModal = false;
- fetch('/api/v1/shop/auth/logout', {
+ fetch('/api/v1/storefront/auth/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -157,14 +157,14 @@ function accountDashboard() {
// Redirect to login page
setTimeout(() => {
- window.location.href = '{{ base_url }}shop/account/login';
+ window.location.href = '{{ base_url }}storefront/account/login';
}, 500);
} else {
console.error('Logout failed with status:', response.status);
this.showToast('Logout failed', 'error');
// Still redirect on failure (cookie might be deleted)
setTimeout(() => {
- window.location.href = '{{ base_url }}shop/account/login';
+ window.location.href = '{{ base_url }}storefront/account/login';
}, 1000);
}
})
@@ -173,7 +173,7 @@ function accountDashboard() {
this.showToast('Logout failed', 'error');
// Redirect anyway
setTimeout(() => {
- window.location.href = '{{ base_url }}shop/account/login';
+ window.location.href = '{{ base_url }}storefront/account/login';
}, 1000);
});
}
diff --git a/app/modules/customers/templates/customers/storefront/forgot-password.html b/app/modules/customers/templates/customers/storefront/forgot-password.html
index f54ef877..86fad3c9 100644
--- a/app/modules/customers/templates/customers/storefront/forgot-password.html
+++ b/app/modules/customers/templates/customers/storefront/forgot-password.html
@@ -39,7 +39,7 @@
{# Tailwind CSS v4 (built locally via standalone CLI) #}
-
+
@@ -143,13 +143,13 @@
Remember your password?
+ href="{{ base_url }}storefront/account/login">
Sign in
+ href="{{ base_url }}storefront/">
â Continue shopping
@@ -218,7 +218,7 @@
this.loading = true;
try {
- const response = await fetch('/api/v1/shop/auth/forgot-password', {
+ const response = await fetch('/api/v1/storefront/auth/forgot-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
diff --git a/app/modules/customers/templates/customers/storefront/login.html b/app/modules/customers/templates/customers/storefront/login.html
index 6b9ff799..64955081 100644
--- a/app/modules/customers/templates/customers/storefront/login.html
+++ b/app/modules/customers/templates/customers/storefront/login.html
@@ -38,7 +38,7 @@
{# Tailwind CSS v4 (built locally via standalone CLI) #}
-
+
@@ -127,7 +127,7 @@
style="color: var(--color-primary);">
Remember me
-
Forgot password?
@@ -150,13 +150,13 @@
Don't have an account?
+ href="{{ base_url }}storefront/account/register">
Create an account
@@ -238,7 +238,7 @@
this.loading = true;
try {
- const response = await fetch('/api/v1/shop/auth/login', {
+ const response = await fetch('/api/v1/storefront/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -263,7 +263,7 @@
// Redirect to account page or return URL
setTimeout(() => {
- const returnUrl = new URLSearchParams(window.location.search).get('return') || '{{ base_url }}shop/account';
+ const returnUrl = new URLSearchParams(window.location.search).get('return') || '{{ base_url }}storefront/account';
window.location.href = returnUrl;
}, 1000);
diff --git a/app/modules/customers/templates/customers/storefront/profile.html b/app/modules/customers/templates/customers/storefront/profile.html
index a9807ef5..36b33ef3 100644
--- a/app/modules/customers/templates/customers/storefront/profile.html
+++ b/app/modules/customers/templates/customers/storefront/profile.html
@@ -11,7 +11,7 @@
Join thousands of satisfied customers today
- View All Products diff --git a/app/modules/cms/templates/cms/storefront/landing-minimal.html b/app/modules/cms/templates/cms/storefront/landing-minimal.html index 280d4e76..c77070a4 100644 --- a/app/modules/cms/templates/cms/storefront/landing-minimal.html +++ b/app/modules/cms/templates/cms/storefront/landing-minimal.html @@ -1,7 +1,7 @@ {# app/templates/store/landing-minimal.html #} {# standalone #} {# Minimal Landing Page Template - Ultra Clean #} -{% extends "shop/base.html" %} +{% extends "storefront/base.html" %} {% block title %}{{ store.name }}{% endblock %} {% block meta_description %}{{ page.meta_description or store.description or store.name }}{% endblock %} @@ -37,7 +37,7 @@ {# Single CTA #}Explore our collection and find what you're looking for
- Browse Products diff --git a/app/modules/core/utils/page_context.py b/app/modules/core/utils/page_context.py index 7238492a..bfcc50fa 100644 --- a/app/modules/core/utils/page_context.py +++ b/app/modules/core/utils/page_context.py @@ -146,6 +146,29 @@ def get_context_for_frontend( f"[CONTEXT] {len(modules_with_providers)} modules have providers but none contributed" ) + # Pass enabled module codes to templates for conditional rendering + context["enabled_modules"] = enabled_module_codes + + # For storefront, build nav menu structure from module declarations + if frontend_type == FrontendType.STOREFRONT: + from app.modules.core.services.menu_discovery_service import ( + menu_discovery_service, + ) + + platform_id = platform.id if platform else None + sections = menu_discovery_service.get_menu_sections_for_frontend( + db, FrontendType.STOREFRONT, platform_id + ) + # Build dict of section_id -> list of enabled items for easy template access + storefront_nav: dict[str, list] = {} + for section in sections: + enabled_items = [ + item for item in section.items if item.is_module_enabled + ] + if enabled_items: + storefront_nav[section.id] = enabled_items + context["storefront_nav"] = storefront_nav + # Add any extra context passed by the caller if extra_context: context.update(extra_context) @@ -318,6 +341,10 @@ def get_storefront_context( ) base_url = f"{full_prefix}{store.subdomain}/" + # Read subscription info set by StorefrontAccessMiddleware + subscription = getattr(request.state, "subscription", None) + subscription_tier = getattr(request.state, "subscription_tier", None) + # Build storefront-specific base context storefront_base = { "store": store, @@ -325,6 +352,11 @@ def get_storefront_context( "clean_path": clean_path, "access_method": access_method, "base_url": base_url, + "enabled_modules": set(), + "storefront_nav": {}, + "subscription": subscription, + "subscription_tier": subscription_tier, + "tier_code": subscription_tier.code if subscription_tier else None, } # If no db session, return just the base context diff --git a/app/modules/customers/definition.py b/app/modules/customers/definition.py index 59b2109e..e77e87fb 100644 --- a/app/modules/customers/definition.py +++ b/app/modules/customers/definition.py @@ -132,6 +132,43 @@ customers_module = ModuleDefinition( ], ), ], + FrontendType.STOREFRONT: [ + MenuSectionDefinition( + id="account", + label_key=None, + order=10, + items=[ + MenuItemDefinition( + id="dashboard", + label_key="storefront.account.dashboard", + icon="home", + route="storefront/account/dashboard", + order=10, + ), + MenuItemDefinition( + id="profile", + label_key="storefront.account.profile", + icon="user", + route="storefront/account/profile", + order=20, + ), + MenuItemDefinition( + id="addresses", + label_key="storefront.account.addresses", + icon="map-pin", + route="storefront/account/addresses", + order=30, + ), + MenuItemDefinition( + id="settings", + label_key="storefront.account.settings", + icon="cog", + route="storefront/account/settings", + order=90, + ), + ], + ), + ], }, is_core=True, # Customers is a core module - customer data is fundamental # ========================================================================= diff --git a/app/modules/customers/templates/customers/storefront/addresses.html b/app/modules/customers/templates/customers/storefront/addresses.html index 5f2fc89c..952942df 100644 --- a/app/modules/customers/templates/customers/storefront/addresses.html +++ b/app/modules/customers/templates/customers/storefront/addresses.html @@ -392,7 +392,7 @@ function addressesPage() { try { const token = localStorage.getItem('customer_token'); - const response = await fetch('/api/v1/shop/addresses', { + const response = await fetch('/api/v1/storefront/addresses', { headers: { 'Authorization': token ? `Bearer ${token}` : '', } @@ -400,7 +400,7 @@ function addressesPage() { if (!response.ok) { if (response.status === 401) { - window.location.href = '{{ base_url }}shop/account/login'; + window.location.href = '{{ base_url }}storefront/account/login'; return; } throw new Error('Failed to load addresses'); @@ -461,8 +461,8 @@ function addressesPage() { try { const token = localStorage.getItem('customer_token'); const url = this.editingAddress - ? `/api/v1/shop/addresses/${this.editingAddress.id}` - : '/api/v1/shop/addresses'; + ? `/api/v1/storefront/addresses/${this.editingAddress.id}` + : '/api/v1/storefront/addresses'; const method = this.editingAddress ? 'PUT' : 'POST'; const response = await fetch(url, { @@ -500,7 +500,7 @@ function addressesPage() { try { const token = localStorage.getItem('customer_token'); - const response = await fetch(`/api/v1/shop/addresses/${this.deletingAddressId}`, { + const response = await fetch(`/api/v1/storefront/addresses/${this.deletingAddressId}`, { method: 'DELETE', headers: { 'Authorization': token ? `Bearer ${token}` : '', @@ -525,7 +525,7 @@ function addressesPage() { async setAsDefault(addressId) { try { const token = localStorage.getItem('customer_token'); - const response = await fetch(`/api/v1/shop/addresses/${addressId}/default`, { + const response = await fetch(`/api/v1/storefront/addresses/${addressId}/default`, { method: 'PUT', headers: { 'Authorization': token ? `Bearer ${token}` : '', diff --git a/app/modules/customers/templates/customers/storefront/dashboard.html b/app/modules/customers/templates/customers/storefront/dashboard.html index e11f45f2..01e321c8 100644 --- a/app/modules/customers/templates/customers/storefront/dashboard.html +++ b/app/modules/customers/templates/customers/storefront/dashboard.html @@ -18,7 +18,7 @@+ href="{{ base_url }}storefront/"> â Continue shopping
@@ -218,7 +218,7 @@ this.loading = true; try { - const response = await fetch('/api/v1/shop/auth/forgot-password', { + const response = await fetch('/api/v1/storefront/auth/forgot-password', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/app/modules/customers/templates/customers/storefront/login.html b/app/modules/customers/templates/customers/storefront/login.html index 6b9ff799..64955081 100644 --- a/app/modules/customers/templates/customers/storefront/login.html +++ b/app/modules/customers/templates/customers/storefront/login.html @@ -38,7 +38,7 @@ {# Tailwind CSS v4 (built locally via standalone CLI) #} - +