feat: enhance pricing page with feature limits and trial management

- Add comprehensive feature limits enforcement middleware
   - Implement subscription dashboard with usage analytics
   - Create reusable plan card component with feature badges
   - Add trial configuration support with limit overrides
   - Fix payment controller null safety issues
   - Improve pricing page UI with proper feature display
This commit is contained in:
idevakk
2025-11-21 10:55:57 -08:00
parent b497f7796d
commit 72b8109a3a
9 changed files with 1533 additions and 142 deletions

View File

@@ -0,0 +1,230 @@
<div class="max-w-6xl mx-auto p-6">
<!-- Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Subscription Dashboard</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400">Manage your subscription and track usage</p>
</div>
@if($loading)
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
@else
<!-- Subscription Status Card -->
@if($subscription)
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
<div>
<div class="flex items-center gap-3 mb-4">
<h2 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
{{ $plan->name }}
</h2>
<flux:badge variant="solid" color="{{ $this->getSubscriptionStatusColor() }}">
{{ $this->getSubscriptionStatus() }}
</flux:badge>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div>
<p class="text-sm text-gray-600 dark:text-gray-400">Monthly Price</p>
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
${{ number_format($plan->price, 2) }}
</p>
</div>
<div>
<p class="text-sm text-gray-600 dark:text-gray-400">Days Remaining</p>
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
{{ $this->getDaysRemaining() }} days
</p>
</div>
<div>
<p class="text-sm text-gray-600 dark:text-gray-400">Next Billing</p>
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
{{ $this->getNextBillingDate() }}
</p>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-wrap gap-3">
@if($subscription->onTrial())
<flux:button
variant="primary"
wire:click="requestTrialExtension"
wire:loading.attr="disabled"
>
<span wire:loading.remove>Request Trial Extension</span>
<span wire:loading>Processing...</span>
</flux:button>
@endif
@if($subscription->cancelled())
<flux:button
variant="primary"
wire:click="resumeSubscription"
wire:loading.attr="disabled"
>
<span wire:loading.remove>Resume Subscription</span>
<span wire:loading>Processing...</span>
</flux:button>
@else
<flux:button
variant="outline"
wire:click="pauseSubscription"
>
Pause Subscription
</flux:button>
<flux:button
variant="danger"
wire:click="cancelSubscription"
wire:loading.attr="disabled"
>
<span wire:loading.remove>Cancel Subscription</span>
<span wire:loading>Processing...</span>
</flux:button>
@endif
</div>
</div>
</div>
</div>
<!-- Usage Tracking -->
@if($usageData && $usageData->count() > 0)
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">Usage Tracking</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@foreach($usageData as $usage)
@php
$percentage = $this->getUsagePercentage($usage);
$color = $this->getUsageColor($percentage);
@endphp
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div class="flex justify-between items-start mb-2">
<h4 class="font-medium text-gray-900 dark:text-gray-100">
{{ $usage['feature']->display_name }}
</h4>
<flux:badge variant="outline" size="sm" color="{{ $color }}">
{{ $percentage }}%
</flux:badge>
</div>
<div class="mb-2">
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
class="bg-{{ $color }}-500 h-2 rounded-full transition-all duration-300"
style="width: {{ $percentage }}%"
></div>
</div>
</div>
<div class="flex justify-between text-sm text-gray-600 dark:text-gray-400">
<span>Used: {{ $usage['usage'] }}</span>
<span>Limit: {{ $usage['limit']->limit_value ?? 'Unlimited' }}</span>
</div>
@if($usage['remaining'] > 0)
<p class="text-sm text-green-600 dark:text-green-400 mt-1">
{{ $usage['remaining'] }} remaining
</p>
@endif
</div>
@endforeach
</div>
</div>
@endif
<!-- Trial Extensions -->
@if($subscription->onTrial() && $trialExtensions && $trialExtensions->count() > 0)
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">Trial Extensions</h3>
<div class="space-y-3">
@foreach($trialExtensions as $extension)
<div class="flex items-center justify-between p-3 border border-gray-200 dark:border-gray-700 rounded-lg">
<div>
<p class="font-medium text-gray-900 dark:text-gray-100">
{{ $extension->extension_days }} days extension
</p>
<p class="text-sm text-gray-600 dark:text-gray-400">
Requested {{ $extension->created_at->format('M j, Y') }}
</p>
</div>
<flux:badge
variant="{{ $extension->status === 'approved' ? 'solid' : 'outline' }}"
color="{{ $extension->status === 'approved' ? 'green' : ($extension->status === 'rejected' ? 'red' : 'yellow') }}"
>
{{ ucfirst($extension->status) }}
</flux:badge>
</div>
@endforeach
</div>
</div>
@endif
<!-- Upgrade Paths -->
@if($upgradePaths && count($upgradePaths) > 0)
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">Available Upgrades</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach($upgradePaths as $upgradePlan)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:border-blue-500 dark:hover:border-blue-400 transition-colors">
<h4 class="font-semibold text-gray-900 dark:text-gray-100 mb-2">
{{ $upgradePlan['name'] }}
</h4>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-3">
${{ number_format($upgradePlan['price'], 2) }}
<span class="text-sm font-normal text-gray-600 dark:text-gray-400">
/{{ $upgradePlan->getBillingCycleDisplay() }}
</span>
</p>
<flux:button
variant="primary"
class="w-full"
wire:click="choosePlan({{ $upgradePlan->id }})"
>
Upgrade Now
</flux:button>
</div>
@endforeach
</div>
</div>
@endif
@else
<!-- No Subscription State -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-12 text-center">
<flux:icon.inbox class="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
No Active Subscription
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-6">
You don't have an active subscription. Choose a plan to get started!
</p>
<flux:button variant="primary" wire:navigate>
View Plans
</flux:button>
</div>
@endif
<!-- Flash Messages -->
@if(session()->has('success'))
<div class="mt-4 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<p class="text-green-700 dark:text-green-300">{{ session('success') }}</p>
</div>
@endif
@if(session()->has('error'))
<div class="mt-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p class="text-red-700 dark:text-red-300">{{ session('error') }}</p>
</div>
@endif
@if(session()->has('info'))
<div class="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<p class="text-blue-700 dark:text-blue-300">{{ session('info') }}</p>
</div>
@endif
@endif
</div>