{% extends 'base.html.twig' %} {% block title %}Abonnement - Bubul{% endblock %} {% block body %}
{# ================================================================ LAYOUT : sidebar gauche + contenu ================================================================ #}
{# ================================================================ SIDEBAR NAVIGATION ================================================================ #} {% include '_molecules/settings-nav/settings-nav.html.twig' with { user: user, activePage: 'subscription' } only %} {# ================================================================ CONTENU PRINCIPAL ================================================================ #}
{# Titre de page #}
{% include '_molecules/page-header/page-header.html.twig' with { title: 'Abonnement', subtitle: 'Gérez votre plan et votre facturation', icon: 'wallet', variant: 'default', class: 'pt-0' } only %}
{# ================================================================ B2B — accès géré par l'entreprise ================================================================ #} {% if user.isB2B() %} {% set tokenAlloc = user.userToken ? (user.userToken.monthlyAllocation ?? 0) : 0 %} {% set tokenLeft = tokenBalance.remaining %} {% set tokenMax = tokenBalance.max %} {% set tokenPct = tokenBalance.pct %} {# Date de fin : contrat expirant le plus tôt parmi les contrats actifs #} {% set contractEnd = null %} {% for contract in activeContracts %} {% if contractEnd is null or contract.endDate < contractEnd %} {% set contractEnd = contract.endDate %} {% endif %} {% endfor %}
{# Section 1 — Plan actuel #}
{% include '_atoms/dot-texture/dot-texture.html.twig' only %}

Plan actuel

Entreprise

{{ user.company.name }}

{% if contractEnd %}

Fin du contrat

{{ contractEnd|date('d/m/Y') }}

{% endif %}

Votre accès est géré par Bubul. Contactez contact@bubul.app pour toute demande.

{# Bandeau abonnement personnel en transition #} {% if subscription and subscription.plan and not subscription.plan.isFree() %} {% if subscription.isPaused() %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'primary', message: 'Votre abonnement ' ~ subscription.plan.name ~ ' est en pause pendant votre appartenance à ' ~ user.company.name ~ '. Il reprendra automatiquement si vous quittez l\'entreprise.' } only %} {% elseif subscription.status == 'canceled' and subscription.effectivePeriodEnd and subscription.effectivePeriodEnd > date() %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'warning', message: 'Votre abonnement ' ~ subscription.plan.name ~ ' reste actif jusqu\'au ' ~ subscription.effectivePeriodEnd|date('d/m/Y') ~ '. Vous bénéficiez de tous vos avantages jusqu\'à cette date, en plus des tokens de votre entreprise.' } only %} {% endif %} {% endif %}

{# Section 2 — Tokens #}
{% include '_molecules/token-widget/token-widget.html.twig' with { description: 'Les tokens alimentent toutes les fonctionnalités IA de Bubul. Votre entreprise vous alloue ' ~ tokenAlloc|number_format(0, ',', ' ') ~ ' tokens par mois.', balance: tokenLeft, max: tokenMax, pct: tokenPct } only %}
{% else %} {% set tokenUsable = user.userToken ? user.userToken.tokenUsable : 0 %} {% set isActiveStripe = subscription and subscription.isActive() and subscription.stripeSubscriptionId %} {% set isCanceledButActive = subscription and subscription.status == 'canceled' and subscription.plan and not subscription.plan.isFree() and (not subscription.accessUntil or subscription.accessUntil > date()) %} {% set isPastDue = subscription and subscription.status == 'past_due' %} {% set hasStripe = isActiveStripe or isCanceledButActive or isPastDue %} {% set canChangePlan = (isActiveStripe or isCanceledButActive) and not subscription.isInAnnualCommitment() %} {% set canSubscribe = not isActiveStripe and not isCanceledButActive and not isPastDue %}
{# ================================================================ SECTION 1 — PLAN ACTUEL ================================================================ #}
{% if isActiveStripe %}
{% include '_atoms/dot-texture/dot-texture.html.twig' only %}
{# Ligne 1 : nom du plan + billing #}

Plan actuel

{{ subscription.plan.name }}

{{ subscription.isAnnual() ? 'Annuel' : 'Mensuel' }} · {{ subscription.isAnnual() ? subscription.plan.priceAnnual|number_format(2, '.', '') : subscription.plan.priceMonthly|number_format(2, '.', '') }}€/mois

{% if subscription.effectivePeriodEnd %}

Expire le

{{ subscription.effectivePeriodEnd|date('d/m/Y') }}

{% endif %}
{# Pending plan change #} {% if subscription.pendingPlan %}
Changement planifié au prochain renouvellement {{ subscription.pendingPlan.name }}
{% endif %}
{% if subscription.isNearCommitmentEnd() %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'warning', message: 'Votre engagement annuel se termine bientôt. Vous pouvez désormais annuler le renouvellement automatique.' } only %} {% endif %} {# Bouton annuler : - Mensuel : toujours visible - Annuel : uniquement dans les 30 derniers jours de l'engagement #} {% if isActiveStripe and (not subscription.isAnnual() or subscription.isNearCommitmentEnd()) %}
{% endif %} {% elseif isCanceledButActive %}
{% include '_atoms/dot-texture/dot-texture.html.twig' only %}

Plan actuel

{{ subscription.plan.name }}

{{ subscription.isAnnual() ? 'Annuel' : 'Mensuel' }} · {{ subscription.isAnnual() ? subscription.plan.priceAnnual|number_format(2, '.', '') : subscription.plan.priceMonthly|number_format(2, '.', '') }}€/mois

{% if subscription.effectivePeriodEnd %}

Accès jusqu'au

{{ subscription.effectivePeriodEnd|date('d/m/Y') }}

{% endif %}
Renouvellement annulé
{% elseif isPastDue %}
{% include '_atoms/dot-texture/dot-texture.html.twig' with { opacity: 0.12 } only %}

{{ subscription.plan ? subscription.plan.name : 'Abonnement' }}

Paiement en échec

{% else %}
{% include '_atoms/dot-texture/dot-texture.html.twig' with { opacity: 0.12 } only %}

Freemium

Plan gratuit · {{ freemiumPlan ? freemiumPlan.monthlyTokens|number_format(0, ',', ' ') : '0' }} tokens/mois

{% include '_atoms/icon/icon.html.twig' with { name: 'crown', size: 'sm', color: 'white' } only %} Passer en Pro
{% endif %}

{# ================================================================ SECTION 2 — TOKENS ================================================================ #} {% set activePlan = (hasStripe and subscription.plan) ? subscription.plan : freemiumPlan %} {% set monthlyTokens = activePlan ? activePlan.monthlyTokens : 0 %} {% set storageMonths = (activePlan and activePlan.tokenStorageMonths > 0) ? activePlan.tokenStorageMonths : 1 %} {% set maxStorable = tokenMaxStorage %} {% set storePct = tokenBalance.pct %} {% set tokenDesc %}Les tokens alimentent toutes les fonctionnalités IA de Bubul. Votre abonnement {{ (subscription and subscription.plan) ? subscription.plan.name : 'Freemium' }} inclut {{ monthlyTokens|number_format(0, ',', ' ') }} tokens par mois.{% if storageMonths > 1 %} Les tokens non utilisés sont reportés au mois suivant, jusqu'à un maximum de {{ maxStorable|number_format(0, ',', ' ') }} tokens.{% endif %}{% endset %}
{% include '_molecules/token-widget/token-widget.html.twig' with { description: tokenDesc, balance: user.userToken ? user.userToken.tokensRemaining : 0, max: maxStorable, pct: storePct, barVariant: isCanceledButActive ? 'warning' : 'primary' } only %} {# CTA gestion abonnement — vers la liste de tous les plans #} {% if not user.isB2B %}
{% include '_atoms/button/button.html.twig' with { label: 'Modifier mon abonnement', variant: 'primary', size: 'md', href: path('app_upgrade') } only %}
{% endif %} {# Alerte expiration tokens #} {% if isCanceledButActive %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'warning', icon: 'warning', message: 'Ces tokens expireront le ' ~ (subscription.effectivePeriodEnd ? subscription.effectivePeriodEnd|date('d/m/Y') : '—') ~ ', sauf en cas de réactivation.' } only %} {% endif %}
{# ================================================================ SECTION 4 — GÉRER L'ABONNEMENT ================================================================ #}
{# LEFT: description #}

Factures & paiement

Consultez vos factures, mettez à jour votre moyen de paiement et annulez votre abonnement depuis le portail dédié.

  • {% include '_atoms/icon/icon.html.twig' with { name: 'check', size: 'xs', color: 'success' } only %} Télécharger vos factures
  • {% include '_atoms/icon/icon.html.twig' with { name: 'check', size: 'xs', color: 'success' } only %} Mettre à jour votre carte bancaire
{# RIGHT: Stripe card + actions #}
{# Stripe portal #} {% if subscription and subscription.stripeCustomerId %}
{% else %}

Aucune facture pour le moment

{% endif %} {# Mettre à jour le paiement (past_due) #} {% if isPastDue and subscription.stripeCustomerId %}
{% include '_atoms/icon/icon.html.twig' with { name: 'info', size: 'xs', color: 'warning' } only %}

Utilisez le portail Stripe ci-dessus pour mettre à jour votre moyen de paiement.

{% endif %}
{# /col-span-3 #}
{# /grid #}
{# /white card #} {% endif %}
{# /flex-1 contenu principal #}
{# /flex layout #}
{# ================================================================ MODAL : ANNULATION ================================================================ #} {% if subscription and subscription.stripeSubscriptionId and subscription.isActive() %} {% embed '_molecules/modal/modal.html.twig' with { id: 'modal-cancel', title: subscription.isAnnual() ? 'Annuler le renouvellement' : 'Annuler l\'abonnement', subtitle: 'Cette action mettra fin à votre abonnement ' ~ subscription.plan.name ~ '.', icon: 'trash', iconColor: 'error', size: '2xl' } %} {% block body %} {# Avant / Après — même design que les cartes plan #}
{# Maintenant — gradient du plan actuel #}
{% include '_atoms/dot-texture/dot-texture.html.twig' with { size: 20 } only %}

Maintenant

{{ subscription.plan.name }}

{{ (subscription.isAnnual() ? subscription.plan.priceAnnual : subscription.plan.priceMonthly)|number_format(0) }}€ / mois

{% include '_atoms/icon/icon.html.twig' with { name: 'zap', size: 'xs', color: 'white' } only %}

{{ subscription.plan.monthlyTokens|number_format(0, ',', ' ') }} tokens / mois

Accès jusqu'au
{{ subscription.effectivePeriodEnd ? subscription.effectivePeriodEnd|date('d/m/Y') : '—' }}

{# Flèche #}
{% include '_atoms/icon/icon.html.twig' with { name: 'chevron-right', size: 'sm', color: 'gray' } only %}
{# Ensuite — gradient freemium (gris) #}
{% include '_atoms/dot-texture/dot-texture.html.twig' with { size: 20 } only %}

Ensuite

Freemium

Gratuit

{% include '_atoms/icon/icon.html.twig' with { name: 'zap', size: 'xs', color: 'white' } only %}

{{ freemiumPlan ? freemiumPlan.monthlyTokens|number_format(0, ',', ' ') : '0' }} tokens / mois

À partir du {{ subscription.effectivePeriodEnd ? subscription.effectivePeriodEnd|date('d/m/Y') : '—' }}

{# Notice annuel si applicable #} {% if subscription.isAnnual() %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'warning', message: 'Seul le renouvellement automatique est annulé. Votre accès reste actif jusqu\'à la date ci-dessus.' } only %} {% endif %} {% endblock %} {% block footer %} {% include '_atoms/button/button.html.twig' with { label: 'Garder mon abonnement', variant: 'ghost', size: 'lg', attr: { type: 'button', onclick: "closeModal('modal-cancel')" } } only %} {% include '_atoms/button/button.html.twig' with { label: 'Continuer', variant: 'error', size: 'lg', attr: { type: 'button', onclick: "closeModal('modal-cancel'); openModal('modal-cancel-step2')" } } only %} {% endblock %} {% endembed %} {% endif %} {# ================================================================ MODAL : ANNULATION — ÉTAPE 2 (confirmation par frappe) ================================================================ #} {% if subscription and subscription.stripeSubscriptionId and subscription.isActive() %} {% embed '_molecules/modal/modal.html.twig' with { id: 'modal-cancel-step2', title: 'Confirmer l\'annulation', subtitle: 'Vous pourrez réactiver votre abonnement tant que la période payée n\'est pas terminée.', icon: 'trash', iconColor: 'error', size: 'md' } %} {% block body %} {# Aucun encart sur le sort des tokens : l'étape 1 montre déjà le plan actuel, la date de fin d'accès et l'offre Freemium qui prend le relais. Le solde, lui, n'est pas affecté par l'annulation — il n'y a donc rien à annoncer ici. #} {# Input de confirmation #}
{% endblock %} {% block footer %} {% include '_atoms/button/button.html.twig' with { label: 'Retour', variant: 'ghost', size: 'lg', attr: { type: 'button', onclick: "closeModal('modal-cancel-step2'); openModal('modal-cancel')" } } only %} {% endblock %} {% endembed %} {% endif %} {# ================================================================ MODAL : CHANGEMENT DE PLAN (contenu peuplé par JS) ================================================================ #} {% if not user.isB2B() %} {% endif %} {# ================================================================ MODAL : ENGAGEMENT ANNUEL (freemium → checkout annuel) ================================================================ #} {% endblock %}