feat: storefront subscription access guard + module-driven nav + URL rename
Add StorefrontAccessMiddleware that blocks storefront access for stores without an active subscription, returning a multilingual unavailable page (en/fr/de/lb) for page requests and JSON 403 for API requests. Multi-platform aware: resolves subscription for detected platform with fallback to primary. Also includes yesterday's session work: - Module-driven storefront navigation via FrontendType.STOREFRONT menu declarations - shop/ → storefront/ URL rename across 30+ templates - Subscription context (tier_code) passed to storefront templates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,10 +11,10 @@
|
||||
{# Breadcrumbs #}
|
||||
<nav class="mb-6" aria-label="Breadcrumb">
|
||||
<ol class="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<li><a href="{{ base_url }}shop/" class="hover:text-primary">Home</a></li>
|
||||
<li><a href="{{ base_url }}storefront/" class="hover:text-primary">Home</a></li>
|
||||
<li class="flex items-center">
|
||||
<span class="h-4 w-4 mx-2" x-html="$icon('chevron-right', 'h-4 w-4')"></span>
|
||||
<a href="{{ base_url }}shop/cart" class="hover:text-primary">Cart</a>
|
||||
<a href="{{ base_url }}storefront/cart" class="hover:text-primary">Cart</a>
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<span class="h-4 w-4 mx-2" x-html="$icon('chevron-right', 'h-4 w-4')"></span>
|
||||
@@ -36,7 +36,7 @@
|
||||
<span class="mx-auto h-12 w-12 text-gray-400 mb-4 block" x-html="$icon('shopping-bag', 'h-12 w-12 mx-auto')"></span>
|
||||
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-2">Your cart is empty</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-6">Add some products before checking out.</p>
|
||||
<a href="{{ base_url }}shop/products" class="inline-block px-6 py-3 text-white rounded-lg transition-colors" style="background-color: var(--color-primary)">
|
||||
<a href="{{ base_url }}storefront/products" class="inline-block px-6 py-3 text-white rounded-lg transition-colors" style="background-color: var(--color-primary)">
|
||||
Browse Products
|
||||
</a>
|
||||
</div>
|
||||
@@ -384,8 +384,8 @@
|
||||
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<template x-for="item in cartItems" :key="item.product_id">
|
||||
<div class="py-4 flex items-center gap-4">
|
||||
<img loading="lazy" :src="item.image_url || '/static/shop/img/placeholder.svg'"
|
||||
@error="$el.src = '/static/shop/img/placeholder.svg'"
|
||||
<img loading="lazy" :src="item.image_url || '/static/storefront/img/placeholder.svg'"
|
||||
@error="$el.src = '/static/storefront/img/placeholder.svg'"
|
||||
class="w-16 h-16 object-cover rounded-lg">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-gray-900 dark:text-white truncate" x-text="item.name"></p>
|
||||
@@ -428,8 +428,8 @@
|
||||
<template x-for="item in cartItems" :key="item.product_id">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<img loading="lazy" :src="item.image_url || '/static/shop/img/placeholder.svg'"
|
||||
@error="$el.src = '/static/shop/img/placeholder.svg'"
|
||||
<img loading="lazy" :src="item.image_url || '/static/storefront/img/placeholder.svg'"
|
||||
@error="$el.src = '/static/storefront/img/placeholder.svg'"
|
||||
class="w-12 h-12 object-cover rounded">
|
||||
<span class="absolute -top-2 -right-2 w-5 h-5 bg-gray-500 text-white text-xs rounded-full flex items-center justify-center" x-text="item.quantity"></span>
|
||||
</div>
|
||||
@@ -611,7 +611,7 @@ function checkoutPage() {
|
||||
|
||||
async loadCustomerData() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/shop/auth/me');
|
||||
const response = await fetch('/api/v1/storefront/auth/me');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.isLoggedIn = true;
|
||||
@@ -639,7 +639,7 @@ function checkoutPage() {
|
||||
|
||||
async loadSavedAddresses() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/shop/addresses');
|
||||
const response = await fetch('/api/v1/storefront/addresses');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.savedAddresses = data.addresses || [];
|
||||
@@ -706,7 +706,7 @@ function checkoutPage() {
|
||||
async loadCart() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(`/api/v1/shop/cart/${this.sessionId}`);
|
||||
const response = await fetch(`/api/v1/storefront/cart/${this.sessionId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.cartItems = data.items || [];
|
||||
@@ -790,7 +790,7 @@ function checkoutPage() {
|
||||
country_iso: this.shippingAddress.country_iso,
|
||||
is_default: this.shippingAddresses.length === 0 // Make default if first address
|
||||
};
|
||||
const response = await fetch('/api/v1/shop/addresses', {
|
||||
const response = await fetch('/api/v1/storefront/addresses', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(addressData)
|
||||
@@ -820,7 +820,7 @@ function checkoutPage() {
|
||||
country_iso: this.billingAddress.country_iso,
|
||||
is_default: this.billingAddresses.length === 0 // Make default if first address
|
||||
};
|
||||
const response = await fetch('/api/v1/shop/addresses', {
|
||||
const response = await fetch('/api/v1/storefront/addresses', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(addressData)
|
||||
@@ -883,7 +883,7 @@ function checkoutPage() {
|
||||
|
||||
console.log('[CHECKOUT] Placing order:', orderData);
|
||||
|
||||
const response = await fetch('/api/v1/shop/orders', {
|
||||
const response = await fetch('/api/v1/storefront/orders', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -900,7 +900,7 @@ function checkoutPage() {
|
||||
console.log('[CHECKOUT] Order placed:', order.order_number);
|
||||
|
||||
// Redirect to confirmation page
|
||||
window.location.href = '{{ base_url }}shop/order-confirmation?order=' + order.order_number;
|
||||
window.location.href = '{{ base_url }}storefront/order-confirmation?order=' + order.order_number;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[CHECKOUT] Error placing order:', error);
|
||||
|
||||
Reference in New Issue
Block a user