diff --git a/resources/js/app.js b/resources/js/app.js index 813e050..a2558cf 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,5 +1,10 @@ import './bootstrap'; +import { registerLadillConfirmStore, registerLadillModalHelpers } from './ladill-modals'; + +registerLadillModalHelpers(); + + import Alpine from 'alpinejs'; import collapse from '@alpinejs/collapse'; @@ -547,4 +552,6 @@ window.serverOrderForm = (products = {}, billingCycleLabels = {}, initial = {}) }); window.Alpine = Alpine; +registerLadillConfirmStore(Alpine); + Alpine.start(); diff --git a/resources/js/ladill-modals.js b/resources/js/ladill-modals.js new file mode 100644 index 0000000..eb2bf74 --- /dev/null +++ b/resources/js/ladill-modals.js @@ -0,0 +1,75 @@ +/** + * Lightweight modal dispatch helpers (optional — balanceGate in layout is primary). + */ + +export function openLadillModal(name) { + if (!name) { + return false; + } + + window.dispatchEvent(new CustomEvent('open-modal', { detail: name, bubbles: true })); + + return true; +} + +export function closeLadillModal(name) { + if (!name) { + return false; + } + + window.dispatchEvent(new CustomEvent('close-modal', { detail: name, bubbles: true })); + + return true; +} + +export function ladillNeedsTopup(balance, price) { + const normalizedBalance = Number.parseFloat(balance); + const normalizedPrice = Number.parseFloat(price); + + if (!Number.isFinite(normalizedBalance) || !Number.isFinite(normalizedPrice)) { + return false; + } + + return normalizedBalance <= 0 || normalizedBalance < normalizedPrice; +} + +export function registerLadillConfirmStore(Alpine) { + Alpine.store('ladillConfirm', { + open: false, + title: '', + message: '', + confirmLabel: 'Confirm', + cancelLabel: 'Cancel', + variant: 'danger', + _resolve: null, + + ask(options = {}) { + return new Promise((resolve) => { + this.title = options.title || 'Are you sure?'; + this.message = options.message || ''; + this.confirmLabel = options.confirmLabel || 'Confirm'; + this.cancelLabel = options.cancelLabel || 'Cancel'; + this.variant = options.variant || 'danger'; + this._resolve = resolve; + this.open = true; + }); + }, + + answer(confirmed) { + if (this._resolve) { + this._resolve(confirmed); + } + + this.open = false; + this._resolve = null; + }, + }); + + window.ladillConfirm = (options = {}) => Alpine.store('ladillConfirm').ask(options); +} + +export function registerLadillModalHelpers() { + window.openLadillModal = openLadillModal; + window.closeLadillModal = closeLadillModal; + window.ladillNeedsTopup = ladillNeedsTopup; +} diff --git a/resources/views/components/confirm-dialog.blade.php b/resources/views/components/confirm-dialog.blade.php new file mode 100644 index 0000000..1d0ee69 --- /dev/null +++ b/resources/views/components/confirm-dialog.blade.php @@ -0,0 +1,69 @@ +@props([ + 'name', + 'title', + 'message' => null, + 'action', + 'method' => 'POST', + 'confirmLabel' => 'Confirm', + 'cancelLabel' => 'Cancel', + 'variant' => 'danger', +]) + +@php + $confirmBtnClass = $variant === 'danger' + ? 'bg-red-600 hover:bg-red-700' + : 'bg-violet-600 hover:bg-violet-700'; + $iconWrapClass = $variant === 'danger' + ? 'bg-red-100 text-red-600' + : 'bg-violet-100 text-violet-600'; +@endphp + +@if(isset($trigger)) + + {{ $trigger }} + +@endif + + +
+
+
+ @if($variant === 'danger') + + + + @else + + + + @endif +
+

{{ $title }}

+ @if($message) +

{{ $message }}

+ @endif + @isset($details) +
{{ $details }}
+ @endisset +
+ +
+ @csrf + @if(strtoupper($method) !== 'POST') + @method($method) + @endif + @isset($fields) + {{ $fields }} + @endisset + + +
+
+
diff --git a/resources/views/components/modal.blade.php b/resources/views/components/modal.blade.php new file mode 100644 index 0000000..a823707 --- /dev/null +++ b/resources/views/components/modal.blade.php @@ -0,0 +1,88 @@ +@props([ + 'name', + 'show' => false, + 'maxWidth' => '2xl' +]) + +@php +$maxWidth = [ + 'sm' => 'sm:max-w-sm', + 'md' => 'sm:max-w-md', + 'lg' => 'sm:max-w-lg', + 'xl' => 'sm:max-w-xl', + '2xl' => 'sm:max-w-2xl', +][$maxWidth]; +@endphp + +
+ {{-- Backdrop click to close --}} +
+ + {{-- Panel: bottom-sheet on mobile, centered card on sm+ --}} +
+ {{-- Drag handle (mobile only) --}} +
+
+
+ + {{-- Close button (desktop only) --}} + + + {{ $slot }} +
+
diff --git a/resources/views/email/account/developers.blade.php b/resources/views/email/account/developers.blade.php index 28d1b4a..ea2649a 100644 --- a/resources/views/email/account/developers.blade.php +++ b/resources/views/email/account/developers.blade.php @@ -46,10 +46,18 @@ {{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }}

