refactor: complete module-driven architecture migration
This commit completes the migration to a fully module-driven architecture: ## Models Migration - Moved all domain models from models/database/ to their respective modules: - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc. - cms: MediaFile, VendorTheme - messaging: Email, VendorEmailSettings, VendorEmailTemplate - core: AdminMenuConfig - models/database/ now only contains Base and TimestampMixin (infrastructure) ## Schemas Migration - Moved all domain schemas from models/schema/ to their respective modules: - tenancy: company, vendor, admin, team, vendor_domain - cms: media, image, vendor_theme - messaging: email - models/schema/ now only contains base.py and auth.py (infrastructure) ## Routes Migration - Moved admin routes from app/api/v1/admin/ to modules: - menu_config.py -> core module - modules.py -> tenancy module - module_config.py -> tenancy module - app/api/v1/admin/ now only aggregates auto-discovered module routes ## Menu System - Implemented module-driven menu system with MenuDiscoveryService - Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT - Added MenuItemDefinition and MenuSectionDefinition dataclasses - Each module now defines its own menu items in definition.py - MenuService integrates with MenuDiscoveryService for template rendering ## Documentation - Updated docs/architecture/models-structure.md - Updated docs/architecture/menu-management.md - Updated architecture validation rules for new exceptions ## Architecture Validation - Updated MOD-019 rule to allow base.py in models/schema/ - Created core module exceptions.py and schemas/ directory - All validation errors resolved (only warnings remain) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,8 @@ Defines the billing module including its features, menu items,
|
||||
route configurations, and scheduled tasks.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition, ScheduledTask
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
from app.modules.base import MenuItemDefinition, MenuSectionDefinition, ModuleDefinition, ScheduledTask
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
@@ -53,6 +53,72 @@ billing_module = ModuleDefinition(
|
||||
"invoices", # Vendor invoice history
|
||||
],
|
||||
},
|
||||
# New module-driven menu definitions
|
||||
menus={
|
||||
FrontendType.ADMIN: [
|
||||
MenuSectionDefinition(
|
||||
id="billing",
|
||||
label_key="billing.menu.billing_subscriptions",
|
||||
icon="credit-card",
|
||||
order=50,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="subscription-tiers",
|
||||
label_key="billing.menu.subscription_tiers",
|
||||
icon="tag",
|
||||
route="/admin/subscription-tiers",
|
||||
order=10,
|
||||
),
|
||||
MenuItemDefinition(
|
||||
id="subscriptions",
|
||||
label_key="billing.menu.vendor_subscriptions",
|
||||
icon="credit-card",
|
||||
route="/admin/subscriptions",
|
||||
order=20,
|
||||
),
|
||||
MenuItemDefinition(
|
||||
id="billing-history",
|
||||
label_key="billing.menu.billing_history",
|
||||
icon="document-text",
|
||||
route="/admin/billing-history",
|
||||
order=30,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
MenuSectionDefinition(
|
||||
id="sales",
|
||||
label_key="billing.menu.sales_orders",
|
||||
icon="currency-euro",
|
||||
order=20,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="invoices",
|
||||
label_key="billing.menu.invoices",
|
||||
icon="currency-euro",
|
||||
route="/vendor/{vendor_code}/invoices",
|
||||
order=30,
|
||||
),
|
||||
],
|
||||
),
|
||||
MenuSectionDefinition(
|
||||
id="account",
|
||||
label_key="billing.menu.account_settings",
|
||||
icon="credit-card",
|
||||
order=900,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="billing",
|
||||
label_key="billing.menu.billing",
|
||||
icon="credit-card",
|
||||
route="/vendor/{vendor_code}/billing",
|
||||
order=30,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
is_core=False, # Billing can be disabled (e.g., internal platforms)
|
||||
# =========================================================================
|
||||
# Self-Contained Module Configuration
|
||||
|
||||
@@ -89,7 +89,17 @@
|
||||
"payment_method_updated": "Zahlungsmethode aktualisiert",
|
||||
"subscription_cancelled": "Abonnement gekündigt",
|
||||
"error_loading": "Fehler beim Laden der Abrechnungsinformationen",
|
||||
"error_updating": "Fehler beim Aktualisieren des Abonnements"
|
||||
"error_updating": "Fehler beim Aktualisieren des Abonnements",
|
||||
"failed_to_load_billing_data": "Failed to load billing data",
|
||||
"failed_to_create_checkout_session": "Failed to create checkout session",
|
||||
"failed_to_open_payment_portal": "Failed to open payment portal",
|
||||
"subscription_cancelled_you_have_access_u": "Subscription cancelled. You have access until the end of your billing period.",
|
||||
"failed_to_cancel_subscription": "Failed to cancel subscription",
|
||||
"subscription_reactivated": "Subscription reactivated!",
|
||||
"failed_to_reactivate_subscription": "Failed to reactivate subscription",
|
||||
"failed_to_purchase_addon": "Failed to purchase add-on",
|
||||
"addon_cancelled_successfully": "Add-on cancelled successfully",
|
||||
"failed_to_cancel_addon": "Failed to cancel add-on"
|
||||
},
|
||||
"limits": {
|
||||
"orders_exceeded": "Monatliches Bestelllimit erreicht. Upgrade für mehr.",
|
||||
|
||||
@@ -89,7 +89,17 @@
|
||||
"payment_method_updated": "Payment method updated",
|
||||
"subscription_cancelled": "Subscription cancelled",
|
||||
"error_loading": "Error loading billing information",
|
||||
"error_updating": "Error updating subscription"
|
||||
"error_updating": "Error updating subscription",
|
||||
"failed_to_load_billing_data": "Failed to load billing data",
|
||||
"failed_to_create_checkout_session": "Failed to create checkout session",
|
||||
"failed_to_open_payment_portal": "Failed to open payment portal",
|
||||
"subscription_cancelled_you_have_access_u": "Subscription cancelled. You have access until the end of your billing period.",
|
||||
"failed_to_cancel_subscription": "Failed to cancel subscription",
|
||||
"subscription_reactivated": "Subscription reactivated!",
|
||||
"failed_to_reactivate_subscription": "Failed to reactivate subscription",
|
||||
"failed_to_purchase_addon": "Failed to purchase add-on",
|
||||
"addon_cancelled_successfully": "Add-on cancelled successfully",
|
||||
"failed_to_cancel_addon": "Failed to cancel add-on"
|
||||
},
|
||||
"limits": {
|
||||
"orders_exceeded": "Monthly order limit reached. Upgrade to continue.",
|
||||
|
||||
@@ -89,7 +89,17 @@
|
||||
"payment_method_updated": "Moyen de paiement mis à jour",
|
||||
"subscription_cancelled": "Abonnement annulé",
|
||||
"error_loading": "Erreur lors du chargement des informations de facturation",
|
||||
"error_updating": "Erreur lors de la mise à jour de l'abonnement"
|
||||
"error_updating": "Erreur lors de la mise à jour de l'abonnement",
|
||||
"failed_to_load_billing_data": "Failed to load billing data",
|
||||
"failed_to_create_checkout_session": "Failed to create checkout session",
|
||||
"failed_to_open_payment_portal": "Failed to open payment portal",
|
||||
"subscription_cancelled_you_have_access_u": "Subscription cancelled. You have access until the end of your billing period.",
|
||||
"failed_to_cancel_subscription": "Failed to cancel subscription",
|
||||
"subscription_reactivated": "Subscription reactivated!",
|
||||
"failed_to_reactivate_subscription": "Failed to reactivate subscription",
|
||||
"failed_to_purchase_addon": "Failed to purchase add-on",
|
||||
"addon_cancelled_successfully": "Add-on cancelled successfully",
|
||||
"failed_to_cancel_addon": "Failed to cancel add-on"
|
||||
},
|
||||
"limits": {
|
||||
"orders_exceeded": "Limite mensuelle de commandes atteinte. Passez à un niveau supérieur.",
|
||||
|
||||
@@ -89,7 +89,17 @@
|
||||
"payment_method_updated": "Zuelungsmethod aktualiséiert",
|
||||
"subscription_cancelled": "Abonnement gekënnegt",
|
||||
"error_loading": "Feeler beim Lueden vun de Rechnungsinformatiounen",
|
||||
"error_updating": "Feeler beim Aktualiséieren vum Abonnement"
|
||||
"error_updating": "Feeler beim Aktualiséieren vum Abonnement",
|
||||
"failed_to_load_billing_data": "Failed to load billing data",
|
||||
"failed_to_create_checkout_session": "Failed to create checkout session",
|
||||
"failed_to_open_payment_portal": "Failed to open payment portal",
|
||||
"subscription_cancelled_you_have_access_u": "Subscription cancelled. You have access until the end of your billing period.",
|
||||
"failed_to_cancel_subscription": "Failed to cancel subscription",
|
||||
"subscription_reactivated": "Subscription reactivated!",
|
||||
"failed_to_reactivate_subscription": "Failed to reactivate subscription",
|
||||
"failed_to_purchase_addon": "Failed to purchase add-on",
|
||||
"addon_cancelled_successfully": "Add-on cancelled successfully",
|
||||
"failed_to_cancel_addon": "Failed to cancel add-on"
|
||||
},
|
||||
"limits": {
|
||||
"orders_exceeded": "Monatlech Bestellungslimit erreecht. Upgrade fir méi.",
|
||||
|
||||
@@ -18,7 +18,7 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_admin_api, require_module_access
|
||||
from app.core.database import get_db
|
||||
from app.modules.billing.services import admin_subscription_service, subscription_service
|
||||
from models.database.user import User
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.billing.schemas import (
|
||||
BillingHistoryListResponse,
|
||||
BillingHistoryWithVendor,
|
||||
|
||||
@@ -20,7 +20,7 @@ from app.api.deps import get_current_vendor_api, require_module_access
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.modules.billing.services import billing_service, subscription_service
|
||||
from models.database.user import User
|
||||
from app.modules.tenancy.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_db, require_menu_access
|
||||
from app.modules.core.utils.page_context import get_admin_context
|
||||
from app.templates_config import templates
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
from models.database.user import User
|
||||
from app.modules.enums import FrontendType
|
||||
from app.modules.tenancy.models import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_vendor_from_cookie_or_header, get_db
|
||||
from app.modules.core.utils.page_context import get_vendor_context
|
||||
from app.templates_config import templates
|
||||
from models.database.user import User
|
||||
from app.modules.tenancy.models import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from app.modules.billing.models import (
|
||||
VendorSubscription,
|
||||
)
|
||||
from app.modules.catalog.models import Product
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
from app.modules.tenancy.models import Vendor, VendorUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from app.modules.billing.models import (
|
||||
VendorAddOn,
|
||||
VendorSubscription,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from app.modules.billing.models import (
|
||||
SubscriptionStatus,
|
||||
VendorSubscription,
|
||||
)
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
from app.modules.tenancy.models import Vendor, VendorUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from app.modules.billing.models import (
|
||||
SubscriptionTier,
|
||||
VendorSubscription,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -308,7 +308,7 @@ class StripeService:
|
||||
customer_id = subscription.stripe_customer_id
|
||||
else:
|
||||
# Get vendor owner email
|
||||
from models.database.vendor import VendorUser
|
||||
from app.modules.tenancy.models import VendorUser
|
||||
|
||||
owner = (
|
||||
db.query(VendorUser)
|
||||
|
||||
@@ -46,7 +46,7 @@ from app.modules.billing.schemas import (
|
||||
UsageSummary,
|
||||
)
|
||||
from app.modules.catalog.models import Product
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
from app.modules.tenancy.models import Vendor, VendorUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
23
app/modules/billing/static/vendor/js/billing.js
vendored
23
app/modules/billing/static/vendor/js/billing.js
vendored
@@ -29,6 +29,9 @@ function vendorBilling() {
|
||||
|
||||
// Initialize
|
||||
async init() {
|
||||
// Load i18n translations
|
||||
await I18n.loadModule('billing');
|
||||
|
||||
// Guard against multiple initialization
|
||||
if (window._vendorBillingInitialized) return;
|
||||
window._vendorBillingInitialized = true;
|
||||
@@ -81,7 +84,7 @@ function vendorBilling() {
|
||||
|
||||
} catch (error) {
|
||||
billingLog.error('Error loading billing data:', error);
|
||||
Utils.showToast('Failed to load billing data', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_load_billing_data'), 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -101,7 +104,7 @@ function vendorBilling() {
|
||||
}
|
||||
} catch (error) {
|
||||
billingLog.error('Error creating checkout:', error);
|
||||
Utils.showToast('Failed to create checkout session', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_create_checkout_session'), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -113,7 +116,7 @@ function vendorBilling() {
|
||||
}
|
||||
} catch (error) {
|
||||
billingLog.error('Error opening portal:', error);
|
||||
Utils.showToast('Failed to open payment portal', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_open_payment_portal'), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -125,24 +128,24 @@ function vendorBilling() {
|
||||
});
|
||||
|
||||
this.showCancelModal = false;
|
||||
Utils.showToast('Subscription cancelled. You have access until the end of your billing period.', 'success');
|
||||
Utils.showToast(I18n.t('billing.messages.subscription_cancelled_you_have_access_u'), 'success');
|
||||
await this.loadData();
|
||||
|
||||
} catch (error) {
|
||||
billingLog.error('Error cancelling subscription:', error);
|
||||
Utils.showToast('Failed to cancel subscription', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_cancel_subscription'), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async reactivate() {
|
||||
try {
|
||||
await apiClient.post('/vendor/billing/reactivate', {});
|
||||
Utils.showToast('Subscription reactivated!', 'success');
|
||||
Utils.showToast(I18n.t('billing.messages.subscription_reactivated'), 'success');
|
||||
await this.loadData();
|
||||
|
||||
} catch (error) {
|
||||
billingLog.error('Error reactivating subscription:', error);
|
||||
Utils.showToast('Failed to reactivate subscription', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_reactivate_subscription'), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -159,7 +162,7 @@ function vendorBilling() {
|
||||
}
|
||||
} catch (error) {
|
||||
billingLog.error('Error purchasing addon:', error);
|
||||
Utils.showToast('Failed to purchase add-on', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_purchase_addon'), 'error');
|
||||
} finally {
|
||||
this.purchasingAddon = null;
|
||||
}
|
||||
@@ -172,11 +175,11 @@ function vendorBilling() {
|
||||
|
||||
try {
|
||||
await apiClient.delete(`/vendor/billing/addons/${addon.id}`);
|
||||
Utils.showToast('Add-on cancelled successfully', 'success');
|
||||
Utils.showToast(I18n.t('billing.messages.addon_cancelled_successfully'), 'success');
|
||||
await this.loadData();
|
||||
} catch (error) {
|
||||
billingLog.error('Error cancelling addon:', error);
|
||||
Utils.showToast('Failed to cancel add-on', 'error');
|
||||
Utils.showToast(I18n.t('billing.messages.failed_to_cancel_addon'), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{# Standalone Pricing Page #}
|
||||
{% extends "public/base.html" %}
|
||||
|
||||
{% block title %}{{ _("platform.pricing.title") }} - Wizamart{% endblock %}
|
||||
{% block title %}{{ _("cms.platform.pricing.title") }} - Wizamart{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="{ annual: false }" class="py-16 lg:py-24">
|
||||
@@ -10,15 +10,15 @@
|
||||
{# Header #}
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
{{ _("platform.pricing.title") }}
|
||||
{{ _("cms.platform.pricing.title") }}
|
||||
</h1>
|
||||
<p class="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
|
||||
{{ _("platform.pricing.trial_note", trial_days=trial_days) }}
|
||||
{{ _("cms.platform.pricing.trial_note", trial_days=trial_days) }}
|
||||
</p>
|
||||
|
||||
{# Billing Toggle #}
|
||||
<div class="flex items-center justify-center mt-8 space-x-4">
|
||||
<span class="text-gray-700 dark:text-gray-300" :class="{ 'font-semibold': !annual }">{{ _("platform.pricing.monthly") }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300" :class="{ 'font-semibold': !annual }">{{ _("cms.platform.pricing.monthly") }}</span>
|
||||
<button @click="annual = !annual"
|
||||
class="relative w-14 h-7 rounded-full transition-colors"
|
||||
:class="annual ? 'bg-indigo-600' : 'bg-gray-300 dark:bg-gray-600'">
|
||||
@@ -26,8 +26,8 @@
|
||||
:class="annual ? 'translate-x-7' : ''"></span>
|
||||
</button>
|
||||
<span class="text-gray-700 dark:text-gray-300" :class="{ 'font-semibold': annual }">
|
||||
{{ _("platform.pricing.annual") }}
|
||||
<span class="text-green-600 text-sm font-medium ml-1">{{ _("platform.pricing.save_months") }}</span>
|
||||
{{ _("cms.platform.pricing.annual") }}
|
||||
<span class="text-green-600 text-sm font-medium ml-1">{{ _("cms.platform.pricing.save_months") }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
{% if tier.is_popular %}
|
||||
<div class="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<span class="bg-indigo-600 text-white text-xs font-bold px-3 py-1 rounded-full">{{ _("platform.pricing.recommended") }}</span>
|
||||
<span class="bg-indigo-600 text-white text-xs font-bold px-3 py-1 rounded-full">{{ _("cms.platform.pricing.recommended") }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
<template x-if="!annual">
|
||||
<div>
|
||||
<span class="text-4xl font-extrabold text-gray-900 dark:text-white">{{ tier.price_monthly }}</span>
|
||||
<span class="text-gray-500">{{ _("platform.pricing.per_month") }}</span>
|
||||
<span class="text-gray-500">{{ _("cms.platform.pricing.per_month") }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="annual">
|
||||
<div>
|
||||
{% if tier.price_annual %}
|
||||
<span class="text-4xl font-extrabold text-gray-900 dark:text-white">{{ (tier.price_annual / 12)|round(0)|int }}</span>
|
||||
<span class="text-gray-500">{{ _("platform.pricing.per_month") }}</span>
|
||||
<div class="text-sm text-gray-500">{{ tier.price_annual }}{{ _("platform.pricing.per_year") }}</div>
|
||||
<span class="text-gray-500">{{ _("cms.platform.pricing.per_month") }}</span>
|
||||
<div class="text-sm text-gray-500">{{ tier.price_annual }}{{ _("cms.platform.pricing.per_year") }}</div>
|
||||
{% else %}
|
||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">{{ _("platform.pricing.custom") }}</span>
|
||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">{{ _("cms.platform.pricing.custom") }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</template>
|
||||
@@ -71,37 +71,37 @@
|
||||
<svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
{% if tier.orders_per_month %}{{ _("platform.pricing.orders_per_month", count=tier.orders_per_month) }}{% else %}{{ _("platform.pricing.unlimited_orders") }}{% endif %}
|
||||
{% if tier.orders_per_month %}{{ _("cms.platform.pricing.orders_per_month", count=tier.orders_per_month) }}{% else %}{{ _("cms.platform.pricing.unlimited_orders") }}{% endif %}
|
||||
</li>
|
||||
<li class="flex items-center text-gray-700 dark:text-gray-300">
|
||||
<svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
{% if tier.products_limit %}{{ _("platform.pricing.products_limit", count=tier.products_limit) }}{% else %}{{ _("platform.pricing.unlimited_products") }}{% endif %}
|
||||
{% if tier.products_limit %}{{ _("cms.platform.pricing.products_limit", count=tier.products_limit) }}{% else %}{{ _("cms.platform.pricing.unlimited_products") }}{% endif %}
|
||||
</li>
|
||||
<li class="flex items-center text-gray-700 dark:text-gray-300">
|
||||
<svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
{% if tier.team_members %}{{ _("platform.pricing.team_members", count=tier.team_members) }}{% else %}{{ _("platform.pricing.unlimited_team") }}{% endif %}
|
||||
{% if tier.team_members %}{{ _("cms.platform.pricing.team_members", count=tier.team_members) }}{% else %}{{ _("cms.platform.pricing.unlimited_team") }}{% endif %}
|
||||
</li>
|
||||
<li class="flex items-center text-gray-700 dark:text-gray-300">
|
||||
<svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
{{ _("platform.pricing.letzshop_sync") }}
|
||||
{{ _("cms.platform.pricing.letzshop_sync") }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if tier.is_enterprise %}
|
||||
<a href="/contact" class="block w-full py-3 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white font-semibold rounded-xl text-center hover:bg-gray-300 transition-colors">
|
||||
{{ _("platform.pricing.contact_sales") }}
|
||||
{{ _("cms.platform.pricing.contact_sales") }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a :href="'/signup?tier={{ tier.code }}&annual=' + annual"
|
||||
class="block w-full py-3 font-semibold rounded-xl text-center transition-colors
|
||||
{% if tier.is_popular %}bg-indigo-600 hover:bg-indigo-700 text-white{% else %}bg-indigo-100 text-indigo-700 hover:bg-indigo-200{% endif %}">
|
||||
{{ _("platform.pricing.start_trial") }}
|
||||
{{ _("cms.platform.pricing.start_trial") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@
|
||||
{# Back to Home #}
|
||||
<div class="text-center mt-12">
|
||||
<a href="/" class="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
← {{ _("platform.pricing.back_home") }}
|
||||
← {{ _("cms.platform.pricing.back_home") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{# Signup Success Page #}
|
||||
{% extends "public/base.html" %}
|
||||
|
||||
{% block title %}{{ _("platform.success.title") }}{% endblock %}
|
||||
{% block title %}{{ _("cms.platform.success.title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="min-h-screen py-16 bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
|
||||
@@ -17,23 +17,23 @@
|
||||
|
||||
{# Welcome Message #}
|
||||
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
{{ _("platform.success.title") }}
|
||||
{{ _("cms.platform.success.title") }}
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-gray-600 dark:text-gray-400 mb-8">
|
||||
{{ _("platform.success.subtitle", trial_days=trial_days) }}
|
||||
{{ _("cms.platform.success.subtitle", trial_days=trial_days) }}
|
||||
</p>
|
||||
|
||||
{# Next Steps #}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 border border-gray-200 dark:border-gray-700 text-left mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _("platform.success.what_next") }}</h2>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _("cms.platform.success.what_next") }}</h2>
|
||||
<ul class="space-y-4">
|
||||
<li class="flex items-start">
|
||||
<div class="w-6 h-6 bg-indigo-100 dark:bg-indigo-900/30 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<span class="text-indigo-600 dark:text-indigo-400 text-sm font-bold">1</span>
|
||||
</div>
|
||||
<span class="ml-3 text-gray-700 dark:text-gray-300">
|
||||
<strong>{{ _("platform.success.step_connect") }}</strong> {{ _("platform.success.step_connect_desc") }}
|
||||
<strong>{{ _("cms.platform.success.step_connect") }}</strong> {{ _("cms.platform.success.step_connect_desc") }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
@@ -41,7 +41,7 @@
|
||||
<span class="text-indigo-600 dark:text-indigo-400 text-sm font-bold">2</span>
|
||||
</div>
|
||||
<span class="ml-3 text-gray-700 dark:text-gray-300">
|
||||
<strong>{{ _("platform.success.step_invoicing") }}</strong> {{ _("platform.success.step_invoicing_desc") }}
|
||||
<strong>{{ _("cms.platform.success.step_invoicing") }}</strong> {{ _("cms.platform.success.step_invoicing_desc") }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
@@ -49,7 +49,7 @@
|
||||
<span class="text-indigo-600 dark:text-indigo-400 text-sm font-bold">3</span>
|
||||
</div>
|
||||
<span class="ml-3 text-gray-700 dark:text-gray-300">
|
||||
<strong>{{ _("platform.success.step_products") }}</strong> {{ _("platform.success.step_products_desc") }}
|
||||
<strong>{{ _("cms.platform.success.step_products") }}</strong> {{ _("cms.platform.success.step_products_desc") }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -59,7 +59,7 @@
|
||||
{% if vendor_code %}
|
||||
<a href="/vendor/{{ vendor_code }}/dashboard"
|
||||
class="inline-flex items-center px-8 py-4 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-xl shadow-lg transition-all hover:scale-105">
|
||||
{{ _("platform.success.go_to_dashboard") }}
|
||||
{{ _("cms.platform.success.go_to_dashboard") }}
|
||||
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/>
|
||||
</svg>
|
||||
@@ -67,14 +67,14 @@
|
||||
{% else %}
|
||||
<a href="/admin/login"
|
||||
class="inline-flex items-center px-8 py-4 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-xl shadow-lg transition-all">
|
||||
{{ _("platform.success.login_dashboard") }}
|
||||
{{ _("cms.platform.success.login_dashboard") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# Support Link #}
|
||||
<p class="mt-8 text-gray-500 dark:text-gray-400">
|
||||
{{ _("platform.success.need_help") }}
|
||||
<a href="/contact" class="text-indigo-600 dark:text-indigo-400 hover:underline">{{ _("platform.success.contact_support") }}</a>
|
||||
{{ _("cms.platform.success.need_help") }}
|
||||
<a href="/contact" class="text-indigo-600 dark:text-indigo-400 hover:underline">{{ _("cms.platform.success.contact_support") }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user