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:
2026-02-18 13:27:31 +01:00
parent 682213fdee
commit 2c710ad416
46 changed files with 1484 additions and 231 deletions

View File

@@ -63,7 +63,7 @@
{# Store Logo #}
<div class="flex items-center">
<a href="{{ base_url }}shop/" class="flex items-center space-x-3">
<a href="{{ base_url }}storefront/" class="flex items-center space-x-3">
{% if theme.branding.logo %}
{# Show light logo in light mode, dark logo in dark mode #}
<img x-show="!dark"
@@ -84,25 +84,28 @@
</a>
</div>
{# Navigation #}
{# Navigation — Home is always shown, module items are dynamic #}
<nav class="hidden md:flex space-x-8">
<a href="{{ base_url }}" class="text-gray-700 dark:text-gray-300 hover:text-primary">
Home
</a>
<a href="{{ base_url }}shop/products" class="text-gray-700 dark:text-gray-300 hover:text-primary">
Products
{% for item in storefront_nav.get('nav', []) %}
<a href="{{ base_url }}{{ item.route }}" class="text-gray-700 dark:text-gray-300 hover:text-primary">
{{ _(item.label_key) }}
</a>
<a href="{{ base_url }}shop/about" class="text-gray-700 dark:text-gray-300 hover:text-primary">
About
</a>
<a href="{{ base_url }}shop/contact" class="text-gray-700 dark:text-gray-300 hover:text-primary">
Contact
{% endfor %}
{# CMS pages (About, Contact) are already dynamic via header_pages #}
{% for page in header_pages|default([]) %}
<a href="{{ base_url }}storefront/{{ page.slug }}" class="text-gray-700 dark:text-gray-300 hover:text-primary">
{{ page.title }}
</a>
{% endfor %}
</nav>
{# Right side actions #}
<div class="flex items-center space-x-4">
{% if 'catalog' in enabled_modules|default([]) %}
{# Search #}
<button @click="openSearch()" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -110,9 +113,11 @@
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
{% endif %}
{% if 'cart' in enabled_modules|default([]) %}
{# Cart #}
<a href="{{ base_url }}shop/cart" class="relative p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<a href="{{ base_url }}storefront/cart" class="relative p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
@@ -123,6 +128,7 @@
style="background-color: var(--color-accent)">
</span>
</a>
{% endif %}
{# Theme toggle #}
<button @click="toggleTheme()"
@@ -181,7 +187,7 @@
{% endif %}
{# Account #}
<a href="{{ base_url }}shop/account" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<a href="{{ base_url }}storefront/account" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
@@ -256,9 +262,11 @@
<div>
<h4 class="font-semibold mb-4">Quick Links</h4>
<ul class="space-y-2">
<li><a href="{{ base_url }}shop/products" class="text-gray-600 hover:text-primary dark:text-gray-400">Products</a></li>
{% if 'catalog' in enabled_modules|default([]) %}
<li><a href="{{ base_url }}storefront/products" class="text-gray-600 hover:text-primary dark:text-gray-400">Products</a></li>
{% endif %}
{% for page in col1_pages %}
<li><a href="{{ base_url }}shop/{{ page.slug }}" class="text-gray-600 hover:text-primary dark:text-gray-400">{{ page.title }}</a></li>
<li><a href="{{ base_url }}storefront/{{ page.slug }}" class="text-gray-600 hover:text-primary dark:text-gray-400">{{ page.title }}</a></li>
{% endfor %}
</ul>
</div>
@@ -269,7 +277,7 @@
<h4 class="font-semibold mb-4">Information</h4>
<ul class="space-y-2">
{% for page in col2_pages %}
<li><a href="{{ base_url }}shop/{{ page.slug }}" class="text-gray-600 hover:text-primary dark:text-gray-400">{{ page.title }}</a></li>
<li><a href="{{ base_url }}storefront/{{ page.slug }}" class="text-gray-600 hover:text-primary dark:text-gray-400">{{ page.title }}</a></li>
{% endfor %}
</ul>
</div>
@@ -279,18 +287,20 @@
<div>
<h4 class="font-semibold mb-4">Quick Links</h4>
<ul class="space-y-2">
<li><a href="{{ base_url }}shop/products" class="text-gray-600 hover:text-primary dark:text-gray-400">Products</a></li>
<li><a href="{{ base_url }}shop/about" class="text-gray-600 hover:text-primary dark:text-gray-400">About Us</a></li>
<li><a href="{{ base_url }}shop/contact" class="text-gray-600 hover:text-primary dark:text-gray-400">Contact</a></li>
{% if 'catalog' in enabled_modules|default([]) %}
<li><a href="{{ base_url }}storefront/products" class="text-gray-600 hover:text-primary dark:text-gray-400">Products</a></li>
{% endif %}
<li><a href="{{ base_url }}storefront/about" class="text-gray-600 hover:text-primary dark:text-gray-400">About Us</a></li>
<li><a href="{{ base_url }}storefront/contact" class="text-gray-600 hover:text-primary dark:text-gray-400">Contact</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold mb-4">Information</h4>
<ul class="space-y-2">
<li><a href="{{ base_url }}shop/faq" class="text-gray-600 hover:text-primary dark:text-gray-400">FAQ</a></li>
<li><a href="{{ base_url }}shop/shipping" class="text-gray-600 hover:text-primary dark:text-gray-400">Shipping</a></li>
<li><a href="{{ base_url }}shop/returns" class="text-gray-600 hover:text-primary dark:text-gray-400">Returns</a></li>
<li><a href="{{ base_url }}storefront/faq" class="text-gray-600 hover:text-primary dark:text-gray-400">FAQ</a></li>
<li><a href="{{ base_url }}storefront/shipping" class="text-gray-600 hover:text-primary dark:text-gray-400">Shipping</a></li>
<li><a href="{{ base_url }}storefront/returns" class="text-gray-600 hover:text-primary dark:text-gray-400">Returns</a></li>
</ul>
</div>
{% endif %}
@@ -321,7 +331,7 @@
<script defer src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
{# 4. Base Shop Layout (Alpine.js component - must load before Alpine) #}
<script defer src="{{ url_for('static', path='storefront/js/storefront-layout.js') }}"></script>
<script defer src="{{ url_for('core_static', path='storefront/js/storefront-layout.js') }}"></script>
{# 5. Utilities #}
<script defer src="{{ url_for('static', path='shared/js/utils.js') }}"></script>