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:
@@ -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>
|
||||
Reference in New Issue
Block a user