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:
@@ -1,116 +1,106 @@
|
||||
<div class="mx-auto max-w-3xl px-4 py-8 sm:px-6 sm:py-12 lg:px-8 ">
|
||||
{{-- <script src="https://shoppy.gg/api/embed.js"></script>--}}
|
||||
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 sm:py-12 lg:px-8">
|
||||
<div class="w-full mb-8 items-center flex justify-center">
|
||||
<h1 class="text-center text-3xl text-gray-900 dark:text-gray-200">Purchase Subscription</h1>
|
||||
<h1 class="text-center text-3xl text-gray-900 dark:text-gray-200">Choose Your Plan</h1>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:items-center md:gap-8">
|
||||
|
||||
<!-- Plan Tiers Navigation -->
|
||||
@if($planTiers && $planTiers->count() > 1)
|
||||
<div class="flex justify-center mb-8">
|
||||
<div class="inline-flex rounded-lg border border-gray-200 dark:border-gray-700 p-1">
|
||||
<button
|
||||
wire:click="filterByTier(null)"
|
||||
class="px-4 py-2 text-sm font-medium rounded-md {{ $selectedTier === null ? 'bg-blue-600 text-white' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-900 dark:hover:bg-gray-900' }} cursor-pointer">
|
||||
All Plans
|
||||
</button>
|
||||
@foreach($planTiers as $tier)
|
||||
<button
|
||||
wire:click="filterByTier({{ $tier->id }})"
|
||||
class="px-4 py-2 text-sm font-medium rounded-md {{ $selectedTier === $tier->id ? 'bg-blue-600 text-white' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-900 dark:hover:bg-gray-900' }} cursor-pointer">
|
||||
{{ $tier->name }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Plans Grid -->
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-3">
|
||||
@if(isset($plans))
|
||||
@foreach($plans as $plan)
|
||||
<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">
|
||||
<div class="text-center">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-400">{{ $plan['name'] }} @if(!$plan['monthly_billing'])
|
||||
<flux:badge variant="solid" size="sm" color="emerald">2 Months Free</flux:badge>
|
||||
@endif</h2>
|
||||
@php
|
||||
$providers = $this->getPlanProviders($plan->id);
|
||||
$features = $this->getPlanFeatures($plan->id);
|
||||
$hasTrial = $this->planHasTrial($plan->id);
|
||||
$trialConfig = $this->planHasTrial($plan->id) ? $this->getTrialConfig($plan->id) : null;
|
||||
$billingCycle = $this->getBillingCycleDisplay($plan);
|
||||
$isPopularTier = $plan->planTier && $plan->planTier->sort_order > 1;
|
||||
@endphp
|
||||
|
||||
<p class="mt-2 sm:mt-4">
|
||||
<strong class="text-3xl font-bold text-gray-900 dark:text-gray-200 sm:text-4xl">${{ $plan['price'] }}</strong>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">/{{ $plan['monthly_billing'] ? 'month' : 'year' }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="mt-6 space-y-2">
|
||||
|
||||
@if($plan['details'])
|
||||
@forelse ($plan['details'] as $key => $value)
|
||||
@if ($value)
|
||||
<li class="flex items-center gap-1">
|
||||
@if($value == "true")<flux:icon.check-circle />
|
||||
@else <flux:icon.circle-x />
|
||||
@endif
|
||||
<span class="text-gray-700 dark:text-gray-400 "> {{ $key }} </span>
|
||||
</li>
|
||||
@endif
|
||||
@empty
|
||||
@endforelse
|
||||
@endif
|
||||
</ul>
|
||||
|
||||
@if($plan['accept_stripe'] && $plan['pricing_id'] !== null)
|
||||
<flux:button variant="primary" class="w-full mt-6 cursor-pointer" wire:click="choosePlan('{{ $plan['pricing_id'] }}')">
|
||||
Pay with card
|
||||
</flux:button>
|
||||
@endif
|
||||
@if($plan['accept_shoppy'] && $plan['shoppy_product_id'] !== null)
|
||||
<flux:button variant="filled" class="w-full mt-2 cursor-pointer" data-shoppy-product="{{ $plan['shoppy_product_id'] }}">
|
||||
Pay with crypto
|
||||
</flux:button>
|
||||
@endif
|
||||
@if($plan['accept_oxapay'] && $plan['oxapay_link'] !== null)
|
||||
<flux:button
|
||||
variant="filled"
|
||||
class="w-full mt-2 cursor-pointer"
|
||||
tag="a"
|
||||
href="{{ $plan['oxapay_link'] }}"
|
||||
target="_blank"
|
||||
>
|
||||
Pay with crypto
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
@include('livewire.dashboard.partials.plan-card', [
|
||||
'plan' => $plan,
|
||||
'providers' => $providers,
|
||||
'features' => $features,
|
||||
'hasTrial' => $hasTrial,
|
||||
'trialConfig' => $trialConfig,
|
||||
'billingCycle' => $billingCycle,
|
||||
'isPopularTier' => $isPopularTier
|
||||
])
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="w-full mt-8">
|
||||
<!-- Activation Key Section -->
|
||||
<div class="w-full mt-12">
|
||||
<flux:separator text="or" />
|
||||
<div class="w-full mt-4 mb-8 items-center flex justify-center">
|
||||
<h1 class="text-center text-3xl text-gray-900 dark:text-gray-200">Have an Activation Key?</h1>
|
||||
<div class="w-full mt-8 mb-6 items-center flex justify-center">
|
||||
<h1 class="text-center text-2xl text-gray-900 dark:text-gray-200">Have an Activation Key?</h1>
|
||||
</div>
|
||||
<div class="flex rounded-lg overflow-hidden border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-zinc-800">
|
||||
<input
|
||||
type="text"
|
||||
wire:model="activation_key"
|
||||
class="w-full px-4 py-2 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 placeholder-zinc-500 focus:outline-none"
|
||||
placeholder="Enter your activation key"
|
||||
/>
|
||||
<button
|
||||
wire:click="activateKey"
|
||||
class="cursor-pointer px-5 text-white transition-colors dark:text-white bg-[#4361EE] dark:bg-[#4361EE] disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
:disabled="wire:loading"
|
||||
>
|
||||
<!-- Show Loader when loading -->
|
||||
<span wire:loading.remove>Activate</span>
|
||||
<span wire:loading class="flex justify-center items-center px-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_7NYg{animation:spinner_0KQs 1.2s cubic-bezier(0.52,.6,.25,.99) infinite}@keyframes spinner_0KQs{0%{r:0;opacity:1}100%{r:11px;opacity:0}}</style><circle class="spinner_7NYg" cx="12" cy="12" r="0" fill="white"/></svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Redeem your activation key, purchased with Pay with Crypto option.
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
@error('activation_key')
|
||||
<div class="mt-4 app-primary">
|
||||
{{ $message }}
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="flex rounded-lg overflow-hidden border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-zinc-800">
|
||||
<input
|
||||
type="text"
|
||||
wire:model="activation_key"
|
||||
class="w-full px-4 py-3 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 placeholder-zinc-500 focus:outline-none"
|
||||
placeholder="Enter your activation key"
|
||||
/>
|
||||
<button
|
||||
wire:click="activateKey"
|
||||
wire:loading.attr="disabled"
|
||||
class="cursor-pointer px-6 py-3 text-white transition-colors dark:text-white bg-[#4361EE] hover:bg-[#3651D4] disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span wire:loading.remove>Activate</span>
|
||||
<span wire:loading class="flex justify-center items-center">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>.spinner_7NYg{animation:spinner_0KQs 1.2s cubic-bezier(0.52,.6,.25,.99) infinite}@keyframes spinner_0KQs{0%{r:0;opacity:1}100%{r:11px;opacity:0}}</style>
|
||||
<circle class="spinner_7NYg" cx="12" cy="12" r="0" fill="white"/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Redeem your activation key for instant access to premium features.
|
||||
</div>
|
||||
@enderror
|
||||
<!-- Success/Error Message -->
|
||||
@if (session()->has('success'))
|
||||
<div class="mt-4" style="color: #00AB55">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session()->has('error'))
|
||||
<div class="mt-4 app-primary">
|
||||
{{ session('error') }}
|
||||
<!-- Error/Success Messages -->
|
||||
<div class="mt-4 space-y-2">
|
||||
@error('activation_key')
|
||||
<div class="p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 text-sm">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@endif
|
||||
@enderror
|
||||
|
||||
@if (session()->has('success'))
|
||||
<div class="p-3 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-700 dark:text-green-300 text-sm">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session()->has('error'))
|
||||
<div class="p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 text-sm">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user