[FEAT] 優化機台 API 通訊識別、補齊前端必填驗證、並配置 Demo 站隊列自動化部署 🦾🚀
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 49s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 49s
This commit is contained in:
@@ -74,7 +74,7 @@
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Machine Name') }}</label>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Machine Name') }} <span class="text-rose-500">*</span></label>
|
||||
<input type="text" name="name" value="{{ old('name', $machine->name) }}" class="luxury-input w-full" required>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-2 pb-20" x-data="{
|
||||
tab: '{{ $tab }}',
|
||||
showCreateMachineModal: false,
|
||||
showPhotoModal: false,
|
||||
showDetailDrawer: false,
|
||||
@@ -24,8 +25,10 @@
|
||||
this.maintenanceQrUrl = baseUrl.replace('SERIAL_NO', machine.serial_no);
|
||||
this.showMaintenanceQrModal = true;
|
||||
},
|
||||
openDetail(machine) {
|
||||
openDetail(machine, id, serial) {
|
||||
this.currentMachine = machine;
|
||||
window.activeMachineId = id || machine?.id;
|
||||
window.activeMachineSerial = serial || machine?.serial_no;
|
||||
this.showDetailDrawer = true;
|
||||
},
|
||||
openPhotoModal(machine) {
|
||||
@@ -60,8 +63,57 @@
|
||||
confirmDelete(action) {
|
||||
this.deleteFormAction = action;
|
||||
this.isDeleteConfirmOpen = true;
|
||||
},
|
||||
// API Token Management
|
||||
showApiToken: false,
|
||||
loadingRegenerate: false,
|
||||
isRegenerateConfirmOpen: false,
|
||||
copyToken(machine) {
|
||||
if (!machine?.api_token) return;
|
||||
navigator.clipboard.writeText(machine.api_token).then(() => {
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: '{{ __('API Token Copied') }}', type: 'success' } }));
|
||||
});
|
||||
},
|
||||
regenerateToken() {
|
||||
this.isRegenerateConfirmOpen = true;
|
||||
},
|
||||
executeRegeneration(id, serial) {
|
||||
// 僅使用機台序號 (Serial Number) 作為識別碼
|
||||
const targetSerial = serial || window.activeMachineSerial || id;
|
||||
|
||||
if (!targetSerial) {
|
||||
console.error('ExecuteRegeneration failed: No serial number available');
|
||||
window.dispatchEvent(new CustomEvent('toast', {
|
||||
detail: { message: '{{ __('Missing machine identification') }}', type: 'error' }
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('ExecuteRegeneration using serial:', targetSerial);
|
||||
this.isRegenerateConfirmOpen = false;
|
||||
this.loadingRegenerate = true;
|
||||
|
||||
fetch(`/admin/basic-settings/machines/${targetSerial}/regenerate-token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name=\'csrf-token\']').content,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(res => res.json()).then(data => {
|
||||
this.loadingRegenerate = false;
|
||||
if(data.success) {
|
||||
if (this.currentMachine) {
|
||||
this.currentMachine.api_token = data.api_token;
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: data.message, type: 'success' } }));
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loadingRegenerate = false;
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: '{{ __('Error processing request') }}', type: 'error' } }));
|
||||
});
|
||||
}
|
||||
}">
|
||||
}" @execute-regenerate.window="executeRegeneration($event.detail)">
|
||||
<!-- 1. Header Area -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
@@ -149,7 +201,7 @@
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@forelse($machines as $machine)
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6 cursor-pointer" @click="openDetail({{ $machine->toJson() }})">
|
||||
<td class="px-6 py-6 cursor-pointer" @click='openDetail({{ $machine->toJson() }}, {{ $machine->id }}, "{{ $machine->serial_no }}")'>
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden">
|
||||
@@ -175,7 +227,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6" @click="openDetail({{ $machine->toJson() }})">
|
||||
<td class="px-6 py-6 cursor-pointer" @click='openDetail({{ $machine->toJson() }}, {{ $machine->id }}, "{{ $machine->serial_no }}")'>
|
||||
<span
|
||||
class="text-xs font-bold text-slate-600 dark:text-slate-300 uppercase tracking-widest">
|
||||
{{ $machine->machineModel->name ?? '--' }}
|
||||
@@ -183,7 +235,7 @@
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
@php
|
||||
$isOnline = $machine->last_heartbeat_at && $machine->last_heartbeat_at->diffInMinutes() < 5;
|
||||
$isOnline = $machine->last_heartbeat_at && $machine->last_heartbeat_at->diffInSeconds() < 30;
|
||||
@endphp <div class="flex items-center gap-2.5">
|
||||
<div class="relative flex h-2.5 w-2.5">
|
||||
@if($isOnline)
|
||||
@@ -396,22 +448,25 @@
|
||||
<div class="px-8 py-8 space-y-6">
|
||||
<div>
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
|
||||
__('Machine Name') }}</label>
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Machine Name') }} <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="name" required class="luxury-input w-full"
|
||||
placeholder="{{ __('Enter machine name') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
|
||||
__('Serial No') }}</label>
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Serial No') }} <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="serial_no" required class="luxury-input w-full"
|
||||
placeholder="{{ __('Enter serial number') }}">
|
||||
</div>
|
||||
<div class="relative z-20">
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
|
||||
__('Owner') }}</label>
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Owner') }}
|
||||
</label>
|
||||
<x-searchable-select name="company_id" required :placeholder="__('Select Owner')">
|
||||
@foreach($companies as $company)
|
||||
<option value="{{ $company->id }}" data-title="{{ $company->name }}{{ $company->code ? ' (' . $company->code . ')' : '' }}">
|
||||
@@ -422,15 +477,17 @@
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
|
||||
__('Location') }}</label>
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Location') }}
|
||||
</label>
|
||||
<input type="text" name="location" class="luxury-input w-full"
|
||||
placeholder="{{ __('Enter machine location') }}">
|
||||
</div>
|
||||
<div class="relative z-10">
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
|
||||
__('Model') }}</label>
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Model') }}
|
||||
</label>
|
||||
<x-searchable-select name="machine_model_id" required :placeholder="__('Select Model')">
|
||||
@foreach($models as $model)
|
||||
<option value="{{ $model->id }}">{{ $model->name }}</option>
|
||||
@@ -439,8 +496,9 @@
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
|
||||
__('Machine Images') }} ({{ __('Max 3') }})</label>
|
||||
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">
|
||||
{{ __('Machine Images') }} ({{ __('Max 3') }})
|
||||
</label>
|
||||
<label
|
||||
class="relative flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-slate-200 dark:border-slate-800 rounded-2xl cursor-pointer bg-slate-50/50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-800/80 transition-all group">
|
||||
<template x-if="selectedFileCount === 0">
|
||||
@@ -772,7 +830,7 @@
|
||||
|
||||
<div class="p-10 flex flex-col items-center gap-6">
|
||||
<div class="p-4 bg-white rounded-3xl shadow-xl border border-slate-100">
|
||||
<img :src="'https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=' + encodeURIComponent(maintenanceQrUrl)"
|
||||
<img :src="'{{ route('admin.basic-settings.qr-code') }}?data=' + encodeURIComponent(maintenanceQrUrl)"
|
||||
class="w-48 h-48"
|
||||
alt="{{ __('Maintenance QR Code') }}">
|
||||
</div>
|
||||
@@ -893,11 +951,38 @@
|
||||
<span class="text-xs font-black text-slate-700 dark:text-slate-300"
|
||||
x-text="currentMachine?.card_reader_no || '--'"></span>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between p-2 border-b border-slate-50 dark:border-white/5">
|
||||
<span class="text-xs font-bold text-slate-500">{{ __('API Token') }}</span>
|
||||
<span class="text-[10px] font-mono text-slate-400 truncate max-w-[150px]"
|
||||
x-text="currentMachine?.api_token || '--'"></span>
|
||||
<div class="flex flex-col gap-3 p-3 mt-1 bg-slate-50 dark:bg-slate-800/40 rounded-xl border border-slate-100 dark:border-slate-700/50 relative">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[11px] font-black text-slate-500 uppercase tracking-widest">{{ __('API Token') }}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<template x-if="currentMachine?.api_token">
|
||||
<div class="flex items-center gap-1">
|
||||
<button @click="showApiToken = !showApiToken"
|
||||
class="p-1.5 rounded-lg text-slate-400 hover:text-cyan-500 hover:bg-cyan-50 dark:hover:bg-cyan-900/40 transition-all font-bold"
|
||||
:title="showApiToken ? '{{ __('Hide') }}' : '{{ __('Show') }}'">
|
||||
<svg x-show="!showApiToken" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
<svg x-show="showApiToken" x-cloak class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>
|
||||
</button>
|
||||
<button @click="copyToken(currentMachine)"
|
||||
class="p-1.5 rounded-lg text-slate-400 hover:text-emerald-500 hover:bg-emerald-50 dark:hover:bg-emerald-900/40 transition-all font-bold"
|
||||
title="{{ __('Copy') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<button @click="regenerateToken()" :disabled="loadingRegenerate"
|
||||
class="ml-2 px-2.5 py-1.5 rounded-lg bg-rose-50 dark:bg-rose-500/10 text-rose-500 hover:bg-rose-100 dark:hover:bg-rose-500/20 text-[10px] font-black uppercase tracking-widest transition-all disabled:opacity-50 flex items-center gap-1.5 border border-rose-100 dark:border-rose-500/20"
|
||||
title="{{ __('Regenerate') }}">
|
||||
<svg x-show="loadingRegenerate" class="animate-spin w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
||||
<svg x-show="!loadingRegenerate" class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
<span>{{ __('Regenerate') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-900/50 rounded-lg border border-slate-200 dark:border-slate-700/50 p-2.5 overflow-x-auto custom-scrollbar">
|
||||
<span class="text-xs font-mono font-bold tracking-[0.1em] text-cyan-600 dark:text-cyan-400 select-all block whitespace-nowrap min-w-full"
|
||||
x-text="currentMachine?.api_token ? (showApiToken ? currentMachine.api_token : '•'.repeat(40)) : '{{ __('None') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -926,6 +1011,16 @@
|
||||
<!-- Global Delete Confirm Modal -->
|
||||
<x-delete-confirm-modal />
|
||||
|
||||
<x-confirm-modal
|
||||
alpine-var="isRegenerateConfirmOpen"
|
||||
confirm-action="isRegenerateConfirmOpen = false; window.dispatchEvent(new CustomEvent('execute-regenerate', { detail: window.activeMachineSerial || window.activeMachineId }))"
|
||||
icon-type="warning"
|
||||
confirm-color="sky"
|
||||
:title="__('Are you sure?')"
|
||||
:message="__('Regenerating the token will disconnect the physical machine until it is updated. Continue?')"
|
||||
:confirm-text="__('Yes, regenerate')"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
91
resources/views/components/confirm-modal.blade.php
Normal file
91
resources/views/components/confirm-modal.blade.php
Normal file
@@ -0,0 +1,91 @@
|
||||
@props([
|
||||
'alpineVar' => 'isOpen',
|
||||
'confirmAction' => 'confirm()', // The JS expression to run on confirm
|
||||
'iconType' => 'warning', // warning, info, danger, success
|
||||
'title' => __('Confirm'),
|
||||
'message' => __('Are you sure?'),
|
||||
'confirmText' => __('Confirm'),
|
||||
'cancelText' => __('Cancel'),
|
||||
'confirmColor' => 'sky', // sky, rose, amber, emerald
|
||||
])
|
||||
|
||||
@php
|
||||
$iconClasses = [
|
||||
'warning' => 'bg-amber-100 dark:bg-amber-500/10 text-amber-600 dark:text-amber-400',
|
||||
'danger' => 'bg-rose-100 dark:bg-rose-500/10 text-rose-600 dark:text-rose-400',
|
||||
'info' => 'bg-sky-100 dark:bg-sky-500/10 text-sky-600 dark:text-sky-400',
|
||||
'success' => 'bg-emerald-100 dark:bg-emerald-500/10 text-emerald-600 dark:text-emerald-400',
|
||||
][$iconType];
|
||||
|
||||
$btnClasses = [
|
||||
'sky' => 'bg-sky-500 hover:bg-sky-600 shadow-sky-200',
|
||||
'rose' => 'bg-rose-500 hover:bg-rose-600 shadow-rose-200',
|
||||
'amber' => 'bg-amber-500 hover:bg-amber-600 shadow-amber-200',
|
||||
'emerald' => 'bg-emerald-500 hover:bg-emerald-600 shadow-emerald-200',
|
||||
][$confirmColor];
|
||||
@endphp
|
||||
|
||||
<template x-teleport="body">
|
||||
<div x-show="{{ $alpineVar }}" class="fixed inset-0 z-[200] overflow-y-auto" x-cloak>
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div x-show="{{ $alpineVar }}" x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity bg-slate-900/60 backdrop-blur-sm"
|
||||
@click="{{ $alpineVar }} = false"></div>
|
||||
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
|
||||
|
||||
<div x-show="{{ $alpineVar }}" x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white dark:bg-slate-900 rounded-3xl shadow-2xl sm:my-8 sm:align-middle sm:max-w-md sm:w-full sm:p-8 border border-slate-100 dark:border-slate-800 relative z-10">
|
||||
|
||||
<div class="sm:flex sm:items-start text-center sm:text-left">
|
||||
<div class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto rounded-2xl sm:mx-0 sm:h-12 sm:w-12 {{ $iconClasses }}">
|
||||
@if($iconType === 'warning')
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
||||
</svg>
|
||||
@elseif($iconType === 'danger')
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
@elseif($iconType === 'info')
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
@elseif($iconType === 'success')
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-3 sm:mt-0 sm:ml-6">
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white leading-6 tracking-tight font-display uppercase">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
<div class="mt-4">
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 leading-relaxed">
|
||||
{{ $message }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 sm:mt-10 sm:flex sm:flex-row-reverse gap-3">
|
||||
<button type="button" @click="{{ $confirmAction }}"
|
||||
class="inline-flex justify-center w-full px-6 py-3 text-sm font-black text-white transition-all rounded-xl shadow-lg dark:shadow-none hover:scale-[1.02] active:scale-[0.98] sm:w-auto uppercase tracking-widest font-display {{ $btnClasses }}">
|
||||
{{ $confirmText }}
|
||||
</button>
|
||||
<button type="button" @click="{{ $alpineVar }} = false"
|
||||
class="inline-flex justify-center w-full px-6 py-3 mt-3 text-sm font-black text-slate-700 dark:text-slate-200 transition-all bg-slate-100 dark:bg-slate-800 rounded-xl hover:bg-slate-200 dark:hover:bg-slate-700 sm:mt-0 sm:w-auto uppercase tracking-widest font-display">
|
||||
{{ $cancelText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user