Replace native confirm() dialogs with styled confirm modals.
Deploy Ladill Hosting / deploy (push) Successful in 30s
Deploy Ladill Hosting / deploy (push) Successful in 30s
Add shared confirm-dialog component, Alpine ladillConfirm store, and swap browser confirms for consistent bottom-sheet modals across user, admin, and hosting flows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
|
||||||
|
import { registerLadillConfirmStore, registerLadillModalHelpers } from './ladill-modals';
|
||||||
|
|
||||||
|
registerLadillModalHelpers();
|
||||||
|
|
||||||
|
|
||||||
import Alpine from 'alpinejs';
|
import Alpine from 'alpinejs';
|
||||||
import collapse from '@alpinejs/collapse';
|
import collapse from '@alpinejs/collapse';
|
||||||
|
|
||||||
@@ -187,4 +192,6 @@ Alpine.data('topbarSearch', (config = {}) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
window.Alpine = Alpine;
|
window.Alpine = Alpine;
|
||||||
|
registerLadillConfirmStore(Alpine);
|
||||||
|
|
||||||
Alpine.start();
|
Alpine.start();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
<span @click.stop="$dispatch('open-modal', {{ Js::from($name) }})" class="inline-flex">
|
||||||
|
{{ $trigger }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-modal :name="$name" maxWidth="md">
|
||||||
|
<div class="px-5 pb-6 pt-2 sm:px-6 sm:pb-6 sm:pt-4">
|
||||||
|
<div class="flex flex-col items-center text-center sm:items-start sm:text-left">
|
||||||
|
<div @class(['flex h-12 w-12 items-center justify-center rounded-full', $iconWrapClass])>
|
||||||
|
@if($variant === 'danger')
|
||||||
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/>
|
||||||
|
</svg>
|
||||||
|
@else
|
||||||
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"/>
|
||||||
|
</svg>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-4 text-lg font-semibold text-slate-900">{{ $title }}</h2>
|
||||||
|
@if($message)
|
||||||
|
<p class="mt-2 text-sm leading-relaxed text-slate-500">{{ $message }}</p>
|
||||||
|
@endif
|
||||||
|
@isset($details)
|
||||||
|
<div class="mt-3 w-full">{{ $details }}</div>
|
||||||
|
@endisset
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" action="{{ $action }}" class="mt-6 flex flex-col-reverse gap-2.5 sm:flex-row sm:justify-end">
|
||||||
|
@csrf
|
||||||
|
@if(strtoupper($method) !== 'POST')
|
||||||
|
@method($method)
|
||||||
|
@endif
|
||||||
|
@isset($fields)
|
||||||
|
{{ $fields }}
|
||||||
|
@endisset
|
||||||
|
<button type="button"
|
||||||
|
@click="$dispatch('close-modal', {{ Js::from($name) }})"
|
||||||
|
class="rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
|
||||||
|
{{ $cancelLabel }}
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
@class(['rounded-xl px-4 py-2.5 text-sm font-semibold text-white transition', $confirmBtnClass])>
|
||||||
|
{{ $confirmLabel }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</x-modal>
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="{
|
||||||
|
show: @js($show),
|
||||||
|
focusables() {
|
||||||
|
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||||
|
return [...$el.querySelectorAll(selector)]
|
||||||
|
.filter(el => ! el.hasAttribute('disabled'))
|
||||||
|
},
|
||||||
|
firstFocusable() { return this.focusables()[0] },
|
||||||
|
lastFocusable() { return this.focusables().slice(-1)[0] },
|
||||||
|
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
||||||
|
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
||||||
|
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
||||||
|
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||||
|
}"
|
||||||
|
x-init="$watch('show', value => {
|
||||||
|
if (value) {
|
||||||
|
document.body.classList.add('overflow-y-hidden');
|
||||||
|
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
|
||||||
|
} else {
|
||||||
|
setTimeout(() => { if (!show) document.body.classList.remove('overflow-y-hidden'); }, 200);
|
||||||
|
}
|
||||||
|
})"
|
||||||
|
x-on:open-modal.window="String($event.detail) === @js($name) ? show = true : null"
|
||||||
|
data-modal-name="{{ $name }}"
|
||||||
|
x-on:close-modal.window="String($event.detail) === @js($name) ? show = false : null"
|
||||||
|
x-on:close.stop="show = false"
|
||||||
|
x-on:keydown.escape.window="show = false"
|
||||||
|
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||||
|
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||||
|
x-show="show"
|
||||||
|
x-cloak
|
||||||
|
x-transition:enter="transition-opacity ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="transition-opacity ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 z-50 flex items-end sm:items-center sm:justify-center"
|
||||||
|
style="background: rgba(0,0,0,0.45); backdrop-filter: blur(3px);"
|
||||||
|
>
|
||||||
|
{{-- Backdrop click to close --}}
|
||||||
|
<div class="absolute inset-0" x-on:click="show = false" data-close-modal="{{ $name }}"></div>
|
||||||
|
|
||||||
|
{{-- Panel: bottom-sheet on mobile, centered card on sm+ --}}
|
||||||
|
<div
|
||||||
|
x-show="show"
|
||||||
|
class="relative z-10 w-full max-h-[90vh] overflow-y-auto rounded-t-3xl bg-white shadow-2xl sm:mb-6 sm:max-h-[85vh] sm:rounded-2xl {{ $maxWidth }} sm:border sm:border-gray-200"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="translate-y-full sm:translate-y-0 sm:opacity-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="translate-y-0 sm:opacity-100 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="translate-y-0 sm:opacity-100 sm:scale-100"
|
||||||
|
x-transition:leave-end="translate-y-full sm:translate-y-0 sm:opacity-0 sm:scale-95"
|
||||||
|
x-on:click.stop
|
||||||
|
>
|
||||||
|
{{-- Drag handle (mobile only) --}}
|
||||||
|
<div class="flex justify-center pb-1 pt-3 sm:hidden">
|
||||||
|
<div class="h-1 w-10 rounded-full bg-slate-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Close button (desktop only) --}}
|
||||||
|
<button @click="show = false" data-close-modal="{{ $name }}" type="button"
|
||||||
|
class="absolute right-3 top-3 z-10 hidden h-8 w-8 items-center justify-center rounded-full text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 sm:flex">
|
||||||
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -46,10 +46,18 @@
|
|||||||
{{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }}
|
{{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" action="{{ route('account.developers.destroy', $token->id) }}" onsubmit="return confirm('Revoke {{ $token->name }}?')">
|
<x-confirm-dialog
|
||||||
@csrf @method('DELETE')
|
:name="'revoke-token-'.$token->id"
|
||||||
<button class="text-xs font-medium text-rose-600 hover:underline">Revoke</button>
|
title="Revoke API token?"
|
||||||
</form>
|
:message="'Revoke '.$token->name.'? Apps using this token will stop working.'"
|
||||||
|
:action="route('account.developers.destroy', $token->id)"
|
||||||
|
method="DELETE"
|
||||||
|
confirm-label="Revoke"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="text-xs font-medium text-rose-600 hover:underline">Revoke</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<p class="px-5 py-8 text-center text-sm text-slate-400">No tokens yet.</p>
|
<p class="px-5 py-8 text-center text-sm text-slate-400">No tokens yet.</p>
|
||||||
|
|||||||
@@ -22,13 +22,22 @@
|
|||||||
<div class="mt-4 rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2.5 text-sm text-emerald-800">
|
<div class="mt-4 rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2.5 text-sm text-emerald-800">
|
||||||
Linked mailbox: <span class="font-semibold">{{ $linkStatus['linked_mailbox'] }}</span>
|
Linked mailbox: <span class="font-semibold">{{ $linkStatus['linked_mailbox'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" action="{{ route('account.settings.mailbox-unlink') }}" class="mt-3"
|
<x-confirm-dialog
|
||||||
onsubmit="return confirm('Unlink this mailbox from your Ladill account? Ladill Mail will no longer open automatically with Ladill sign-in.')">
|
name="unlink-mailbox"
|
||||||
@csrf @method('DELETE')
|
title="Unlink mailbox?"
|
||||||
<button type="submit" class="rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 transition hover:border-slate-300 hover:bg-slate-50">
|
message="Unlink this mailbox from your Ladill account? Ladill Mail will no longer open automatically with Ladill sign-in."
|
||||||
Unlink mailbox
|
:action="route('account.settings.mailbox-unlink')"
|
||||||
</button>
|
method="DELETE"
|
||||||
</form>
|
confirm-label="Unlink"
|
||||||
|
variant="primary"
|
||||||
|
class="mt-3"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 transition hover:border-slate-300 hover:bg-slate-50">
|
||||||
|
Unlink mailbox
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
@elseif(($linkStatus['stage'] ?? '') === 'needs_domain')
|
@elseif(($linkStatus['stage'] ?? '') === 'needs_domain')
|
||||||
<p class="mt-3 text-sm text-amber-800">Add and verify an email domain first.</p>
|
<p class="mt-3 text-sm text-amber-800">Add and verify an email domain first.</p>
|
||||||
<a href="{{ route('email.domains.index') }}" class="mt-3 inline-flex items-center gap-1 text-sm font-semibold text-indigo-700 hover:text-indigo-800">
|
<a href="{{ route('email.domains.index') }}" class="mt-3 inline-flex items-center gap-1 text-sm font-semibold text-indigo-700 hover:text-indigo-800">
|
||||||
|
|||||||
@@ -64,10 +64,18 @@
|
|||||||
<option value="admin" @selected($member->role === 'admin')>Admin</option>
|
<option value="admin" @selected($member->role === 'admin')>Admin</option>
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="{{ route('account.team.destroy', $member) }}" onsubmit="return confirm('Remove {{ $member->email }}?')">
|
<x-confirm-dialog
|
||||||
@csrf @method('DELETE')
|
:name="'remove-member-'.$member->id"
|
||||||
<button class="text-xs font-medium text-rose-600 hover:underline">Remove</button>
|
title="Remove team member?"
|
||||||
</form>
|
:message="'Remove '.$member->email.' from this team?'"
|
||||||
|
:action="route('account.team.destroy', $member)"
|
||||||
|
method="DELETE"
|
||||||
|
confirm-label="Remove"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="text-xs font-medium text-rose-600 hover:underline">Remove</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
@else
|
@else
|
||||||
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-medium capitalize text-slate-600">{{ $member->role }}</span>
|
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-medium capitalize text-slate-600">{{ $member->role }}</span>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -54,9 +54,18 @@
|
|||||||
</div>
|
</div>
|
||||||
@endunless
|
@endunless
|
||||||
|
|
||||||
<form method="POST" action="{{ route('email.domains.destroy', $domain['id']) }}" class="mt-4" onsubmit="return confirm('Remove {{ $domain['domain'] }}?')">
|
<x-confirm-dialog
|
||||||
@csrf @method('DELETE')
|
:name="'remove-domain-'.$domain['id']"
|
||||||
<button class="text-xs font-medium text-rose-600 hover:underline">Remove domain</button>
|
title="Remove domain?"
|
||||||
</form>
|
:message="'Remove '.$domain['domain'].'?'"
|
||||||
|
:action="route('email.domains.destroy', $domain['id'])"
|
||||||
|
method="DELETE"
|
||||||
|
confirm-label="Remove domain"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="text-xs font-medium text-rose-600 hover:underline">Remove domain</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -74,10 +74,19 @@
|
|||||||
<div class="mt-4 rounded-2xl border border-rose-200 bg-white p-5">
|
<div class="mt-4 rounded-2xl border border-rose-200 bg-white p-5">
|
||||||
<h2 class="text-sm font-semibold text-rose-700">Delete mailbox</h2>
|
<h2 class="text-sm font-semibold text-rose-700">Delete mailbox</h2>
|
||||||
<p class="mt-0.5 text-xs text-slate-400">This permanently removes the mailbox and its email.</p>
|
<p class="mt-0.5 text-xs text-slate-400">This permanently removes the mailbox and its email.</p>
|
||||||
<form method="POST" action="{{ route('email.mailboxes.destroy', $mailbox['id']) }}" class="mt-3" onsubmit="return confirm('Delete {{ $mailbox['address'] }}? This cannot be undone.')">
|
<x-confirm-dialog
|
||||||
@csrf @method('DELETE')
|
:name="'delete-mailbox-'.$mailbox['id']"
|
||||||
<button class="rounded-lg bg-rose-600 px-4 py-2 text-sm font-semibold text-white hover:bg-rose-700">Delete mailbox</button>
|
title="Delete mailbox?"
|
||||||
</form>
|
:message="'Delete '.$mailbox['address'].'? This cannot be undone.'"
|
||||||
|
:action="route('email.mailboxes.destroy', $mailbox['id'])"
|
||||||
|
method="DELETE"
|
||||||
|
confirm-label="Delete mailbox"
|
||||||
|
class="mt-3"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="rounded-lg bg-rose-600 px-4 py-2 text-sm font-semibold text-white hover:bg-rose-700">Delete mailbox</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -42,10 +42,18 @@
|
|||||||
{{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }}
|
{{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" action="{{ route('account.developers.destroy', $token->id) }}" onsubmit="return confirm('Revoke {{ $token->name }}?')">
|
<x-confirm-dialog
|
||||||
@csrf @method('DELETE')
|
:name="'revoke-token-'.$token->id"
|
||||||
<button class="text-xs font-medium text-rose-600 hover:underline">Revoke</button>
|
title="Revoke API token?"
|
||||||
</form>
|
:message="'Revoke '.$token->name.'? Apps using this token will stop working.'"
|
||||||
|
:action="route('account.developers.destroy', $token->id)"
|
||||||
|
method="DELETE"
|
||||||
|
confirm-label="Revoke"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="text-xs font-medium text-rose-600 hover:underline">Revoke</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<p class="px-5 py-8 text-center text-sm text-slate-400">No tokens yet.</p>
|
<p class="px-5 py-8 text-center text-sm text-slate-400">No tokens yet.</p>
|
||||||
|
|||||||
@@ -62,10 +62,18 @@
|
|||||||
<option value="admin" @selected($member->role === 'admin')>Admin</option>
|
<option value="admin" @selected($member->role === 'admin')>Admin</option>
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="{{ route('account.team.destroy', $member) }}" onsubmit="return confirm('Remove {{ $member->email }}?')">
|
<x-confirm-dialog
|
||||||
@csrf @method('DELETE')
|
:name="'remove-member-'.$member->id"
|
||||||
<button class="text-xs font-medium text-rose-600 hover:underline">Remove</button>
|
title="Remove team member?"
|
||||||
</form>
|
:message="'Remove '.$member->email.' from this team?'"
|
||||||
|
:action="route('account.team.destroy', $member)"
|
||||||
|
method="DELETE"
|
||||||
|
confirm-label="Remove"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="text-xs font-medium text-rose-600 hover:underline">Remove</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
@else
|
@else
|
||||||
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-medium capitalize text-slate-600">{{ $member->role }}</span>
|
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-medium capitalize text-slate-600">{{ $member->role }}</span>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -359,14 +359,21 @@
|
|||||||
<span x-text="showCredentials ? 'Hide' : 'Show'"></span> Credentials
|
<span x-text="showCredentials ? 'Hide' : 'Show'"></span> Credentials
|
||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
<form action="{{ route('hosting.panel.apps.delete', [$account, $site]) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this {{ ucfirst($site->installed_app) }} installation? This will remove all application files and cannot be undone.');">
|
<x-confirm-dialog
|
||||||
@csrf
|
:name="'delete-app-'.$site->id"
|
||||||
@method('DELETE')
|
:title="'Delete '.ucfirst($site->installed_app).'?'"
|
||||||
<button type="submit" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
message="This will remove all application files and cannot be undone."
|
||||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
:action="route('hosting.panel.apps.delete', [$account, $site])"
|
||||||
Delete
|
method="DELETE"
|
||||||
</button>
|
confirm-label="Delete"
|
||||||
</form>
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
||||||
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -111,15 +111,24 @@
|
|||||||
<code class="text-sm font-mono text-slate-600 break-all">{{ $job['command'] }}</code>
|
<code class="text-sm font-mono text-slate-600 break-all">{{ $job['command'] }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right">
|
<td class="px-6 py-4 whitespace-nowrap text-right">
|
||||||
<form action="{{ route('hosting.panel.cron.delete', $account) }}" method="POST" onsubmit="return confirm('Delete this cron job?')">
|
<x-confirm-dialog
|
||||||
@csrf
|
:name="'delete-cron-'.$job['line_number']"
|
||||||
@method('DELETE')
|
title="Delete cron job?"
|
||||||
<input type="hidden" name="line_number" value="{{ $job['line_number'] }}">
|
message="Delete this cron job?"
|
||||||
<button type="submit" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
:action="route('hosting.panel.cron.delete', $account)"
|
||||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
method="DELETE"
|
||||||
Delete
|
confirm-label="Delete"
|
||||||
</button>
|
>
|
||||||
</form>
|
<x-slot:fields>
|
||||||
|
<input type="hidden" name="line_number" value="{{ $job['line_number'] }}">
|
||||||
|
</x-slot:fields>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
||||||
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|||||||
@@ -71,14 +71,21 @@
|
|||||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
|
||||||
Reset Password
|
Reset Password
|
||||||
</button>
|
</button>
|
||||||
<form action="{{ route('hosting.panel.databases.delete', [$account, $database]) }}" method="POST" onsubmit="return confirm('Are you sure? This will permanently delete the database and all its data.')">
|
<x-confirm-dialog
|
||||||
@csrf
|
:name="'delete-db-'.$database->id"
|
||||||
@method('DELETE')
|
title="Delete database?"
|
||||||
<button type="submit" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
message="This will permanently delete the database and all its data."
|
||||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
:action="route('hosting.panel.databases.delete', [$account, $database])"
|
||||||
Delete
|
method="DELETE"
|
||||||
</button>
|
confirm-label="Delete"
|
||||||
</form>
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
||||||
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -126,8 +133,8 @@
|
|||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script>
|
<script>
|
||||||
async function resetPassword(databaseId) {
|
async async function resetPassword(databaseId) {
|
||||||
if (!confirm('Reset the database password? You will need to update your application configuration.')) return;
|
if (!await window.ladillConfirm({ title: 'Reset password?', message: 'Reset the database password? You will need to update your application configuration.', confirmLabel: 'Reset', variant: 'primary' })) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`{{ url('hosting/panel/' . $account->id . '/databases') }}/${databaseId}/reset-password`, {
|
const res = await fetch(`{{ url('hosting/panel/' . $account->id . '/databases') }}/${databaseId}/reset-password`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -178,14 +178,21 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right">
|
<td class="px-6 py-4 whitespace-nowrap text-right">
|
||||||
@if((int) $account->user_id === (int) auth()->id())
|
@if((int) $account->user_id === (int) auth()->id())
|
||||||
<form action="{{ route('hosting.panel.domains.remove', [$account, $site]) }}" method="POST" onsubmit="return confirm('Remove this domain? The files will not be deleted.')">
|
<x-confirm-dialog
|
||||||
@csrf
|
:name="'remove-domain-'.$site->id"
|
||||||
@method('DELETE')
|
title="Remove domain?"
|
||||||
<button type="submit" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
message="Remove this domain? The files will not be deleted."
|
||||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
:action="route('hosting.panel.domains.remove', [$account, $site])"
|
||||||
Remove
|
method="DELETE"
|
||||||
</button>
|
confirm-label="Remove"
|
||||||
</form>
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
||||||
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
@else
|
@else
|
||||||
<span class="text-xs text-slate-400">Owner only</span>
|
<span class="text-xs text-slate-400">Owner only</span>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -418,9 +418,9 @@
|
|||||||
this.selected = event.target.checked ? [...this.allPaths] : [];
|
this.selected = event.target.checked ? [...this.allPaths] : [];
|
||||||
},
|
},
|
||||||
|
|
||||||
bulkAction(action) {
|
async async async bulkAction(action) {
|
||||||
if (action === 'delete') {
|
if (action === 'delete') {
|
||||||
if (!confirm('Delete ' + this.selected.length + ' item(s)? This cannot be undone.')) return;
|
if (!await this.$store.ladillConfirm.ask({ title: 'Delete items?', message: 'Delete ' + this.selected.length + ' item(s)? This cannot be undone.' })) return;
|
||||||
this.submitBulkDelete();
|
this.submitBulkDelete();
|
||||||
} else if (action === 'move' || action === 'copy') {
|
} else if (action === 'move' || action === 'copy') {
|
||||||
this.moveAction = action;
|
this.moveAction = action;
|
||||||
@@ -567,7 +567,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async deleteFile(path) {
|
async deleteFile(path) {
|
||||||
if (!confirm('Are you sure you want to delete this?')) return;
|
if (!await this.$store.ladillConfirm.ask({ title: 'Delete item?', message: 'Are you sure you want to delete this?' })) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch('{{ route("hosting.panel.files.delete", $account) }}', {
|
const res = await fetch('{{ route("hosting.panel.files.delete", $account) }}', {
|
||||||
@@ -583,7 +583,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async extractFile(path, name) {
|
async extractFile(path, name) {
|
||||||
if (!confirm('Extract "' + name + '" in the current directory?\n\nThis may take a while for large archives.')) return;
|
if (!await this.$store.ladillConfirm.ask({ title: 'Extract archive?', message: 'Extract "' + name + '" in the current directory?\n\nThis may take a while for large archives.', confirmLabel: 'Extract', variant: 'primary' })) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.extracting = true;
|
this.extracting = true;
|
||||||
this.extractFileName = name;
|
this.extractFileName = name;
|
||||||
|
|||||||
@@ -37,14 +37,25 @@
|
|||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form action="{{ route('hosting.panel.logs.clear', $account) }}" method="POST" onsubmit="return confirm('Clear this log file?')" class="ml-auto">
|
<x-confirm-dialog
|
||||||
@csrf
|
name="clear-log"
|
||||||
<input type="hidden" name="type" value="{{ $logType }}">
|
title="Clear log file?"
|
||||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg border border-red-200 bg-white px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50 transition">
|
message="Clear this log file? This cannot be undone."
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
:action="route('hosting.panel.logs.clear', $account)"
|
||||||
Clear Log
|
method="POST"
|
||||||
</button>
|
confirm-label="Clear log"
|
||||||
</form>
|
class="ml-auto"
|
||||||
|
>
|
||||||
|
<x-slot:fields>
|
||||||
|
<input type="hidden" name="type" value="{{ $logType }}">
|
||||||
|
</x-slot:fields>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="inline-flex items-center gap-2 rounded-lg border border-red-200 bg-white px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50 transition">
|
||||||
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
||||||
|
Clear Log
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||||
|
|||||||
@@ -80,13 +80,21 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
@if ($teamMembership->hasSshAccessKey())
|
@if ($teamMembership->hasSshAccessKey())
|
||||||
<form method="POST" action="{{ route('hosting.panel.settings.ssh-key.destroy', $account) }}" class="mt-3" onsubmit="return confirm('Remove your SFTP key access for this hosting account?');">
|
<x-confirm-dialog
|
||||||
@csrf
|
name="remove-sftp-key"
|
||||||
@method('DELETE')
|
title="Remove SFTP key?"
|
||||||
<button type="submit" class="inline-flex items-center rounded-lg border border-slate-300 px-5 py-2.5 text-sm font-medium text-slate-700 transition hover:border-slate-400 hover:text-slate-900">
|
message="Remove your SFTP key access for this hosting account?"
|
||||||
Remove SFTP Key
|
:action="route('hosting.panel.settings.ssh-key.destroy', $account)"
|
||||||
</button>
|
method="DELETE"
|
||||||
</form>
|
confirm-label="Remove key"
|
||||||
|
class="mt-3"
|
||||||
|
>
|
||||||
|
<x-slot:trigger>
|
||||||
|
<button type="button" class="inline-flex items-center rounded-lg border border-slate-300 px-5 py-2.5 text-sm font-medium text-slate-700 transition hover:border-slate-400 hover:text-slate-900">
|
||||||
|
Remove SFTP Key
|
||||||
|
</button>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-confirm-dialog>
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
<div class="mt-5 rounded-xl bg-slate-50 px-4 py-4 text-sm text-slate-600">
|
<div class="mt-5 rounded-xl bg-slate-50 px-4 py-4 text-sm text-slate-600">
|
||||||
|
|||||||
@@ -57,5 +57,6 @@
|
|||||||
@include('partials.afia')
|
@include('partials.afia')
|
||||||
@endauth
|
@endauth
|
||||||
@include('partials.sso-keepalive')
|
@include('partials.sso-keepalive')
|
||||||
|
@include('partials.confirm-prompt')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -56,5 +56,6 @@
|
|||||||
@include('partials.afia')
|
@include('partials.afia')
|
||||||
@endauth
|
@endauth
|
||||||
@include('partials.sso-keepalive')
|
@include('partials.sso-keepalive')
|
||||||
|
@include('partials.confirm-prompt')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<div
|
||||||
|
x-cloak
|
||||||
|
x-show="$store.ladillConfirm.open"
|
||||||
|
x-transition:enter="transition-opacity ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="transition-opacity ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
class="fixed inset-0 z-[60] flex items-end sm:items-center sm:justify-center"
|
||||||
|
style="background: rgba(0,0,0,0.45); backdrop-filter: blur(3px);"
|
||||||
|
@keydown.escape.window="$store.ladillConfirm.answer(false)"
|
||||||
|
>
|
||||||
|
<div class="absolute inset-0" @click="$store.ladillConfirm.answer(false)"></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-show="$store.ladillConfirm.open"
|
||||||
|
class="relative z-10 w-full max-h-[90vh] overflow-y-auto rounded-t-3xl bg-white shadow-2xl sm:mb-6 sm:max-h-[85vh] sm:max-w-md sm:rounded-2xl sm:border sm:border-gray-200"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="translate-y-full sm:translate-y-0 sm:opacity-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="translate-y-0 sm:opacity-100 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="translate-y-0 sm:opacity-100 sm:scale-100"
|
||||||
|
x-transition:leave-end="translate-y-full sm:translate-y-0 sm:opacity-0 sm:scale-95"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<div class="flex justify-center pb-1 pt-3 sm:hidden">
|
||||||
|
<div class="h-1 w-10 rounded-full bg-slate-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-5 pb-6 pt-2 sm:px-6 sm:pb-6 sm:pt-4">
|
||||||
|
<div class="flex flex-col items-center text-center sm:items-start sm:text-left">
|
||||||
|
<div class="flex h-12 w-12 items-center justify-center rounded-full"
|
||||||
|
:class="$store.ladillConfirm.variant === 'primary' ? 'bg-violet-100 text-violet-600' : 'bg-red-100 text-red-600'">
|
||||||
|
<template x-if="$store.ladillConfirm.variant === 'primary'">
|
||||||
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
<template x-if="$store.ladillConfirm.variant !== 'primary'">
|
||||||
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-4 text-lg font-semibold text-slate-900" x-text="$store.ladillConfirm.title"></h2>
|
||||||
|
<p x-show="$store.ladillConfirm.message" class="mt-2 whitespace-pre-line text-sm leading-relaxed text-slate-500" x-text="$store.ladillConfirm.message"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex flex-col-reverse gap-2.5 sm:flex-row sm:justify-end">
|
||||||
|
<button type="button"
|
||||||
|
@click="$store.ladillConfirm.answer(false)"
|
||||||
|
class="rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-50"
|
||||||
|
x-text="$store.ladillConfirm.cancelLabel">
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
@click="$store.ladillConfirm.answer(true)"
|
||||||
|
class="rounded-xl px-4 py-2.5 text-sm font-semibold text-white transition"
|
||||||
|
:class="$store.ladillConfirm.variant === 'primary' ? 'bg-violet-600 hover:bg-violet-700' : 'bg-red-600 hover:bg-red-700'"
|
||||||
|
x-text="$store.ladillConfirm.confirmLabel">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user