Skip to main content

Feedback & status

Upgrade Banner code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/upgrade-banner.blade.php

@props([
    'title' => null,
    'description' => null,
    'variant' => 'brand',
    'icon' => null,
    'cta' => 'Upgrade plan',
    'href' => null,
    'plan' => null,
    'usageLabel' => null,
    'used' => null,
    'limit' => null,
    'unit' => null,
    'dismissible' => false,
    'storageKey' => null,
])

@php
    /*
    | Contextual upgrade / plan-limit nudge card. Designed to surface when a
    | usage-meter nears or hits a quota. Renders an icon medallion, a headline +
    | description, an optional inline usage meter and a primary CTA. Use the
    | `actions` slot to override the buttons. Distinct from the full-width
    | announcement banner component.
    |
    | variant: brand (nudge), amber (approaching limit), red (limit reached)
    |
    | Usage:
    |   <x-ui.upgrade-banner
    |       variant="amber"
    |       title="You're approaching your API limit"
    |       description="You've used 92% of your monthly requests."
    |       :used="9200" :limit="10000" usage-label="API calls" unit="requests"
    |       cta="Upgrade to Pro" href="/billing" />
    */
    $variants = [
        'brand' => [
            'wrap' => 'border-brand-200 bg-gradient-to-br from-brand-50 to-white dark:border-brand-900/60 dark:from-brand-950/50 dark:to-gray-900',
            'medallion' => 'bg-brand-100 text-brand-600 dark:bg-brand-900/60 dark:text-brand-300',
            'icon' => 'rocket-launch',
            'button' => 'primary',
            'meter' => 'brand',
        ],
        'amber' => [
            'wrap' => 'border-amber-200 bg-gradient-to-br from-amber-50 to-white dark:border-amber-900/60 dark:from-amber-950/40 dark:to-gray-900',
            'medallion' => 'bg-amber-100 text-amber-600 dark:bg-amber-900/60 dark:text-amber-300',
            'icon' => 'warning',
            'button' => 'primary',
            'meter' => 'amber',
        ],
        'red' => [
            'wrap' => 'border-red-200 bg-gradient-to-br from-red-50 to-white dark:border-red-900/60 dark:from-red-950/40 dark:to-gray-900',
            'medallion' => 'bg-red-100 text-red-600 dark:bg-red-900/60 dark:text-red-300',
            'icon' => 'warning-octagon',
            'button' => 'danger',
            'meter' => 'red',
        ],
    ];
    $meta = $variants[$variant] ?? $variants['brand'];
    $resolvedIcon = $icon ?? $meta['icon'];
    $hasMeter = $used !== null && $limit !== null;
@endphp

<div
    @if ($dismissible)
        x-data="{
            show: true,
            init() {
                @if ($storageKey)
                    if (localStorage.getItem('upgrade-banner:{{ $storageKey }}') === 'dismissed') { this.show = false; }
                @endif
            },
            dismiss() {
                this.show = false;
                @if ($storageKey)
                    localStorage.setItem('upgrade-banner:{{ $storageKey }}', 'dismissed');
                @endif
            },
        }"
        x-show="show"
        x-cloak
    @endif
    role="region"
    aria-label="{{ $title ?? 'Upgrade' }}"
    {{ $attributes->merge(['class' => 'relative overflow-hidden rounded-2xl border p-5 sm:p-6 '.$meta['wrap']]) }}
>
    <div class="flex flex-col gap-4 sm:flex-row sm:items-start">
        <span class="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl {{ $meta['medallion'] }}">
            <x-ui.icon :name="$resolvedIcon" weight="fill" size="lg" />
        </span>

        <div class="min-w-0 flex-1">
            <div class="flex flex-wrap items-center gap-2">
                @if ($title)
                    <h3 class="text-base font-semibold text-gray-900 dark:text-white">{{ $title }}</h3>
                @endif
                @if ($plan)
                    <x-ui.badge variant="gray" size="sm">{{ $plan }}</x-ui.badge>
                @endif
            </div>

            @if ($description)
                <p class="mt-1 text-sm text-gray-600 dark:text-gray-300">{{ $description }}</p>
            @endif

            @if (! $slot->isEmpty())
                <div class="mt-1 text-sm text-gray-600 dark:text-gray-300">{{ $slot }}</div>
            @endif

            @if ($hasMeter)
                <div class="mt-4">
                    <x-ui.usage-meter
                        :label="$usageLabel"
                        :used="$used"
                        :limit="$limit"
                        :unit="$unit"
                        :variant="$meta['meter']"
                        size="sm"
                    />
                </div>
            @endif

            <div class="mt-4 flex flex-wrap items-center gap-3">
                @isset($actions)
                    {{ $actions }}
                @else
                    <x-ui.button :variant="$meta['button']" size="sm" :href="$href" icon="lightning">{{ $cta }}</x-ui.button>
                @endisset
            </div>
        </div>
    </div>

    @if ($dismissible)
        <button
            type="button"
            @click="dismiss()"
            class="absolute end-3 top-3 rounded-md p-1.5 text-gray-400 opacity-70 transition hover:bg-black/5 hover:opacity-100 dark:text-gray-500 dark:hover:bg-white/5"
            aria-label="Dismiss"
        >
            <x-ui.icon name="x" weight="bold" size="sm" />
        </button>
    @endif
</div>