CluPilotCloud/resources/views/livewire/admin/plan-versions.blade.php

196 lines
12 KiB
PHP

@php
$eur = fn (int $cents) => Illuminate\Support\Number::currency($cents / 100, in: $currency, locale: app()->getLocale());
@endphp
<div class="space-y-5">
<div class="animate-rise">
<a href="{{ route('admin.plans') }}" wire:navigate class="inline-flex items-center gap-1.5 text-xs font-medium text-muted hover:text-body">
<x-ui.icon name="arrow-left" class="size-3.5" />
{{ __('plans.back') }}
</a>
<h1 class="mt-2 text-2xl font-bold tracking-tight text-ink">{{ $family->name }}</h1>
<p class="mt-1 text-sm text-muted">{{ __('plans.versions_subtitle') }}</p>
</div>
@unless ($family->sales_enabled)
{{-- The kill switch overrides every window, so say so once at the top
rather than leaving each version to look live on its own. --}}
<div class="rounded-lg border border-warning-border bg-warning-bg p-4 text-sm text-body animate-rise">
{{ __('plans.family_withdrawn_note') }}
</div>
@endunless
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[1fr_380px] xl:items-start">
{{-- Versions --}}
<div class="space-y-4 animate-rise [animation-delay:60ms]">
@forelse ($versions as $version)
@php
// The window being open is not the same as the plan being
// sold: the family's kill switch overrides every window.
$windowOpen = $version->isAvailableAt($now);
$live = $windowOpen && $family->sales_enabled;
$scheduled = $version->isPublished() && $version->available_from->greaterThan($now);
@endphp
<div wire:key="v-{{ $version->uuid }}" class="rounded-lg border border-line bg-surface p-5 shadow-xs
{{ $live ? 'border-success-border' : '' }}">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<h2 class="text-base font-semibold text-ink">{{ __('plans.version', ['n' => $version->version]) }}</h2>
@if ($live)
<x-ui.badge status="active">{{ __('plans.badge_live') }}</x-ui.badge>
@elseif ($windowOpen)
<x-ui.badge status="suspended">{{ __('plans.badge_withdrawn') }}</x-ui.badge>
@elseif ($scheduled)
<x-ui.badge status="pending">{{ __('plans.badge_scheduled') }}</x-ui.badge>
@elseif ($version->isPublished())
<x-ui.badge status="closed">{{ __('plans.badge_closed') }}</x-ui.badge>
@else
<x-ui.badge status="warning">{{ __('plans.badge_draft') }}</x-ui.badge>
@endif
</div>
<p class="mt-1 text-xs text-muted">
@if ($version->isPublished())
{{ __('plans.window', [
'from' => $version->available_from->isoFormat('L LT'),
'until' => $version->available_until?->isoFormat('L LT') ?? __('plans.open_ended'),
]) }}
@else
{{ __('plans.draft_hint') }}
@endif
</p>
</div>
<div class="flex gap-2">
@if (! $version->isPublished())
<x-ui.button size="sm" wire:click="choose('{{ $version->uuid }}')">{{ __('plans.publish') }}</x-ui.button>
<x-ui.button variant="secondary" size="sm"
x-on:click="Livewire.dispatch('openModal', { component: 'admin.confirm-delete-plan-draft', arguments: { uuid: '{{ $version->uuid }}' } })">
{{ __('plans.discard') }}
</x-ui.button>
@elseif ($windowOpen)
<x-ui.button variant="secondary" size="sm" wire:click="close('{{ $version->uuid }}')">
{{ __('plans.close_now') }}
</x-ui.button>
@endif
</div>
</div>
{{-- Publish form, inline under the draft it applies to --}}
@if ($publishing === $version->uuid)
<div class="mt-4 rounded-lg border border-line-strong bg-surface-2 p-4">
<p class="text-sm text-body">{{ __('plans.publish_warning') }}</p>
<div class="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
<x-ui.input name="availableFrom" type="datetime-local"
:label="__('plans.available_from')" wire:model="availableFrom" />
<x-ui.input name="availableUntil" type="datetime-local"
:label="__('plans.available_until')" :hint="__('plans.available_until_hint')"
wire:model="availableUntil" />
</div>
<div class="mt-4 flex justify-end gap-2">
<x-ui.button variant="secondary" size="sm" wire:click="cancelPublish">{{ __('plans.cancel') }}</x-ui.button>
<x-ui.button size="sm" wire:click="publish" wire:loading.attr="disabled">{{ __('plans.publish_confirm') }}</x-ui.button>
</div>
</div>
@endif
<dl class="mt-4 grid grid-cols-2 gap-x-4 gap-y-2 text-sm sm:grid-cols-4">
@foreach ([
['plans.f_storage', $version->quota_gb.' GB'],
['plans.f_traffic', $version->traffic_gb.' GB'],
['plans.f_seats', $version->seats],
['plans.f_performance', __('billing.perf.'.($version->performance ?? 'standard'))],
['plans.f_ram', $version->ram_mb.' MB'],
['plans.f_cores', $version->cores],
['plans.f_disk', $version->disk_gb.' GB'],
['plans.f_template', $version->template_vmid ?? '—'],
] as [$label, $value])
<div>
<dt class="text-xs font-semibold text-muted">{{ __($label) }}</dt>
<dd class="text-body">{{ $value }}</dd>
</div>
@endforeach
</dl>
<div class="mt-4 flex flex-wrap gap-4 border-t border-line pt-3 text-sm">
@foreach ($version->prices->sortBy('term') as $price)
<span class="text-body">
<span class="text-xs font-semibold text-muted">{{ __('plans.term_'.$price->term) }}</span>
<span class="ml-1.5 font-semibold text-ink">{{ $eur($price->amount_cents) }}</span>
<span class="text-xs text-muted">{{ __('plans.net') }}</span>
</span>
@endforeach
</div>
@if ($version->features)
<div class="mt-3 flex flex-wrap gap-1.5">
@foreach ($version->features as $feature)
<span class="rounded-pill border border-line-strong bg-surface-2 px-2 py-0.5 text-xs text-muted">
{{ __('billing.feature.'.$feature) }}
</span>
@endforeach
</div>
@endif
</div>
@empty
<p class="rounded-lg border border-line bg-surface p-8 text-center text-sm text-muted shadow-xs">
{{ __('plans.no_versions') }}
</p>
@endforelse
</div>
{{-- New draft --}}
<div class="rounded-lg border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:120ms]">
<h2 class="text-base font-semibold text-ink">{{ __('plans.draft_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('plans.draft_body') }}</p>
<form wire:submit="draft" class="mt-4 space-y-4">
<div class="grid grid-cols-2 gap-3">
<x-ui.input name="quotaGb" type="number" min="1" max="1000000" :label="__('plans.f_storage')" wire:model="quotaGb" />
<x-ui.input name="trafficGb" type="number" min="0" max="10000000" :label="__('plans.f_traffic')" wire:model="trafficGb" />
<x-ui.input name="seats" type="number" min="1" max="100000" :label="__('plans.f_seats')" wire:model="seats" />
<div class="space-y-1.5">
<label for="performance" class="block text-sm font-medium text-body">{{ __('plans.f_performance') }}</label>
{{-- A list, not free text: a typo here would be published,
frozen, and shown to customers as a raw key. --}}
<select id="performance" wire:model="performance"
class="block w-full rounded border border-line bg-surface px-3 py-2 text-sm text-ink">
@foreach ($performanceClasses as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</select>
@error('performance')<p class="text-xs text-danger">{{ $message }}</p>@enderror
</div>
</div>
<p class="pt-1 text-xs font-semibold text-muted">{{ __('plans.infra') }}</p>
<div class="grid grid-cols-2 gap-3">
<x-ui.input name="ramMb" type="number" min="512" max="4194304" :label="__('plans.f_ram')" wire:model="ramMb" />
<x-ui.input name="cores" type="number" min="1" max="512" :label="__('plans.f_cores')" wire:model="cores" />
<x-ui.input name="diskGb" type="number" min="1" max="1000000" :label="__('plans.f_disk')" :hint="__('plans.disk_hint')" wire:model="diskGb" />
<x-ui.input name="templateVmid" type="number" min="100" max="999999999" :label="__('plans.f_template')" wire:model="templateVmid" />
</div>
<p class="pt-1 text-xs font-semibold text-muted">{{ __('plans.pricing') }}</p>
<div class="grid grid-cols-2 gap-3">
<x-ui.input name="monthlyPrice" inputmode="decimal" :label="__('plans.term_monthly')" :hint="__('plans.euro_hint')" wire:model="monthlyPrice" />
<x-ui.input name="yearlyPrice" inputmode="decimal" :label="__('plans.term_yearly')" :hint="__('plans.euro_hint')" wire:model="yearlyPrice" />
</div>
<p class="pt-1 text-xs font-semibold text-muted">{{ __('plans.features') }}</p>
<div class="space-y-2">
@foreach ($featureKeys as $key)
<div wire:key="feat-{{ $key }}">
<x-ui.checkbox :name="'feature-'.$key" wire:model="features" value="{{ $key }}"
:label="__('billing.feature.'.$key)" />
</div>
@endforeach
</div>
<x-ui.button type="submit" class="w-full" wire:loading.attr="disabled">
{{ __('plans.draft_create') }}
</x-ui.button>
</form>
</div>
</div>
</div>