-
- @csrf @method('DELETE') - -
+ + + + + @empty

No tokens yet.

diff --git a/resources/views/email/account/settings.blade.php b/resources/views/email/account/settings.blade.php index 7ec7bfc..54b854a 100644 --- a/resources/views/email/account/settings.blade.php +++ b/resources/views/email/account/settings.blade.php @@ -22,13 +22,22 @@
Linked mailbox: {{ $linkStatus['linked_mailbox'] }}
-
- @csrf @method('DELETE') - -
+ + + + + @elseif(($linkStatus['stage'] ?? '') === 'needs_domain')

Add and verify an email domain first.

diff --git a/resources/views/email/account/team.blade.php b/resources/views/email/account/team.blade.php index 48793ae..70c9fef 100644 --- a/resources/views/email/account/team.blade.php +++ b/resources/views/email/account/team.blade.php @@ -64,10 +64,18 @@ -
- @csrf @method('DELETE') - -
+ + + + + @else {{ $member->role }} @endif diff --git a/resources/views/email/domains/show.blade.php b/resources/views/email/domains/show.blade.php index 7805fb8..0d4c453 100644 --- a/resources/views/email/domains/show.blade.php +++ b/resources/views/email/domains/show.blade.php @@ -54,9 +54,18 @@ @endunless -
- @csrf @method('DELETE') - -
+ + + + + @endsection diff --git a/resources/views/email/mailboxes/show.blade.php b/resources/views/email/mailboxes/show.blade.php index 79c78f8..e5ee875 100644 --- a/resources/views/email/mailboxes/show.blade.php +++ b/resources/views/email/mailboxes/show.blade.php @@ -74,10 +74,19 @@

Delete mailbox

This permanently removes the mailbox and its email.

-
- @csrf @method('DELETE') - -
+ + + + +
@endsection diff --git a/resources/views/hosting/account/developers.blade.php b/resources/views/hosting/account/developers.blade.php index 5afe1e2..214f429 100644 --- a/resources/views/hosting/account/developers.blade.php +++ b/resources/views/hosting/account/developers.blade.php @@ -42,10 +42,18 @@ {{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }}

-
- @csrf @method('DELETE') - -
+ + + + + @empty

No tokens yet.

diff --git a/resources/views/hosting/account/team.blade.php b/resources/views/hosting/account/team.blade.php index 646671d..1045ce8 100644 --- a/resources/views/hosting/account/team.blade.php +++ b/resources/views/hosting/account/team.blade.php @@ -62,10 +62,18 @@ -
- @csrf @method('DELETE') - -
+ + + + + @else {{ $member->role }} @endif diff --git a/resources/views/hosting/panel/apps.blade.php b/resources/views/hosting/panel/apps.blade.php index b7a6053..c252e98 100644 --- a/resources/views/hosting/panel/apps.blade.php +++ b/resources/views/hosting/panel/apps.blade.php @@ -359,14 +359,21 @@ Credentials @endif -
- @csrf - @method('DELETE') - -
+ + + + + diff --git a/resources/views/hosting/panel/cron.blade.php b/resources/views/hosting/panel/cron.blade.php index 6a0668c..9ab3d6c 100644 --- a/resources/views/hosting/panel/cron.blade.php +++ b/resources/views/hosting/panel/cron.blade.php @@ -111,15 +111,24 @@ {{ $job['command'] }} -
- @csrf - @method('DELETE') - - -
+ + + + + + + + @endforeach diff --git a/resources/views/hosting/panel/databases.blade.php b/resources/views/hosting/panel/databases.blade.php index 685cf44..11686e7 100644 --- a/resources/views/hosting/panel/databases.blade.php +++ b/resources/views/hosting/panel/databases.blade.php @@ -71,14 +71,21 @@ Reset Password -
- @csrf - @method('DELETE') - -
+ + + + + @@ -126,8 +133,8 @@ @push('scripts')