feat: dashboard Second Brain, essai 7 jours et vérification e-mail
Rendre le dashboard actionnable (inbox, peek, carte mentale), aligner la facturation sur l’essai 7 jours, et bloquer le login e-mail tant que l’adresse n’est pas confirmée. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { toast } from 'sonner';
|
||||
import { format } from 'date-fns';
|
||||
import { motion } from 'motion/react';
|
||||
import { BillingHistory } from './billing-history';
|
||||
import { SUBSCRIPTION_TRIAL_DAYS } from '@/lib/billing/trial-constants';
|
||||
|
||||
type Tier = 'PRO' | 'BUSINESS';
|
||||
type Interval = 'month' | 'year';
|
||||
@@ -22,6 +23,9 @@ interface BillingStatus {
|
||||
currentPeriodEnd: string | null;
|
||||
cancelAtPeriodEnd: boolean;
|
||||
hasStripeSubscription: boolean;
|
||||
trialEndsAt?: string | null;
|
||||
trialEligible?: boolean;
|
||||
trialDays?: number;
|
||||
billingEnabled?: boolean;
|
||||
prices?: {
|
||||
PRO: {
|
||||
@@ -250,6 +254,12 @@ export function BillingPlans() {
|
||||
|
||||
const effectiveTier = status?.effectiveTier ?? 'BASIC';
|
||||
const isPaid = effectiveTier !== 'BASIC';
|
||||
const isTrialing = (status?.status ?? '').toUpperCase() === 'TRIALING';
|
||||
const trialEligible = !!status?.trialEligible;
|
||||
const trialDays = status?.trialDays ?? SUBSCRIPTION_TRIAL_DAYS;
|
||||
|
||||
const trialCta = (fallback: string) =>
|
||||
trialEligible ? t('billing.startTrialCta', { days: trialDays }) : fallback;
|
||||
|
||||
const plans = [
|
||||
{
|
||||
@@ -280,6 +290,7 @@ export function BillingPlans() {
|
||||
period: interval === 'month' ? t('billing.perMonth') : t('billing.perYear'),
|
||||
description: t('billing.proDescription') || 'Pour les consultants et créateurs exigeants.',
|
||||
features: [
|
||||
...(trialEligible ? [t('billing.trialFeature', { days: trialDays })] : []),
|
||||
t('billing.proFeature1'),
|
||||
t('billing.proFeature2'),
|
||||
t('billing.proFeature3'),
|
||||
@@ -289,7 +300,7 @@ export function BillingPlans() {
|
||||
],
|
||||
current: effectiveTier === 'PRO',
|
||||
popular: true,
|
||||
buttonText: effectiveTier === 'PRO' ? (t('billing.currentPlan') || 'Plan Actuel') : (t('billing.proCta') || 'Passer au Plan Pro'),
|
||||
buttonText: effectiveTier === 'PRO' ? (t('billing.currentPlan') || 'Plan Actuel') : trialCta(t('billing.proCta') || 'Passer au Plan Pro'),
|
||||
buttonClass: effectiveTier === 'PRO'
|
||||
? 'bg-paper text-concrete cursor-default'
|
||||
: 'bg-brand-accent text-white shadow-xl shadow-brand-accent/20 hover:scale-[1.02] active:scale-95',
|
||||
@@ -302,6 +313,7 @@ export function BillingPlans() {
|
||||
(interval === 'month' ? (t('billing.businessPrice') || '29,90€') : (t('billing.businessAnnualPrice') || '299€')),
|
||||
period: interval === 'month' ? t('billing.perMonth') : t('billing.perYear'),
|
||||
features: [
|
||||
...(trialEligible ? [t('billing.trialFeature', { days: trialDays })] : []),
|
||||
t('billing.businessFeature1'),
|
||||
t('billing.businessFeature2'),
|
||||
t('billing.businessFeature3'),
|
||||
@@ -310,7 +322,7 @@ export function BillingPlans() {
|
||||
t('billing.businessFeature6'),
|
||||
],
|
||||
current: effectiveTier === 'BUSINESS',
|
||||
buttonText: effectiveTier === 'BUSINESS' ? (t('billing.currentPlan') || 'Plan Actuel') : (t('billing.businessCta') || 'Choisir Plan Business'),
|
||||
buttonText: effectiveTier === 'BUSINESS' ? (t('billing.currentPlan') || 'Plan Actuel') : trialCta(t('billing.businessCta') || 'Choisir Plan Business'),
|
||||
buttonClass: effectiveTier === 'BUSINESS'
|
||||
? 'bg-paper text-concrete cursor-default'
|
||||
: 'bg-ink text-white shadow-xl shadow-ink/20 hover:scale-[1.02] active:scale-95',
|
||||
@@ -415,9 +427,11 @@ export function BillingPlans() {
|
||||
<div className="ml-auto">
|
||||
<span className={cn(
|
||||
'px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest',
|
||||
status?.status === 'active' || status?.status === 'ACTIVE'
|
||||
? 'bg-primary/10 text-primary/80 dark:text-primary border border-primary/20'
|
||||
: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20'
|
||||
isTrialing
|
||||
? 'bg-sky-500/10 text-sky-700 dark:text-sky-300 border border-sky-500/20'
|
||||
: status?.status === 'active' || status?.status === 'ACTIVE'
|
||||
? 'bg-primary/10 text-primary/80 dark:text-primary border border-primary/20'
|
||||
: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20'
|
||||
)}>
|
||||
{status?.status ? t(`billing.${status.status.toLowerCase()}`) || status.status : t('billing.active')}
|
||||
</span>
|
||||
@@ -425,6 +439,12 @@ export function BillingPlans() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isTrialing && status?.trialEndsAt && (
|
||||
<p className="text-xs text-sky-700 dark:text-sky-300 bg-sky-500/10 border border-sky-500/20 rounded-xl px-3 py-2">
|
||||
{t('billing.trialEndsOn', { date: formatDate(status.trialEndsAt) })}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isPaid && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-4 border-t border-border/40">
|
||||
<div className="space-y-1">
|
||||
@@ -439,10 +459,18 @@ export function BillingPlans() {
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-concrete uppercase tracking-wider">
|
||||
{status?.cancelAtPeriodEnd ? t('billing.expiresOn') : t('billing.nextBillingDate')}
|
||||
{isTrialing
|
||||
? t('billing.trialEndsLabel')
|
||||
: status?.cancelAtPeriodEnd
|
||||
? t('billing.expiresOn')
|
||||
: t('billing.nextBillingDate')}
|
||||
</span>
|
||||
<p className="text-xs font-semibold text-ink">
|
||||
{status?.currentPeriodEnd ? formatDate(status.currentPeriodEnd) : '—'}
|
||||
{isTrialing && status?.trialEndsAt
|
||||
? formatDate(status.trialEndsAt)
|
||||
: status?.currentPeriodEnd
|
||||
? formatDate(status.currentPeriodEnd)
|
||||
: '—'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -619,6 +647,7 @@ export function BillingPlans() {
|
||||
brainstorm_expand: t('usageMeter.featureBrainstormExpand'),
|
||||
brainstorm_enrich: t('usageMeter.featureBrainstormEnrich'),
|
||||
suggest_charts: t('usageMeter.featureCharts'),
|
||||
interactive_demo: t('usageMeter.featureInteractiveDemo'),
|
||||
publish_enhance: t('usageMeter.featurePublishEnhance'),
|
||||
ai_flashcard: t('usageMeter.featureFlashcards'),
|
||||
voice_transcribe: t('usageMeter.featureVoice'),
|
||||
|
||||
Reference in New Issue
Block a user