fix(plans): prevent deletion of plans with active subscriptions

- Fix bulk delete and individual delete actions using before() hook with halt()
  - Add daily/weekly billing cycle options to plan resource and Polar provider
  - Enhance payment confirmation with dynamic polling and loading states
  - Add graceful handling for deleted plans in subscription display
  - Update Polar provider to support dynamic billing cycles
This commit is contained in:
idevakk
2025-12-07 02:23:14 -08:00
parent 1b438cbf89
commit 5fabec1f9d
7 changed files with 225 additions and 93 deletions

View File

@@ -27,8 +27,32 @@
</div>
<p class="text-sm text-gray-500 dark:text-gray-500">
Check {{ $pollCount }} of {{ $maxPolls }} Retrying in 30 seconds...
Check {{ $pollCount }} of {{ $maxPolls }} Checking every 30 seconds...
</p>
@if ($pollCount > 1)
<p class="text-xs text-gray-400 dark:text-gray-600 mt-1">
This is taking longer than expected. Webhook processing may be delayed.
</p>
<div class="mt-4">
<button wire:click="checkSubscriptionStatus"
wire:loading.attr="disabled"
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed">
@if ($isChecking)
<svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
Checking...
@else
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
Check Now
@endif
</button>
</div>
@endif
</div>
@endif
@@ -198,11 +222,11 @@
</div>
@endif
<!-- Polling Indicator - temporarily disabled -->
<!-- Polling Indicator -->
@if ($this->shouldContinuePolling)
<!-- <div wire:poll.30000ms
<div wire:poll.30000ms="checkSubscriptionStatus"
wire:key="subscription-poll-{{ $subscription->id ?? 'none' }}-{{ $pollCount }}">
</div> -->
</div>
@endif
</div>

View File

@@ -52,7 +52,7 @@
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-3">
<flux:heading level="3">{{ $latestActiveSubscription->plan->name }}</flux:heading>
<flux:heading level="3">{{ $latestActiveSubscription->getPlanDisplayName() }}</flux:heading>
<flux:badge variant="success">
{{ ucfirst($latestActiveSubscription->status) }}
</flux:badge>
@@ -62,7 +62,7 @@
</div>
<flux:text class="mt-2 text-gray-600 dark:text-gray-400">
{{ $latestActiveSubscription->plan->description ?? 'Subscription plan' }}
{{ $latestActiveSubscription->plan?->description ?? 'Subscription plan' }}
</flux:text>
@if($latestActiveSubscription->isActive())
@@ -152,7 +152,7 @@
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-3">
<flux:heading level="4">{{ $subscription->plan->name }}</flux:heading>
<flux:heading level="4">{{ $subscription->getPlanDisplayName() }}</flux:heading>
<flux:badge
variant="{{ $subscription->isActive() ? 'success' : ($subscription->isCancelled() ? 'danger' : 'warning') }}"
>
@@ -190,7 +190,7 @@
<div class="text-right">
<flux:text class="text-lg font-semibold">
${{ number_format($subscription->plan->price, 2) }}
${{ number_format($subscription->getPlanPrice(), 2) }}
</flux:text>
@if($isWithin30Minutes && $subscription->id === $latestSubscription->id)
@@ -232,7 +232,7 @@
@if($subscriptionToCancel)
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded-md">
<flux:text class="font-medium">{{ $subscriptionToCancel->plan->name }}</flux:text>
<flux:text class="font-medium">{{ $subscriptionToCancel->getPlanDisplayName() }}</flux:text>
<flux:text class="text-sm text-gray-600 dark:text-gray-400">
{{ $subscriptionToCancel->getProviderDisplayName() }}
</flux:text>