feat(billing): implement US-3.7 billing and subscription UX with detailed dashboard, real-time invoice history, inline paywall and upgrade confirmation
This commit is contained in:
111
memento-note/components/settings/inline-paywall.tsx
Normal file
111
memento-note/components/settings/inline-paywall.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { AlertTriangle, Sparkles, Key, X, ArrowRight } from 'lucide-react';
|
||||
import { useLanguage } from '@/lib/i18n';
|
||||
|
||||
interface InlinePaywallProps {
|
||||
feature: string;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export function InlinePaywall({ feature, onDismiss }: InlinePaywallProps) {
|
||||
const { t } = useLanguage();
|
||||
const router = useRouter();
|
||||
const [timeLeft, setTimeLeft] = useState(10);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeLeft <= 0) {
|
||||
onDismiss();
|
||||
return;
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
setTimeLeft((prev) => prev - 1);
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [timeLeft, onDismiss]);
|
||||
|
||||
const handleUpgrade = () => {
|
||||
router.push('/settings/billing');
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
const handleByok = () => {
|
||||
router.push('/settings/ai#byok');
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
const featureLabels: Record<string, string> = {
|
||||
semantic_search: t('usageMeter.featureSearch') || 'Recherche Sémantique',
|
||||
auto_tag: t('usageMeter.featureTags') || 'Tags Automatiques',
|
||||
auto_title: t('usageMeter.featureTitles') || 'Titres Automatiques',
|
||||
reformulate: t('usageMeter.featureReformulate') || 'Reformulation',
|
||||
chat: t('usageMeter.featureChat') || 'Chat IA',
|
||||
brainstorm_create: t('usageMeter.featureBrainstormCreate') || 'Création de Brainstorm',
|
||||
brainstorm_expand: t('usageMeter.featureBrainstormExpand') || 'Expansion de Brainstorm',
|
||||
brainstorm_enrich: t('usageMeter.featureBrainstormEnrich') || 'Enrichissement de Brainstorm',
|
||||
};
|
||||
|
||||
const currentFeatureLabel = featureLabels[feature] || feature;
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 10 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: -10 }}
|
||||
transition={{ duration: 0.3, ease: 'easeOut' }}
|
||||
className="rounded-2xl border border-rose-200 dark:border-rose-900/40 bg-rose-50/60 dark:bg-rose-950/15 backdrop-blur-md p-6 space-y-4 shadow-lg"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-2.5 bg-rose-500/10 text-rose-500 rounded-xl shrink-0">
|
||||
<AlertTriangle size={20} />
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<h4 className="text-sm font-bold text-rose-800 dark:text-rose-400">
|
||||
{t('quotaPaywall.title') || 'Limite mensuelle atteinte'}
|
||||
</h4>
|
||||
<p className="text-xs text-rose-700/80 dark:text-rose-400/70 font-light leading-relaxed">
|
||||
{(t('quotaPaywall.description') || "Vous avez épuisé vos crédits pour la fonctionnalité {feature} ce mois-ci.").replace('{feature}', currentFeatureLabel)}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDismiss}
|
||||
className="text-rose-500/60 hover:text-rose-500 dark:text-rose-400/50 dark:hover:text-rose-400 p-1 hover:bg-rose-500/5 dark:hover:bg-rose-400/5 rounded-lg transition-all"
|
||||
title={t('quotaPaywall.later') || 'Peut-être plus tard'}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-3 pt-2 sm:pt-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleUpgrade}
|
||||
className="inline-flex items-center justify-center gap-2 px-4 py-2.5 bg-[#D4A373] text-white hover:bg-[#C49363] text-xs font-semibold rounded-xl transition-all shadow-sm shadow-[#D4A373]/15"
|
||||
>
|
||||
<Sparkles size={14} />
|
||||
{t('quotaPaywall.upgrade') || 'Passer au plan Pro'}
|
||||
<ArrowRight size={12} className="ml-1" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleByok}
|
||||
className="inline-flex items-center justify-center gap-2 px-4 py-2.5 border border-rose-300 dark:border-rose-800/40 text-rose-800 dark:text-rose-400 hover:bg-rose-100/30 dark:hover:bg-rose-400/5 text-xs font-semibold rounded-xl transition-all"
|
||||
>
|
||||
<Key size={14} />
|
||||
{t('quotaPaywall.useOwnKey') || 'Utiliser ma propre clé'}
|
||||
</button>
|
||||
|
||||
<div className="sm:ml-auto flex items-center justify-center gap-1.5 text-[10px] text-rose-500/60 dark:text-rose-400/40 font-mono">
|
||||
<span>Fermeture dans {timeLeft}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user