458 lines
30 KiB
PHP
458 lines
30 KiB
PHP
{{--
|
|
The customer's own settings.
|
|
|
|
It was one column 768 pixels wide inside a 1240-pixel shell — a third of the
|
|
window empty beside it — holding everything a customer might ever change:
|
|
the company address, the password, two-factor, the signed-in devices, the
|
|
branding, the contract and the button that closes the account. Two thousand
|
|
pixels of scroll, and the way to a particular thing was knowing how far down
|
|
it lived.
|
|
|
|
Four tabs, by what somebody came to do. Inside a tab the short cards sit side
|
|
by side instead of each taking a full row to hold three lines. The choice is
|
|
in the query string (see the #[Url] attribute on the component), so a reload,
|
|
a bookmark and the back button all land where they were.
|
|
--}}
|
|
<div class="space-y-6">
|
|
|
|
<header class="animate-rise">
|
|
<p class="lbl">{{ __('settings.eyebrow') }}</p>
|
|
<h1 class="mt-[7px] text-[23px] font-bold leading-[1.12] tracking-[-0.03em] text-ink min-[901px]:text-[30px]">
|
|
{{ __('settings.title') }}
|
|
</h1>
|
|
<p class="mt-2 max-w-[76ch] text-sm leading-relaxed text-muted">{{ __('settings.subtitle') }}</p>
|
|
</header>
|
|
|
|
{{-- The same tab bar the console uses, so one vocabulary holds on both sides
|
|
of the login. --}}
|
|
<div class="flex flex-wrap items-center gap-x-1 gap-y-2 border-b border-line animate-rise [animation-delay:40ms]" role="tablist">
|
|
@foreach ($tabs as $name)
|
|
<button type="button" role="tab" wire:click="$set('tab', '{{ $name }}')"
|
|
aria-selected="{{ $tab === $name ? 'true' : 'false' }}"
|
|
@class([
|
|
'flex items-center gap-2 whitespace-nowrap border-b-2 px-4 py-2.5 text-sm font-medium transition-colors -mb-px',
|
|
'border-accent-active text-ink' => $tab === $name,
|
|
'border-transparent text-muted hover:text-ink' => $tab !== $name,
|
|
])>
|
|
<x-ui.icon :name="['profile' => 'users', 'security' => 'shield-check', 'branding' => 'pen', 'contract' => 'receipt'][$name] ?? 'settings'"
|
|
class="size-4" />{{ __('settings.tab.'.$name) }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
|
|
{{-- ── Ihre Daten ───────────────────────────────────────────────────── --}}
|
|
@if ($tab === 'profile')
|
|
<form wire:submit="saveProfile" class="grid gap-5 lg:grid-cols-[minmax(0,1fr)_320px] lg:items-start animate-rise [animation-delay:60ms]">
|
|
<div class="space-y-5 rounded-lg border border-line bg-surface p-6 shadow-xs">
|
|
<div>
|
|
<h2 class="text-sm font-bold text-ink">{{ __('settings.company_title') }}</h2>
|
|
<p class="mt-1 text-sm text-muted">{{ __('settings.company_sub') }}</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<x-ui.input name="companyName" wire:model="companyName" :label="__('settings.company_name')" />
|
|
<x-ui.input name="contactName" wire:model="contactName" :label="__('settings.contact_name')" />
|
|
<x-ui.input name="phone" wire:model="phone" :label="__('settings.phone')" />
|
|
<x-ui.input name="vatId" wire:model="vatId" :label="__('settings.vat_id')" />
|
|
</div>
|
|
|
|
{{-- Field by field, not a paragraph: what landed in the
|
|
textarea was whatever somebody typed — a postcode on the
|
|
street line, a city with no postcode, no country at all —
|
|
and an invoice has to name its recipient precisely enough to
|
|
be a document. Postcode and town share a row because that is
|
|
how they are read and written. --}}
|
|
<div class="space-y-4">
|
|
<p class="text-sm font-medium text-body">{{ __('settings.billing_address') }}</p>
|
|
<x-ui.input name="billingStreet" wire:model="billingStreet" :label="__('settings.billing_street')" />
|
|
<div class="grid gap-4 sm:grid-cols-[140px_minmax(0,1fr)]">
|
|
<x-ui.input name="billingPostcode" wire:model="billingPostcode" :label="__('settings.billing_postcode')" />
|
|
<x-ui.input name="billingCity" wire:model="billingCity" :label="__('settings.billing_city')" />
|
|
</div>
|
|
<x-ui.input name="billingCountry" wire:model="billingCountry" :label="__('settings.billing_country')" />
|
|
</div>
|
|
|
|
<div class="flex justify-end border-t border-line pt-4">
|
|
<x-ui.button variant="primary" type="submit" wire:loading.attr="disabled" wire:target="saveProfile">{{ __('settings.save') }}</x-ui.button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Beside the form, and for a record that has one it is a FACT
|
|
rather than a field. It decides the fourteen-day right of
|
|
withdrawal: a business able to set itself to "Privatperson" here
|
|
is a business able to withdraw from a contract it may not
|
|
withdraw from. Registration asks the question; this page reports
|
|
the answer. The lock is in Settings::saveProfile, not here — a
|
|
form that only hides a control has never stopped anybody who can
|
|
post to /livewire/update.
|
|
|
|
A record created before the question existed has no answer yet,
|
|
and may be given one here, once. --}}
|
|
<div class="space-y-3 rounded-lg border border-line bg-surface p-6 shadow-xs">
|
|
<p class="text-sm font-bold text-ink">{{ __('settings.customer_type') }}</p>
|
|
|
|
@if ($customer?->customer_type)
|
|
<p class="flex items-center gap-2 text-sm font-semibold text-ink">
|
|
<x-ui.icon name="lock" class="size-4 text-muted" />
|
|
{{ __('settings.customer_type_'.$customer->customer_type) }}
|
|
</p>
|
|
<p class="text-xs leading-relaxed text-muted">{{ __('settings.customer_type_locked') }}</p>
|
|
@else
|
|
<div class="space-y-2">
|
|
@foreach ($customerTypes as $type)
|
|
<label class="flex cursor-pointer items-center gap-2 rounded border border-line-strong bg-surface px-3 py-2 text-sm text-ink transition has-[:checked]:border-ink">
|
|
<input type="radio" wire:model="customerType" value="{{ $type }}"
|
|
class="size-4 shrink-0 border-line text-ink" />
|
|
{{ __('settings.customer_type_'.$type) }}
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
<p class="text-xs leading-relaxed text-muted">{{ __('settings.customer_type_once') }}</p>
|
|
@error('customerType')<p class="text-xs text-danger">{{ $message }}</p>@enderror
|
|
@endif
|
|
</div>
|
|
</form>
|
|
@endif
|
|
|
|
{{-- ── Sicherheit ───────────────────────────────────────────────────── --}}
|
|
@if ($tab === 'security')
|
|
<div class="grid gap-5 lg:grid-cols-2 lg:items-start animate-rise [animation-delay:60ms]">
|
|
|
|
{{-- Own password. There was no way to change one at all — Fortify's
|
|
updatePasswords feature was switched off. --}}
|
|
<form wire:submit="updateOwnPassword" class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs">
|
|
<div>
|
|
<h2 class="text-sm font-bold text-ink">{{ __('admin_settings.password_title') }}</h2>
|
|
<p class="mt-1 text-sm text-muted">{{ __('admin_settings.password_sub') }}</p>
|
|
</div>
|
|
|
|
<x-ui.input name="currentPassword" type="password" autocomplete="current-password"
|
|
:label="__('admin_settings.password_current')" wire:model="currentPassword" />
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<x-ui.input name="newPassword" type="password" autocomplete="new-password"
|
|
:label="__('admin_settings.password_new')" wire:model="newPassword" />
|
|
<x-ui.input name="newPasswordConfirmation" type="password" autocomplete="new-password"
|
|
:label="__('admin_settings.password_repeat')" wire:model="newPasswordConfirmation" />
|
|
</div>
|
|
|
|
<div class="flex justify-end border-t border-line pt-4">
|
|
<x-ui.button type="submit" variant="primary" wire:loading.attr="disabled" wire:target="updateOwnPassword">{{ __('admin_settings.password_save') }}</x-ui.button>
|
|
</div>
|
|
</form>
|
|
|
|
{{-- Two-factor. Every action is re-checked server-side against a
|
|
recent password confirmation — hiding the buttons stops nobody
|
|
who can post to /livewire/update. --}}
|
|
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs">
|
|
<div>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<h2 class="text-sm font-bold text-ink">{{ __('settings.twofa_title') }}</h2>
|
|
<span class="inline-flex items-center gap-1.5 rounded-pill border px-2.5 py-0.5 text-xs font-medium
|
|
{{ $twoFactorOn ? 'border-success-border bg-success-bg text-success' : 'border-line-strong bg-surface-2 text-muted' }}">
|
|
<span class="size-1.5 rounded-pill bg-current" aria-hidden="true"></span>
|
|
{{ $twoFactorOn ? __('settings.twofa_state_on') : __('settings.twofa_state_off') }}
|
|
</span>
|
|
</div>
|
|
<p class="mt-1.5 text-sm text-muted">{{ __('settings.twofa_sub') }}</p>
|
|
</div>
|
|
|
|
@if (! $passwordConfirmed)
|
|
{{-- The gate. A signed-in session is not enough to change how
|
|
the account is protected — the risk is an unlocked
|
|
machine. --}}
|
|
<form wire:submit="confirmPassword" class="rounded border border-line bg-surface-2 p-4">
|
|
<p class="text-sm text-body">{{ __('settings.twofa_confirm_first') }}</p>
|
|
<div class="mt-3 flex flex-wrap items-start gap-2">
|
|
<div class="min-w-56 flex-1">
|
|
<x-ui.input name="confirmablePassword" type="password" autocomplete="current-password"
|
|
:label="__('admin_settings.password_current')" wire:model="confirmablePassword" />
|
|
</div>
|
|
<x-ui.button type="submit" variant="secondary" class="mt-7" wire:loading.attr="disabled" wire:target="confirmPassword">
|
|
{{ __('settings.twofa_confirm_button') }}
|
|
</x-ui.button>
|
|
</div>
|
|
</form>
|
|
@elseif ($twoFactorOn)
|
|
<div class="flex flex-wrap gap-2">
|
|
<x-ui.button variant="secondary" wire:click="regenerateRecoveryCodes" wire:loading.attr="disabled" wire:target="regenerateRecoveryCodes">
|
|
{{ __('settings.twofa_new_codes') }}
|
|
</x-ui.button>
|
|
<x-ui.button variant="danger"
|
|
x-on:click="$dispatch('openModal', { component: 'confirm-disable-two-factor' })">
|
|
{{ __('settings.twofa_disable') }}
|
|
</x-ui.button>
|
|
</div>
|
|
@elseif ($twoFactorPending)
|
|
<div class="grid gap-5 sm:grid-cols-[auto_1fr] sm:items-start">
|
|
<div class="rounded border border-line bg-white p-3">{!! $twoFactorQr !!}</div>
|
|
<form wire:submit="confirmTwoFactor" class="space-y-3">
|
|
<p class="text-sm text-muted">{{ __('settings.twofa_scan') }}</p>
|
|
<div class="max-w-48">
|
|
<x-ui.input name="twoFactorCode" inputmode="numeric" autocomplete="one-time-code"
|
|
:label="__('settings.twofa_code')" wire:model="twoFactorCode" />
|
|
</div>
|
|
<x-ui.button type="submit" variant="primary" wire:loading.attr="disabled" wire:target="confirmTwoFactor">
|
|
{{ __('settings.twofa_activate') }}
|
|
</x-ui.button>
|
|
</form>
|
|
</div>
|
|
@else
|
|
<x-ui.button variant="primary" wire:click="enableTwoFactor" wire:loading.attr="disabled" wire:target="enableTwoFactor">
|
|
{{ __('settings.twofa_enable') }}
|
|
</x-ui.button>
|
|
@endif
|
|
|
|
@if ($recoveryCodes)
|
|
{{-- Shown once, on purpose: nobody writes down what they were
|
|
never shown, and these are the way back in when the phone
|
|
is gone. --}}
|
|
<div class="rounded border border-warning-border bg-warning-bg p-4">
|
|
<p class="text-sm font-medium text-warning">{{ __('settings.twofa_codes_title') }}</p>
|
|
<p class="mt-1 text-xs text-warning">{{ __('settings.twofa_codes_hint') }}</p>
|
|
<ul class="mt-3 grid grid-cols-2 gap-x-6 gap-y-1 font-mono text-sm text-ink">
|
|
@foreach ($recoveryCodes as $code)<li class="select-all">{{ $code }}</li>@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Under both, across the width: it answers the same question they
|
|
do — who can get into this account — and it is a table. --}}
|
|
<div class="lg:col-span-2">
|
|
@livewire('sessions')
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- ── Erscheinungsbild ─────────────────────────────────────────────── --}}
|
|
@if ($tab === 'branding')
|
|
<form wire:submit="saveBranding" class="grid gap-5 lg:grid-cols-[minmax(0,1fr)_320px] lg:items-start animate-rise [animation-delay:60ms]">
|
|
<div class="space-y-5 rounded-lg border border-line bg-surface p-6 shadow-xs">
|
|
<div>
|
|
<h2 class="text-sm font-bold text-ink">{{ __('settings.branding_title') }}</h2>
|
|
<p class="mt-1 text-sm text-muted">{{ __('settings.branding_sub') }}</p>
|
|
</div>
|
|
|
|
<x-ui.input name="brandDisplayName" wire:model="brandDisplayName" :label="__('settings.brand_display_name')" :placeholder="$branding['display_name'] ?? ''" />
|
|
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label class="text-sm font-medium text-body">{{ __('settings.brand_primary') }}</label>
|
|
<div class="mt-1.5 flex items-center gap-2">
|
|
<input type="color" wire:model="brandPrimary" value="{{ $brandPrimary ?: ($branding['primary_color'] ?? '#f97316') }}" class="h-9 w-12 rounded border border-line-strong bg-surface" aria-label="{{ __('settings.brand_primary') }}" />
|
|
<input type="text" wire:model="brandPrimary" placeholder="{{ $branding['primary_color'] ?? '#f97316' }}" class="w-28 rounded border border-line-strong bg-surface px-2.5 py-1.5 font-mono text-sm text-ink" />
|
|
</div>
|
|
@error('brandPrimary')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-medium text-body">{{ __('settings.brand_accent') }}</label>
|
|
<div class="mt-1.5 flex items-center gap-2">
|
|
<input type="color" wire:model="brandAccent" value="{{ $brandAccent ?: ($branding['accent_color'] ?? '#c2560a') }}" class="h-9 w-12 rounded border border-line-strong bg-surface" aria-label="{{ __('settings.brand_accent') }}" />
|
|
<input type="text" wire:model="brandAccent" placeholder="{{ $branding['accent_color'] ?? '#c2560a' }}" class="w-28 rounded border border-line-strong bg-surface px-2.5 py-1.5 font-mono text-sm text-ink" />
|
|
</div>
|
|
@error('brandAccent')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end border-t border-line pt-4">
|
|
<x-ui.button variant="primary" type="submit" wire:loading.attr="disabled" wire:target="saveBranding,logo">{{ __('settings.save') }}</x-ui.button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3 rounded-lg border border-line bg-surface p-6 shadow-xs">
|
|
<label class="text-sm font-bold text-ink">{{ __('settings.brand_logo') }}</label>
|
|
<div class="grid size-20 place-items-center overflow-hidden rounded border border-line bg-surface-2">
|
|
@if ($logo)
|
|
<img src="{{ $logo->temporaryUrl() }}" alt="" class="max-h-20 max-w-20 object-contain" />
|
|
@elseif ($logoUrl)
|
|
<img src="{{ $logoUrl }}" alt="" class="max-h-20 max-w-20 object-contain" />
|
|
@else
|
|
<span class="px-2 text-center text-xs text-muted">{{ __('settings.brand_default') }}</span>
|
|
@endif
|
|
</div>
|
|
<input type="file" wire:model="logo" accept="image/png,image/webp" class="w-full text-sm text-body file:mr-3 file:rounded file:border-0 file:bg-surface-2 file:px-3 file:py-1.5 file:text-sm file:font-semibold file:text-body" />
|
|
<p class="text-xs leading-relaxed text-muted">{{ __('settings.brand_logo_hint') }}</p>
|
|
@if ($logoUrl)
|
|
<button type="button" wire:click="removeLogo" class="text-xs font-semibold text-danger hover:underline">{{ __('settings.brand_logo_remove') }}</button>
|
|
@endif
|
|
@error('logo')<p class="text-xs text-danger">{{ $message }}</p>@enderror
|
|
@if ($branding['is_default'] ?? false)
|
|
<p class="border-t border-line pt-3 text-xs text-muted">{{ __('settings.brand_using_default') }}</p>
|
|
@endif
|
|
</div>
|
|
</form>
|
|
@endif
|
|
|
|
{{-- ── Vertrag ──────────────────────────────────────────────────────── --}}
|
|
{{-- ── Vertrag ──────────────────────────────────────────────────────
|
|
One column of full-width sections, not a two-column grid with one card
|
|
spanning both: that produced a short box, a wide one, then a short one
|
|
again — a staircase with no rhythm to it. Each section here has the same
|
|
shape (a header row, then its body), so the eye can run down the page
|
|
instead of tracking sideways. --}}
|
|
@if ($tab === 'contract')
|
|
<div class="space-y-5 animate-rise [animation-delay:60ms]">
|
|
|
|
{{-- Package, cancellation and — while it is open — the withdrawal. --}}
|
|
<section class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs">
|
|
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-line px-6 py-4">
|
|
<div class="min-w-0">
|
|
<h2 class="text-sm font-bold text-ink">{{ __('settings.package_title') }}</h2>
|
|
<p class="mt-0.5 text-sm text-muted">
|
|
@if ($cancellationScheduled)
|
|
{{ __('settings.cancel_scheduled_title') }}
|
|
@elseif ($hasActivePackage)
|
|
{{ __('settings.package_active', ['plan' => $instance ? __('billing.plan.'.$instance->plan) : '—']) }}
|
|
@else
|
|
{{ __('settings.no_package') }}
|
|
@endif
|
|
</p>
|
|
</div>
|
|
@if ($hasActivePackage && ! $cancellationScheduled)
|
|
<x-ui.button variant="secondary" size="sm"
|
|
x-on:click="$dispatch('openModal', { component: 'confirm-cancel-package' })">
|
|
{{ __('settings.cancel_cta') }}
|
|
</x-ui.button>
|
|
@endif
|
|
</div>
|
|
|
|
@if ($cancellationScheduled)
|
|
<div class="flex items-start gap-3 border-b border-line bg-warning-bg px-6 py-4">
|
|
<x-ui.icon name="alert-triangle" class="mt-0.5 size-5 shrink-0 text-warning" />
|
|
<p class="text-sm text-body">{{ __('settings.cancel_scheduled_body', ['date' => $instance?->service_ends_at?->local()->isoFormat('LL')]) }}</p>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- The fourteen-day right of withdrawal. Shown only where it
|
|
exists: a business customer has none and never sees this
|
|
block — and the server refuses them again in
|
|
Settings::withdraw(), because a card that is not rendered has
|
|
never stopped anybody who can post to /livewire/update.
|
|
|
|
Separate from the cancellation above it, and not a variant of
|
|
it. A cancellation ends a contract that was validly concluded
|
|
and keeps the term the customer paid for; a withdrawal
|
|
unwinds the contract itself, ends the service the same day
|
|
and sends the money back. --}}
|
|
@if ($withdrawal->applies && $withdrawal->open)
|
|
<div class="flex flex-wrap items-center justify-between gap-3 px-6 py-4">
|
|
<div class="min-w-0">
|
|
<p class="text-sm font-semibold text-ink">{{ __('withdrawal.card_title') }}</p>
|
|
<p class="mt-0.5 max-w-[70ch] text-sm text-muted">
|
|
{{ __('withdrawal.card_sub', [
|
|
'date' => $withdrawal->endsAt->local()->isoFormat('LL'),
|
|
'days' => $withdrawal->daysLeft(),
|
|
]) }}
|
|
</p>
|
|
</div>
|
|
<x-ui.button variant="secondary" size="sm"
|
|
x-on:click="$dispatch('openModal', { component: 'confirm-withdraw' })">
|
|
{{ __('withdrawal.cta') }}
|
|
</x-ui.button>
|
|
</div>
|
|
@endif
|
|
</section>
|
|
|
|
{{-- ── AV-Vertrag & TOM ──────────────────────────────────────────
|
|
Art. 28 DSGVO wants a contract wherever personal data is
|
|
processed on somebody else's behalf. It is concluded WITH the
|
|
terms at checkout (see the AGB), so this is not a second signing
|
|
ceremony: the document has to be available, current, and
|
|
retrievable. An extra confirmation is offered because some
|
|
customers — a practice, a firm, anybody who gets audited — want
|
|
one on file, and it costs a click.
|
|
|
|
Nothing renders until an operator has published a version. --}}
|
|
@if ($dpa !== null)
|
|
<section class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs">
|
|
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-line px-6 py-4">
|
|
<div class="min-w-0">
|
|
<h2 class="text-sm font-bold text-ink">{{ __('dpa.title') }}</h2>
|
|
<p class="mt-0.5 max-w-[76ch] text-sm leading-relaxed text-muted">{{ __('dpa.sub') }}</p>
|
|
</div>
|
|
<span class="inline-flex shrink-0 items-center gap-1.5 rounded-pill border border-line bg-surface-2 px-2.5 py-0.5 font-mono text-xs text-body">
|
|
{{ __('dpa.version', ['version' => $dpa->version]) }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap items-center gap-2 px-6 py-4">
|
|
<x-ui.button :href="route('dpa.file', 'agreement')" variant="secondary" size="sm" target="_blank">
|
|
<x-ui.icon name="file-text" class="size-4" />{{ __('dpa.read_agreement') }}
|
|
</x-ui.button>
|
|
@if ($dpa->measures_path)
|
|
<x-ui.button :href="route('dpa.file', 'measures')" variant="secondary" size="sm" target="_blank">
|
|
<x-ui.icon name="shield" class="size-4" />{{ __('dpa.read_measures') }}
|
|
</x-ui.button>
|
|
@endif
|
|
{{-- Keep, not just read: the file carries the version in
|
|
its name, so which fassung applied is answerable from
|
|
a downloads folder months later. --}}
|
|
<x-ui.button :href="route('dpa.file', ['which' => 'agreement', 'download' => 1])" variant="ghost" size="sm" download>
|
|
<x-ui.icon name="download" class="size-4" />{{ __('dpa.download_agreement') }}
|
|
</x-ui.button>
|
|
@if ($dpa->measures_path)
|
|
<x-ui.button :href="route('dpa.file', ['which' => 'measures', 'download' => 1])" variant="ghost" size="sm" download>
|
|
<x-ui.icon name="download" class="size-4" />{{ __('dpa.download_measures') }}
|
|
</x-ui.button>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="border-t border-line bg-surface-2 px-6 py-4">
|
|
@if ($dpaAcceptance)
|
|
{{-- R19: stored in UTC, read on the wall clock. --}}
|
|
<p class="flex flex-wrap items-center gap-2 text-sm text-body">
|
|
<x-ui.icon name="check" class="size-4 text-success" />
|
|
{{ __('dpa.accepted_on', [
|
|
'version' => $dpa->version,
|
|
'when' => $dpaAcceptance->accepted_at->local()->isoFormat('LL, LT'),
|
|
]) }}
|
|
</p>
|
|
@else
|
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
<p class="max-w-[76ch] text-sm leading-relaxed text-muted">{{ __('dpa.in_force_note') }}</p>
|
|
<x-ui.button variant="secondary" size="sm"
|
|
wire:click="acceptProcessingAgreement" wire:loading.attr="disabled" wire:target="acceptProcessingAgreement">
|
|
{{ __('dpa.accept_cta') }}
|
|
</x-ui.button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
{{-- Closing the account, and the deadlines that close one by itself. --}}
|
|
<section class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs">
|
|
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-line px-6 py-4">
|
|
<div class="min-w-0">
|
|
<h2 class="text-sm font-bold text-ink">{{ __('settings.close_account_title') }}</h2>
|
|
<p class="mt-0.5 text-sm text-muted">{{ __('settings.close_account_sub') }}</p>
|
|
</div>
|
|
<x-ui.button variant="secondary" size="sm"
|
|
x-on:click="$dispatch('openModal', { component: 'confirm-close-account' })">
|
|
{{ __('settings.close_cta') }}
|
|
</x-ui.button>
|
|
</div>
|
|
|
|
{{-- The deadlines, said where somebody asks the question.
|
|
"Nach fünf Tagen gelöscht" on the verification page reads as
|
|
if it applied to every account; it applies to a registration
|
|
nobody confirmed. The other rule — a year without a package —
|
|
was nowhere at all. Both come from the commands that enforce
|
|
them, so the page cannot drift from what actually happens. --}}
|
|
<div class="px-6 py-4">
|
|
<p class="text-sm font-semibold text-ink">{{ __('settings.lifecycle_title') }}</p>
|
|
<ul class="mt-2 space-y-1.5 text-sm leading-relaxed text-muted">
|
|
<li>{{ __('settings.lifecycle_unverified', ['days' => App\Console\Commands\PruneUnverifiedAccounts::AFTER_DAYS]) }}</li>
|
|
<li>{{ __('settings.lifecycle_dormant', ['warn' => App\Console\Commands\PruneDormantAccounts::WARN_DAYS_BEFORE]) }}</li>
|
|
<li>{{ __('settings.lifecycle_kept') }}</li>
|
|
</ul>
|
|
<a href="{{ route('legal.agb') }}" target="_blank" rel="noopener"
|
|
class="mt-3 inline-block text-sm font-medium text-accent-text underline-offset-4 hover:underline">
|
|
{{ __('settings.lifecycle_terms') }}
|
|
</a>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
@endif
|
|
</div>
|