Update pricing UX, billing flows, and API rules

This commit is contained in:
Dwindi Ramadhana
2026-02-12 00:52:40 +07:00
parent cf065fab1e
commit a905256353
202 changed files with 22348 additions and 301 deletions

View File

@@ -0,0 +1,127 @@
@extends('dashboard.app')
@section('title', 'Billing')
@section('page_title', 'Billing')
@section('page_subtitle', 'Subscription status and plan details.')
@section('dashboard_content')
@php
$user = $user ?? auth()->user();
$subscription = $subscription ?? null;
$hasSub = $subscription !== null;
$orders = $orders ?? collect();
$payments = $payments ?? collect();
@endphp
<div class="grid gap-6 lg:grid-cols-3">
<div class="rounded-3xl glass-card p-6">
<div class="text-xs uppercase tracking-[0.25em] text-gray-400">Plan</div>
<div class="mt-3 text-3xl font-semibold text-white">{{ $hasSub ? ucfirst($subscription->plan ?? 'Personal') : 'Free' }}</div>
<div class="mt-2 text-sm text-gray-400">{{ $hasSub ? ucfirst($subscription->status ?? 'active') : 'No subscription' }}</div>
</div>
<div class="rounded-3xl glass-card p-6">
<div class="text-xs uppercase tracking-[0.25em] text-gray-400">Renewal</div>
<div class="mt-3 text-2xl font-semibold text-white">
{{ $subscription?->next_renewal_at?->toDateString() ?? '—' }}
</div>
<div class="mt-2 text-sm text-gray-400">Next charge date</div>
</div>
<div class="rounded-3xl glass-card p-6">
<div class="text-xs uppercase tracking-[0.25em] text-gray-400">Status</div>
<div class="mt-3 text-2xl font-semibold text-white">
{{ $subscription?->expires_at?->toDateString() ?? 'Active' }}
</div>
<div class="mt-2 text-sm text-gray-400">Access ends</div>
</div>
</div>
<div class="mt-8 rounded-3xl glass-card p-6">
<div class="flex items-center justify-between">
<div>
<div class="text-xs uppercase tracking-[0.25em] text-gray-400">Billing summary</div>
<div class="mt-2 text-xl font-semibold text-white">Subscription details</div>
</div>
<span class="rounded-full border border-white/10 px-3 py-1 text-xs text-gray-300 theme-surface">Current cycle</span>
</div>
<div class="mt-4 text-sm text-gray-300">
@if ($hasSub)
Provider: {{ $subscription->provider ?? 'direct' }} · Started {{ $subscription->started_at?->toDateString() }}
@else
You are on the free plan. Upgrade to unlock private keywords and sync.
@endif
</div>
<div class="mt-3 text-xs text-gray-400">
Downgrading to Free revokes any active API keys immediately.
</div>
<div class="mt-6 grid gap-4 md:grid-cols-2">
<div class="rounded-2xl bg-white/5 border border-white/10 p-4">
<div class="text-sm font-semibold text-gray-200">Payment method</div>
<div class="mt-2 text-2xl font-semibold text-white">Card</div>
<div class="mt-1 text-xs text-gray-400">Coming soon</div>
</div>
<div class="rounded-2xl bg-white/5 border border-white/10 p-4">
<div class="text-sm font-semibold text-gray-200">Invoices</div>
<div class="mt-2 text-2xl font-semibold text-white">0</div>
<div class="mt-1 text-xs text-gray-400">Billing history</div>
</div>
</div>
@if ($payments->count() > 0)
<div class="mt-6">
<div class="text-xs uppercase tracking-[0.25em] text-gray-400">Recent payments</div>
<div class="mt-3 overflow-hidden rounded-2xl border border-white/10">
<table class="min-w-full text-sm text-gray-300">
<thead class="bg-white/5 text-xs uppercase tracking-[0.2em] text-gray-400">
<tr>
<th class="px-4 py-3 text-left">Provider</th>
<th class="px-4 py-3 text-left">Plan</th>
<th class="px-4 py-3 text-left">Amount</th>
<th class="px-4 py-3 text-left">Status</th>
<th class="px-4 py-3 text-left">Created</th>
</tr>
</thead>
<tbody class="divide-y divide-white/10">
@foreach ($payments as $payment)
@php
$status = $payment->status ?? 'pending';
$pill = $status === 'paid'
? ['bg' => 'bg-emerald-100 dark:bg-emerald-500/20', 'text' => 'text-emerald-800 dark:text-emerald-200']
: ($status === 'failed'
? ['bg' => 'bg-rose-100 dark:bg-rose-500/20', 'text' => 'text-rose-800 dark:text-rose-200']
: ['bg' => 'bg-amber-100 dark:bg-amber-500/20', 'text' => 'text-amber-800 dark:text-amber-200']);
@endphp
<tr>
<td class="px-4 py-3">{{ $payment->provider ?? '—' }}</td>
<td class="px-4 py-3">{{ $payment->plan_code ?? '—' }}</td>
<td class="px-4 py-3">{{ $payment->currency ?? 'USD' }} {{ number_format((float) ($payment->amount ?? 0), 2) }}</td>
<td class="px-4 py-3">
<span class="rounded-full {{ $pill['bg'] }} px-3 py-1 text-xs font-semibold {{ $pill['text'] }}">{{ $status }}</span>
</td>
<td class="px-4 py-3 text-xs text-gray-400">{{ $payment->created_at?->toDateString() ?? '—' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@if ($payments->contains(fn ($payment) => in_array($payment->status, ['pending', 'failed'], true)))
<div class="mt-4 rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800 dark:border-amber-400/30 dark:bg-amber-400/10 dark:text-amber-200">
Pending or failed payments need a new checkout. Start a fresh transaction from the pricing page.
</div>
<a href="{{ route('pricing') }}" class="mt-3 inline-flex items-center justify-center rounded-full border border-amber-300 px-4 py-2 text-xs font-semibold text-amber-800 hover:bg-amber-100 dark:border-amber-300/40 dark:text-amber-200 dark:hover:bg-amber-400/10">
Start new checkout
</a>
@endif
</div>
@endif
@if (!$hasSub || (string) $user?->tier !== 'personal')
<div class="mt-6 rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800 dark:border-brand-sun/30 dark:bg-brand-sun/10 dark:text-brand-sun">
Upgrade to Personal for private keywords and synced personalization.
</div>
<a href="{{ route('pricing') }}" class="mt-4 inline-flex items-center justify-center rounded-full bg-brand-sun text-black font-semibold px-4 py-2 text-sm">
Upgrade to Personal
</a>
@endif
</div>
@endsection