Add subcription feature 9
This commit is contained in:
@@ -64,6 +64,7 @@ interface CommunicationPreferencesProps {
|
||||
onSave: (preferences: NotificationPreferences) => Promise<void>;
|
||||
onReset: () => void;
|
||||
hasChanges: boolean;
|
||||
onPreferencesChange?: (preferences: NotificationPreferences) => void;
|
||||
}
|
||||
|
||||
const CommunicationPreferences: React.FC<CommunicationPreferencesProps> = ({
|
||||
@@ -144,10 +145,16 @@ const CommunicationPreferences: React.FC<CommunicationPreferencesProps> = ({
|
||||
];
|
||||
|
||||
const handlePreferenceChange = (key: keyof NotificationPreferences, value: any) => {
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
const newPreferences = {
|
||||
...preferences,
|
||||
[key]: value
|
||||
}));
|
||||
};
|
||||
setPreferences(newPreferences);
|
||||
|
||||
// Notify parent component about changes
|
||||
if (onPreferencesChange) {
|
||||
onPreferencesChange(newPreferences);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContactChange = (field: 'email' | 'phone', value: string) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -335,6 +335,9 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
exp_year?: number;
|
||||
} | null>(null);
|
||||
|
||||
// Billing cycle state (monthly/yearly)
|
||||
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>('monthly');
|
||||
|
||||
// Section visibility states
|
||||
const [showUsage, setShowUsage] = useState(false);
|
||||
const [showBilling, setShowBilling] = useState(false);
|
||||
@@ -439,6 +442,10 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
|
||||
setUsageSummary(usage);
|
||||
setAvailablePlans(plans);
|
||||
// Initialize billing cycle from current subscription
|
||||
if (usage.billing_cycle) {
|
||||
setBillingCycle(usage.billing_cycle);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading subscription data:', error);
|
||||
showToast.error(
|
||||
@@ -470,6 +477,7 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
try {
|
||||
setUpgrading(true);
|
||||
|
||||
// Step 1: Validate the upgrade
|
||||
const validation = await subscriptionService.validatePlanUpgrade(
|
||||
tenantId,
|
||||
selectedPlan
|
||||
@@ -480,21 +488,46 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await subscriptionService.upgradePlan(tenantId, selectedPlan);
|
||||
// Step 2: Execute the upgrade (uses current billing cycle)
|
||||
const result = await subscriptionService.upgradePlan(
|
||||
tenantId,
|
||||
selectedPlan,
|
||||
billingCycle // Pass the currently selected billing cycle
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
showToast.success(result.message);
|
||||
// Show appropriate success message based on trial status
|
||||
if (result.is_trialing && result.trial_preserved) {
|
||||
showToast.success(
|
||||
`¡Plan actualizado a ${result.new_plan}! Tu período de prueba continúa hasta ${
|
||||
result.trial_ends_at ? new Date(result.trial_ends_at).toLocaleDateString('es-ES') : 'su fecha original'
|
||||
}. Al finalizar, se aplicará el precio del nuevo plan.`
|
||||
);
|
||||
} else {
|
||||
showToast.success(result.message || `¡Plan actualizado exitosamente a ${result.new_plan}!`);
|
||||
}
|
||||
|
||||
// Step 3: Invalidate caches to refresh UI
|
||||
subscriptionService.invalidateCache();
|
||||
|
||||
// Step 4: Refresh subscription data from server
|
||||
if (result.requires_token_refresh) {
|
||||
// Force a fresh fetch of subscription data
|
||||
await subscriptionService.getUsageSummary(tenantId).catch(() => {});
|
||||
}
|
||||
|
||||
// Step 5: Invalidate React Query caches
|
||||
await queryClient.invalidateQueries({ queryKey: ['subscription-usage'] });
|
||||
await queryClient.invalidateQueries({ queryKey: ['tenant'] });
|
||||
await queryClient.invalidateQueries({ queryKey: ['subscription'] });
|
||||
|
||||
// Step 6: Notify other components about the change
|
||||
notifySubscriptionChanged();
|
||||
|
||||
// Step 7: Reload subscription data for this page
|
||||
await loadSubscriptionData();
|
||||
|
||||
// Step 8: Close dialog and reset state
|
||||
setUpgradeDialogOpen(false);
|
||||
setSelectedPlan('');
|
||||
} else {
|
||||
@@ -502,7 +535,8 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error upgrading plan:', error);
|
||||
showToast.error('Error al procesar el cambio de plan');
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error al procesar el cambio de plan';
|
||||
showToast.error(errorMessage);
|
||||
} finally {
|
||||
setUpgrading(false);
|
||||
}
|
||||
@@ -647,9 +681,9 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
// Determine if this is a pilot subscription based on characteristics
|
||||
// Pilot subscriptions have extended trial periods (typically 90 days from PILOT2025 coupon)
|
||||
// compared to regular trials (typically 14 days), so we check for longer trial periods
|
||||
const isPilotSubscription = usageSummary.status === 'trialing' &&
|
||||
const isPilotSubscription = (usageSummary.status === 'trialing' ||
|
||||
(usageSummary.trial_ends_at && new Date(usageSummary.trial_ends_at) > new Date())) &&
|
||||
usageSummary.trial_ends_at &&
|
||||
new Date(usageSummary.trial_ends_at) > new Date() &&
|
||||
// Check if trial period is longer than typical trial (e.g., > 60 days indicates pilot)
|
||||
(new Date(usageSummary.trial_ends_at).getTime() - new Date().getTime()) > (60 * 24 * 60 * 60 * 1000); // 60+ days
|
||||
|
||||
@@ -726,6 +760,10 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
onToggle={() => setShowUsage(!showUsage)}
|
||||
showAlert={hasHighUsageMetrics}
|
||||
>
|
||||
|
||||
{/* Spacing between content blocks */}
|
||||
<div className="h-6"></div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Team & Organization */}
|
||||
<div>
|
||||
@@ -886,6 +924,10 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
isOpen={showBilling}
|
||||
onToggle={() => setShowBilling(!showBilling)}
|
||||
>
|
||||
|
||||
{/* Spacing between content blocks */}
|
||||
<div className="h-6"></div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Payment Method */}
|
||||
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)]">
|
||||
@@ -931,8 +973,21 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
<div className="p-4 bg-[var(--bg-secondary)] rounded-full">
|
||||
<Download className="w-8 h-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<p className="text-[var(--text-secondary)]">No hay facturas disponibles</p>
|
||||
<p className="text-sm text-[var(--text-tertiary)]">Las facturas aparecerán aquí una vez realizados los pagos</p>
|
||||
{(usageSummary?.status === 'trialing' ||
|
||||
(usageSummary?.trial_ends_at && new Date(usageSummary.trial_ends_at) > new Date())) ? (
|
||||
<>
|
||||
<p className="text-[var(--text-secondary)]">Periodo de prueba activo</p>
|
||||
<p className="text-sm text-[var(--text-tertiary)]">
|
||||
No hay facturas durante el periodo de prueba. Las facturas aparecerán aquí
|
||||
una vez finalice el periodo de prueba el {usageSummary?.trial_ends_at ? new Date(usageSummary.trial_ends_at).toLocaleDateString('es-ES') : 'próximamente'}.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-[var(--text-secondary)]">No hay facturas disponibles</p>
|
||||
<p className="text-sm text-[var(--text-tertiary)]">Las facturas aparecerán aquí una vez realizados los pagos</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -951,21 +1006,32 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
{Array.isArray(invoices) && invoices.slice(0, 5).map((invoice) => (
|
||||
<tr key={invoice.id} className="border-b border-[var(--border-color)] hover:bg-[var(--bg-secondary)] transition-colors">
|
||||
<td className="py-3 px-4 text-[var(--text-primary)] font-medium">
|
||||
{new Date(invoice.date).toLocaleDateString('es-ES', {
|
||||
{invoice.created ? new Date(invoice.created * 1000).toLocaleDateString('es-ES', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
})}
|
||||
}) : 'N/A'}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-[var(--text-primary)]">
|
||||
{invoice.description || 'Suscripción'}
|
||||
{invoice.trial ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">●</span>
|
||||
Periodo de prueba
|
||||
</span>
|
||||
) : (invoice.description || 'Suscripción')}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-[var(--text-primary)] font-semibold text-right">
|
||||
{subscriptionService.formatPrice(invoice.amount)}
|
||||
{invoice.amount_due !== undefined ? (
|
||||
invoice.trial ? (
|
||||
<span className="text-green-500">0,00 €</span>
|
||||
) : (
|
||||
subscriptionService.formatPrice(invoice.amount_due)
|
||||
)
|
||||
) : 'N/A'}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-center">
|
||||
<Badge variant={invoice.status === 'paid' ? 'success' : invoice.status === 'open' ? 'warning' : 'default'}>
|
||||
{invoice.status === 'paid' ? 'Pagada' : invoice.status === 'open' ? 'Pendiente' : invoice.status}
|
||||
<Badge variant={invoice.trial ? 'success' : invoice.status === 'paid' ? 'success' : invoice.status === 'open' ? 'warning' : 'default'}>
|
||||
{invoice.trial ? 'Prueba' : invoice.status === 'paid' ? 'Pagada' : invoice.status === 'open' ? 'Pendiente' : invoice.status}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-center">
|
||||
@@ -977,7 +1043,7 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
className="flex items-center gap-2 mx-auto"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
{invoice.invoice_pdf ? 'PDF' : 'Ver'}
|
||||
{invoice.invoice_pdf ? 'PDF' : invoice.hosted_invoice_url ? 'Ver' : 'Descargar'}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -995,23 +1061,7 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Support Contact */}
|
||||
<div className="p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<Activity className="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm text-[var(--text-primary)] font-medium mb-1">
|
||||
¿Preguntas sobre tu factura?
|
||||
</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Contacta a nuestro equipo de soporte en{' '}
|
||||
<a href="mailto:support@bakery-ia.com" className="text-blue-500 hover:text-white hover:bg-blue-500 px-2 py-0.5 rounded transition-all duration-200 no-underline">
|
||||
support@bakery-ia.com
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
@@ -1022,6 +1072,10 @@ const SubscriptionPageRedesign: React.FC = () => {
|
||||
isOpen={showPlans}
|
||||
onToggle={() => setShowPlans(!showPlans)}
|
||||
>
|
||||
|
||||
{/* Spacing between content blocks */}
|
||||
<div className="h-6"></div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Available Plans */}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user