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,147 @@
<div class="rounded-2xl border dark:border-white/[0.1] border-black/[0.3]p-6 shadow-xs ring-1 ring-white/[0.5] sm:px-8 lg:p-12 relative @if($plan->planTier && $plan->planTier->sort_order > 1) dark:ring-blue-400 @endif">
@if($plan->planTier && $plan->planTier->sort_order > 1)
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
<flux:badge variant="solid" size="sm" color="blue">Most Popular</flux:badge>
</div>
@endif
<div class="text-center">
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">{{ $plan->name }}</h2>
@if($plan->planTier)
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">{{ $plan->planTier->name }} Tier</p>
@endif
<p class="mt-4 sm:mt-6">
<strong class="text-4xl font-bold text-gray-900 dark:text-gray-100">${{ number_format($plan->price, 2) }}</strong>
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">/{{ $billingCycle }}</span>
</p>
@if($plan->description)
<p class="mt-3 text-sm text-gray-600 dark:text-gray-400">{{ $plan->description }}</p>
@endif
</div>
<!-- Features List -->
@if($features)
<ul class="mt-6 space-y-3">
@foreach($features as $featureData)
<li class="flex items-start gap-2">
<flux:icon.check-circle class="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" />
<div>
<span class="text-gray-700 dark:text-gray-300 font-medium">
{{ $featureData['feature']['display_name'] ?? 'Unknown Feature' }}
</span>
{{-- Simple badge display --}}
@if(isset($featureData['limit']['limit_value']))
@if($featureData['limit']['limit_value'] === null)
<flux:badge variant="outline" size="sm" color="purple" class="ml-2">Unlimited</flux:badge>
@else
@php
$limitValue = (int) $featureData['limit']['limit_value'];
$limitType = $featureData['limit']['limit_type'] ?? '';
$suffix = '';
if ($limitType === 'monthly') {
$suffix = '/month';
} elseif ($limitType === 'daily') {
$suffix = '/day';
} elseif ($limitType === 'weekly') {
$suffix = '/week';
} elseif ($limitType === 'yearly') {
$suffix = '/year';
}
// Don't show suffix for 'total' or empty
@endphp
<flux:badge variant="outline" size="sm" color="blue" class="ml-2">
{{ $limitValue }}{{ $suffix }}
</flux:badge>
@endif
@endif
{{-- Trial limit badge --}}
@if($hasTrial && isset($featureData['limit']['trial_limit_value']) && $featureData['limit']['trial_limit_value'] > 0 && isset($featureData['limit']['applies_during_trial']) && $featureData['limit']['applies_during_trial'])
@php
$trialValue = (int) $featureData['limit']['trial_limit_value'];
$trialSuffix = '';
if (isset($featureData['limit']['limit_type'])) {
$trialType = $featureData['limit']['limit_type'];
if ($trialType === 'monthly') {
$trialSuffix = '/month';
} elseif ($trialType === 'daily') {
$trialSuffix = '/day';
} elseif ($trialType === 'weekly') {
$trialSuffix = '/week';
} elseif ($trialType === 'yearly') {
$trialSuffix = '/year';
}
// Don't show suffix for 'total' or empty
}
@endphp
<flux:badge variant="outline" size="sm" color="yellow" class="mt-1">
Trial: {{ $trialValue }}{{ $trialSuffix }}
</flux:badge>
@endif
</div>
</li>
@endforeach
</ul>
@else
<div class="mt-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
<p class="text-sm text-gray-600 dark:text-gray-400 text-center">
No features configured for this plan.
</p>
</div>
@endif
<!-- Trial Information -->
@if($hasTrial && $trialConfig)
<div class="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div class="flex items-center gap-2 text-blue-700 dark:text-blue-300">
<flux:icon.clock class="w-4 h-4" />
<span class="text-sm font-medium">{{ $trialConfig['duration_days'] }}-day free trial</span>
</div>
@if($trialConfig['requires_payment_method'])
<p class="text-xs text-blue-600 dark:text-blue-400 mt-1">Payment method required</p>
@endif
</div>
@endif
<!-- Payment Provider Buttons -->
<div class="mt-6 space-y-2">
@foreach($providers as $provider)
@if($provider === 'stripe')
@if($hasTrial && $trialConfig)
<flux:button variant="primary" class="w-full" wire:click="startTrial({{ $plan->id }}, '{{ $provider }}')">
Start Free Trial
</flux:button>
@endif
<flux:button variant="{{ $hasTrial && $trialConfig ? 'outline' : 'primary' }}" class="w-full" wire:click="choosePlan({{ $plan->id }}, '{{ $provider }}')">
Pay with Card
</flux:button>
@elseif($provider === 'lemon_squeezy')
<flux:button variant="filled" class="w-full" wire:click="choosePlan({{ $plan->id }}, '{{ $provider }}')">
Pay with Lemon Squeezy
</flux:button>
@elseif($provider === 'polar')
<flux:button variant="filled" class="w-full" wire:click="choosePlan({{ $plan->id }}, '{{ $provider }}')">
Pay with Polar.sh
</flux:button>
@elseif($provider === 'oxapay')
<flux:button variant="filled" class="w-full" wire:click="choosePlan({{ $plan->id }}, '{{ $provider }}')">
Pay with OxaPay
</flux:button>
@elseif($provider === 'crypto')
<flux:button variant="filled" class="w-full" wire:click="choosePlan({{ $plan->id }}, '{{ $provider }}')">
Pay with Crypto
</flux:button>
@elseif($provider === 'activation_key')
<flux:button variant="outline" class="w-full" disabled>
Activate via Activation Key
</flux:button>
@endif
@endforeach
</div>
</div>