Update admin area views and controllers

This commit is contained in:
DariusIII
2026-07-13 11:36:42 +02:00
parent af014a9982
commit b5b6841eca
29 changed files with 896 additions and 316 deletions
@@ -199,23 +199,6 @@ Alpine.data('adminUserEdit', () => ({
}
}));
// Verify User Modal
Alpine.data('verifyUser', () => ({
open: false,
_form: null,
show(form) { this._form = form; this.open = true; },
hide() { this.open = false; this._form = null; },
submit() { if (this._form) this._form.submit(); this.hide(); },
init() {
const self = this;
window.showVerifyModal = function(e, form) { e.preventDefault(); self.show(form); };
window.hideVerifyModal = function() { self.hide(); };
window.submitVerifyForm = function() { self.submit(); };
}
}));
// Admin deleted users
Alpine.data('adminDeletedUsers', () => ({
allChecked: false,
@@ -0,0 +1,36 @@
/**
* Alpine.data('verifyUser') - Admin user verification confirmation modal.
*/
import Alpine from '@alpinejs/csp';
Alpine.data('verifyUser', () => ({
open: false,
_form: null,
show(form) {
this._form = form;
this.open = true;
},
hide() {
this.open = false;
this._form = null;
},
submit() {
if (this._form) {
this._form.submit();
}
this.hide();
},
init() {
window.showVerifyModal = (event, form) => {
event.preventDefault();
this.show(form);
};
window.hideVerifyModal = () => this.hide();
window.submitVerifyForm = () => this.submit();
},
}));
+1 -1
View File
@@ -57,7 +57,7 @@ const lazyComponentMap = {
'adminInvitations': () => import('./components/admin/features.js'), // same file
'adminRegexForm': () => import('./components/admin/features.js'), // same file
'tinyMceEditor': () => import('./components/admin/features.js'), // same file
'verifyUser': () => import('./components/admin/features.js'), // same file
'verifyUser': () => import('./components/admin/verify-user.js'),
'tmuxEdit': () => import('./components/admin/features.js'), // same file
};
+49 -74
View File
@@ -2,29 +2,15 @@
@section('content')
<div x-data="adminGroups" class="space-y-6" data-ajax-url="{{ url('/admin/ajax') }}" data-csrf-token="{{ csrf_token() }}">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
<!-- Header -->
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex flex-wrap justify-between items-center gap-3">
<h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-100">
<i class="fas fa-users mr-2"></i>{{ $title ?? 'Group List' }}
</h1>
<div class="flex flex-wrap gap-2">
<a href="{{ url('/admin/group-list-active') }}" class="px-3 py-2 bg-blue-600 dark:bg-blue-700 text-white text-sm rounded-lg hover:bg-blue-700">
<i class="fas fa-check-circle mr-1"></i>Active Groups
</a>
<a href="{{ url('/admin/group-list-inactive') }}" class="px-3 py-2 bg-gray-600 text-white text-sm rounded-lg hover:bg-gray-700">
<i class="fas fa-times-circle mr-1"></i>Inactive Groups
</a>
<a href="{{ url('/admin/group-list') }}" class="px-3 py-2 bg-indigo-600 text-white text-sm rounded-lg hover:bg-indigo-700">
<i class="fas fa-list mr-1"></i>All Groups
</a>
<a href="{{ url('/admin/group-bulk') }}" class="px-3 py-2 bg-green-600 dark:bg-green-700 text-white text-sm rounded-lg hover:bg-green-700">
<i class="fas fa-plus-circle mr-1"></i>Bulk Add
</a>
</div>
</div>
</div>
<x-admin.card>
<x-admin.page-header :title="$title ?? 'Group List'" icon="fas fa-users" subtitle="Activate, backfill, reset, and purge indexed Usenet groups.">
<x-slot:actions>
<x-admin.button :href="url('/admin/group-list-active')" icon="fas fa-check-circle">Active</x-admin.button>
<x-admin.button :href="url('/admin/group-list-inactive')" tone="gray" icon="fas fa-times-circle">Inactive</x-admin.button>
<x-admin.button :href="url('/admin/group-list')" tone="gray" icon="fas fa-list">All</x-admin.button>
<x-admin.button :href="url('/admin/group-bulk')" tone="success" icon="fas fa-plus-circle">Bulk Add</x-admin.button>
</x-slot:actions>
</x-admin.page-header>
<!-- Info Alert -->
<div class="px-6 py-4 bg-blue-50 dark:bg-blue-900/20 border-b border-blue-100 dark:border-blue-900">
@@ -44,8 +30,7 @@
@endif
@if($grouplist && $grouplist->count() > 0)
<!-- Search and Actions Bar -->
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<x-admin.action-bar>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<!-- Search Form -->
<div>
@@ -62,9 +47,7 @@
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 dark:placeholder-gray-400"
placeholder="Search for group...">
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg hover:bg-blue-700">
Go
</button>
<x-admin.button type="submit" icon="fas fa-search">Go</x-admin.button>
</div>
</form>
</div>
@@ -81,54 +64,54 @@
<span id="selection-counter" class="hidden text-sm text-gray-600 dark:text-gray-400 mr-2">
<span id="selected-count">0</span> selected
</span>
<button type="button"
<x-admin.button type="button"
id="reset-selected-btn"
@click="handleAction('show-reset-selected-modal')"
class="hidden px-3 py-2 bg-orange-600 dark:bg-orange-700 text-white text-sm rounded-lg hover:bg-orange-700 dark:hover:bg-orange-600">
<i class="fas fa-refresh mr-1"></i> Reset Selected
</button>
<button type="button"
tone="warning"
icon="fas fa-refresh"
class="hidden">
Reset Selected
</x-admin.button>
<x-admin.button type="button"
@click="handleAction('show-reset-modal')"
class="px-3 py-2 bg-yellow-600 text-white text-sm rounded-lg hover:bg-yellow-700">
<i class="fas fa-refresh mr-1"></i> Reset All
</button>
<button type="button"
tone="warning"
icon="fas fa-refresh">
Reset All
</x-admin.button>
<x-admin.button type="button"
@click="handleAction('show-purge-modal')"
class="px-3 py-2 bg-red-600 dark:bg-red-700 text-white text-sm rounded-lg hover:bg-red-700">
<i class="fas fa-trash mr-1"></i> Purge All
</button>
tone="danger"
icon="fas fa-trash">
Purge All
</x-admin.button>
</div>
</div>
</div>
</div>
</x-admin.action-bar>
<!-- Groups Table -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-12">
<x-admin.data-table sticky>
<x-slot:head>
<x-admin.th align="center" class="w-12">
<input type="checkbox"
id="select-all-groups"
x-model="allChecked"
@change="toggleAllCheckboxes()"
class="form-checkbox h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500 dark:bg-gray-700"
title="Select all groups on this page">
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Group</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">First Post</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Last Post</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Last Updated</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-32">Status</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-32">Backfill</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-24">Releases</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-24">Min Files</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-24">Min Size</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-32">Backfill Days</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-40">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
</x-admin.th>
<x-admin.th>Group</x-admin.th>
<x-admin.th>First Post</x-admin.th>
<x-admin.th>Last Post</x-admin.th>
<x-admin.th>Last Updated</x-admin.th>
<x-admin.th align="center" class="w-32">Status</x-admin.th>
<x-admin.th align="center" class="w-32">Backfill</x-admin.th>
<x-admin.th align="center" class="w-24">Releases</x-admin.th>
<x-admin.th align="center" class="w-24">Min Files</x-admin.th>
<x-admin.th align="center" class="w-24">Min Size</x-admin.th>
<x-admin.th align="center" class="w-32">Backfill Days</x-admin.th>
<x-admin.th align="center" class="w-40">Actions</x-admin.th>
</x-slot:head>
@foreach($grouplist as $group)
<tr id="grouprow-{{ $group->id }}" class="hover:bg-gray-50 dark:hover:bg-gray-700 group-row">
<td class="px-4 py-4 text-center">
@@ -243,9 +226,7 @@
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</x-admin.data-table>
<!-- Footer -->
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
@@ -259,16 +240,11 @@
</div>
</div>
@else
<div class="px-6 py-12 text-center">
<i class="fas fa-exclamation-triangle text-gray-400 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No groups available</h3>
<p class="text-gray-500 mb-4">No groups have been added yet.</p>
<a href="{{ url('/admin/group-bulk') }}" class="inline-flex items-center px-4 py-2 bg-green-600 dark:bg-green-700 text-white rounded-lg hover:bg-green-700">
<i class="fas fa-plus-circle mr-2"></i>Add Groups
</a>
</div>
<x-admin.empty-state icon="fas fa-exclamation-triangle" title="No groups available" message="No groups have been added yet.">
<x-admin.button :href="url('/admin/group-bulk')" tone="success" icon="fas fa-plus-circle" class="mt-4">Add Groups</x-admin.button>
</x-admin.empty-state>
@endif
</div>
</x-admin.card>
<!-- Reset All Modal -->
<div x-show="resetAllOpen"
@@ -385,4 +361,3 @@
</div>
@endsection
+8 -28
View File
@@ -5,7 +5,7 @@
<x-admin.card>
<x-admin.page-header :title="$title" icon="fas fa-file-lines" subtitle="Browse files from storage/logs and search inside the selected log." />
<x-admin.search-bar>
<x-admin.filter-panel>
@php
$clearSearchParams = array_filter([
'file' => $selectedFile !== '' ? $selectedFile : null,
@@ -50,31 +50,19 @@
</div>
<div class="lg:col-span-4 flex flex-wrap gap-2">
<button type="submit" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
<i class="fas fa-search mr-2"></i>Apply
</button>
<x-admin.button type="submit" icon="fas fa-search">Apply</x-admin.button>
@if($selectedFile !== '')
<a href="{{ route('admin.logs.index', $clearSearchParams) }}"
class="inline-flex items-center px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">
<i class="fas fa-eraser mr-2"></i>Clear Search
</a>
<x-admin.button :href="route('admin.logs.index', $clearSearchParams)" tone="gray" icon="fas fa-eraser">Clear Search</x-admin.button>
@endif
<a href="{{ route('admin.logs.index') }}"
class="inline-flex items-center px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition">
<i class="fas fa-rotate-left mr-2"></i>Reset
</a>
<x-admin.button :href="route('admin.logs.index')" tone="ghost" icon="fas fa-rotate-left">Reset</x-admin.button>
</div>
</form>
</x-admin.search-bar>
</x-admin.filter-panel>
@if(empty($availableLogs))
<div class="px-6 py-12 text-center">
<i class="fas fa-file-circle-xmark text-gray-400 dark:text-gray-600 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No log files found</h3>
<p class="text-gray-500 dark:text-gray-400">The `storage/logs` directory does not currently contain any readable files.</p>
</div>
<x-admin.empty-state icon="fas fa-file-circle-xmark" title="No log files found" message="The storage/logs directory does not currently contain any readable files." />
@elseif($selectedLog !== null)
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 text-sm">
@@ -120,11 +108,7 @@
<x-admin.pagination :paginator="$searchResults" />
@else
<div class="px-6 py-12 text-center">
<i class="fas fa-magnifying-glass text-gray-400 dark:text-gray-600 text-4xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No search results</h3>
<p class="text-gray-500 dark:text-gray-400">Try a different search term or clear the search to view the latest lines.</p>
</div>
<x-admin.empty-state icon="fas fa-magnifying-glass" title="No search results" message="Try a different search term or clear the search to view the latest lines." />
@endif
@elseif($tailView !== null)
<div class="px-6 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 text-sm text-gray-700 dark:text-gray-300">
@@ -136,11 +120,7 @@
</div>
@endif
@else
<div class="px-6 py-12 text-center">
<i class="fas fa-file-lines text-gray-400 dark:text-gray-600 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">Select a log file</h3>
<p class="text-gray-500 dark:text-gray-400">Choose a file from the dropdown above to inspect its contents.</p>
</div>
<x-admin.empty-state icon="fas fa-file-lines" title="Select a log file" message="Choose a file from the dropdown above to inspect its contents." />
@endif
</x-admin.card>
</div>
@@ -28,18 +28,9 @@
</div>
@endif
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
<!-- Header -->
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-200">
<i class="fas fa-flag mr-2 text-red-500"></i>{{ $title }}
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Manage release reports submitted by users</p>
</div>
<!-- Status Stats -->
<x-admin.card>
<x-admin.page-header :title="$title" icon="fas fa-flag" subtitle="Manage release reports submitted by users.">
<x-slot:actions>
<div class="flex flex-wrap gap-2">
<a href="{{ route('admin.release-reports', ['status' => 'pending']) }}"
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'pending' ? 'bg-yellow-500 text-white' : 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 hover:bg-yellow-200 dark:hover:bg-yellow-800' }}">
@@ -62,25 +53,27 @@
<i class="fas fa-list mr-1"></i> All ({{ $statusCounts['total'] }})
</a>
</div>
</div>
</div>
</x-slot:actions>
</x-admin.page-header>
<!-- Reports Table -->
@if($reportsList->count() > 0)
<form id="bulk-action-form" method="POST" action="{{ route('admin.release-reports.bulk') }}" @submit="validateBulkAction($event)">
@csrf
<div class="px-6 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3">
<x-admin.action-bar>
<div class="flex flex-wrap items-center gap-2">
<button type="button"
<x-admin.button type="button"
@click="selectAll()"
class="px-3 py-1.5 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-lg hover:bg-blue-200 dark:hover:bg-blue-800 transition text-sm font-medium">
<i class="fas fa-check-square mr-1"></i> Select All
</button>
<button type="button"
tone="gray"
icon="fas fa-check-square">
Select All
</x-admin.button>
<x-admin.button type="button"
@click="clearSelection()"
class="px-3 py-1.5 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition text-sm font-medium">
<i class="fas fa-square mr-1"></i> Clear Selection
</button>
tone="gray"
icon="fas fa-square">
Clear Selection
</x-admin.button>
<span class="text-sm text-gray-600 dark:text-gray-400">
<span x-text="selectedCount"></span> selected
</span>
@@ -95,29 +88,23 @@
<option value="revert">Revert to Reviewed</option>
<option value="delete">Delete Releases & Resolve</option>
</select>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition">
Apply
</button>
<x-admin.button type="submit" icon="fas fa-check">Apply</x-admin.button>
</div>
</div>
</x-admin.action-bar>
</form>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-10">Select</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Release</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Reporter</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Reason</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Response</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<x-admin.data-table sticky>
<x-slot:head>
<x-admin.th class="w-10">Select</x-admin.th>
<x-admin.th>ID</x-admin.th>
<x-admin.th>Release</x-admin.th>
<x-admin.th>Reporter</x-admin.th>
<x-admin.th>Reason</x-admin.th>
<x-admin.th>Response</x-admin.th>
<x-admin.th>Status</x-admin.th>
<x-admin.th>Date</x-admin.th>
<x-admin.th>Actions</x-admin.th>
</x-slot:head>
@foreach($reportsList as $report)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-4 py-4 whitespace-nowrap">
@@ -332,9 +319,7 @@
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</x-admin.data-table>
<!-- Pagination -->
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
@@ -354,7 +339,7 @@
</p>
</div>
@endif
</div>
</x-admin.card>
</div>
<!-- Report Description Modal -->
@@ -525,4 +510,3 @@
</div>
</div>
@endsection
+21 -21
View File
@@ -17,7 +17,7 @@
</div>
@endif
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<x-admin.filter-panel>
<form method="GET" action="{{ route('admin.release-list') }}" class="flex flex-col sm:flex-row gap-2">
<div class="relative flex-1">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -37,16 +37,16 @@
</option>
@endforeach
</select>
<button type="submit" class="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800">
<x-admin.button type="submit" icon="fas fa-filter">
Apply
</button>
</x-admin.button>
@if(request('search') || (request()->filled('category_id') && (int) request('category_id') !== -1))
<a href="{{ route('admin.release-list') }}" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 text-center">
<x-admin.button :href="route('admin.release-list')" tone="gray" icon="fas fa-eraser">
Clear
</a>
</x-admin.button>
@endif
</form>
</div>
</x-admin.filter-panel>
@if($releaselist->count() > 0)
<form id="bulk-category-form"
@@ -54,18 +54,20 @@
action="{{ route('admin.release-bulk-category') }}"
@submit="validateBulkAction($event)">
@csrf
<div class="px-6 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3">
<x-admin.action-bar>
<div class="flex flex-wrap items-center gap-2">
<button type="button"
<x-admin.button type="button"
@click="selectAll()"
class="px-3 py-1.5 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-lg hover:bg-blue-200 dark:hover:bg-blue-800 transition text-sm font-medium">
<i class="fas fa-check-square mr-1"></i> Select All
</button>
<button type="button"
tone="gray"
icon="fas fa-check-square">
Select All
</x-admin.button>
<x-admin.button type="button"
@click="clearSelection()"
class="px-3 py-1.5 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition text-sm font-medium">
<i class="fas fa-square mr-1"></i> Clear Selection
</button>
tone="gray"
icon="fas fa-square">
Clear Selection
</x-admin.button>
<span class="text-sm text-gray-600 dark:text-gray-400">
<span x-text="selectedCount"></span> selected
</span>
@@ -79,14 +81,12 @@
@endif
@endforeach
</select>
<button type="submit" class="px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition">
<i class="fas fa-tags mr-1"></i> Change Category
</button>
<x-admin.button type="submit" tone="success" icon="fas fa-tags">Change Category</x-admin.button>
</div>
</div>
</x-admin.action-bar>
</form>
<x-admin.data-table>
<x-admin.data-table sticky>
<x-slot:head>
<x-admin.th class="w-10">
<span class="sr-only">Select</span>
@@ -172,7 +172,7 @@
<x-admin.pagination :paginator="$releaselist" />
@else
<x-empty-state
<x-admin.empty-state
icon="fas fa-list"
title="No releases found"
message="{{ request('search') || (request()->filled('category_id') && (int) request('category_id') !== -1) ? 'No releases match your filters.' : 'No releases are currently available in the system.' }}"
+13 -22
View File
@@ -2,17 +2,15 @@
@section('content')
<div class="space-y-6" x-data="adminUserList">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
<x-admin.card>
<x-admin.page-header :title="$title" icon="fas fa-users">
<x-slot:actions>
<a href="{{ url('admin/user-edit?action=add') }}" class="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800">
<i class="fas fa-plus mr-2"></i>Add New User
</a>
<x-admin.button :href="url('admin/user-edit?action=add')" icon="fas fa-plus">Add New User</x-admin.button>
</x-slot:actions>
</x-admin.page-header>
<!-- Search Filters -->
<x-admin.search-bar>
<x-admin.filter-panel>
<form method="get" action="{{ url('admin/user-list') }}">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div>
@@ -83,15 +81,11 @@
</div>
</div>
<div class="mt-4 flex gap-2">
<button type="submit" class="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-md hover:bg-blue-700 dark:hover:bg-blue-800">
<i class="fas fa-search mr-2"></i>Filter
</button>
<a href="{{ url('admin/user-list') }}" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600">
<i class="fas fa-times mr-2"></i>Clear
</a>
<x-admin.button type="submit" icon="fas fa-search">Filter</x-admin.button>
<x-admin.button :href="url('admin/user-list')" tone="gray" icon="fas fa-times">Clear</x-admin.button>
</div>
</form>
</x-admin.search-bar>
</x-admin.filter-panel>
<!-- Validation Messages -->
@if($errors->any())
@@ -120,11 +114,12 @@
<option value="resend_verification">Resend Verification Email</option>
<option value="delete">Soft Delete Selected</option>
</select>
<button type="submit"
<x-admin.button type="submit"
id="bulkUserActionBtn"
class="w-full md:w-auto px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-md hover:bg-blue-700 dark:hover:bg-blue-800">
<i class="fas fa-check mr-2"></i>Apply
</button>
class="w-full md:w-auto"
icon="fas fa-check">
Apply
</x-admin.button>
<p class="text-xs text-gray-500 dark:text-gray-400">
Select non-admin active users below. Deleted users are managed from the Deleted Users page.
</p>
@@ -400,13 +395,9 @@
{{ $userlist->links() }}
</div>
@else
<div class="px-6 py-12 text-center">
<i class="fas fa-users text-gray-400 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No users found</h3>
<p class="text-gray-500">Try adjusting your search filters or add a new user.</p>
</div>
<x-admin.empty-state icon="fas fa-users" title="No users found" message="Try adjusting your search filters or add a new user." />
@endif
</div>
</x-admin.card>
</div>
<!-- Verify User Confirmation Modal (x-data mounts verifyUser so showVerifyModal/submitVerifyForm exist) -->
@@ -0,0 +1,6 @@
{{-- Dense action/filter bar for admin list pages --}}
<div {{ $attributes->merge(['class' => 'px-4 sm:px-6 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700']) }}>
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
{{ $slot }}
</div>
</div>
@@ -0,0 +1,23 @@
@props([
'tone' => 'gray',
'icon' => null,
])
@php
$classes = [
'blue' => 'bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-200',
'green' => 'bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-200',
'gray' => 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200',
'orange' => 'bg-orange-100 dark:bg-orange-900/40 text-orange-800 dark:text-orange-200',
'purple' => 'bg-purple-100 dark:bg-purple-900/40 text-purple-800 dark:text-purple-200',
'red' => 'bg-red-100 dark:bg-red-900/40 text-red-800 dark:text-red-200',
'yellow' => 'bg-yellow-100 dark:bg-yellow-900/40 text-yellow-800 dark:text-yellow-200',
][$tone] ?? 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200';
@endphp
<span {{ $attributes->merge(['class' => 'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-semibold '.$classes]) }}>
@if($icon)
<i class="{{ $icon }}" aria-hidden="true"></i>
@endif
{{ $slot }}
</span>
@@ -0,0 +1,35 @@
@props([
'href' => null,
'type' => 'button',
'tone' => 'primary',
'icon' => null,
])
@php
$classes = [
'danger' => 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
'ghost' => 'bg-transparent text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 focus:ring-gray-400',
'gray' => 'bg-gray-200 text-gray-800 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 focus:ring-gray-400',
'primary' => 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
'success' => 'bg-green-600 text-white hover:bg-green-700 focus:ring-green-500',
'warning' => 'bg-yellow-600 text-white hover:bg-yellow-700 focus:ring-yellow-500',
][$tone] ?? 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500';
$base = 'inline-flex min-h-9 items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 '.$classes;
@endphp
@if($href)
<a href="{{ $href }}" {{ $attributes->merge(['class' => $base]) }}>
@if($icon)
<i class="{{ $icon }}" aria-hidden="true"></i>
@endif
{{ $slot }}
</a>
@else
<button type="{{ $type }}" {{ $attributes->merge(['class' => $base]) }}>
@if($icon)
<i class="{{ $icon }}" aria-hidden="true"></i>
@endif
{{ $slot }}
</button>
@endif
@@ -3,7 +3,6 @@
'noPadding' => false,
])
<div {{ $attributes->merge(['class' => 'bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700']) }}>
<div {{ $attributes->merge(['class' => 'bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700']) }}>
{{ $slot }}
</div>
@@ -1,20 +1,29 @@
{{-- Admin data table with consistent thead styling --}}
@props([
'dense' => true,
'sticky' => false,
'striped' => false,
])
<div class="overflow-x-auto">
<table {{ $attributes->merge(['class' => 'min-w-full divide-y divide-gray-200 dark:divide-gray-700']) }}>
<div class="overflow-x-auto overscroll-x-contain">
<table {{ $attributes->merge(['class' => 'min-w-full table-auto divide-y divide-gray-200 dark:divide-gray-700']) }}>
@if(isset($head))
<thead class="bg-gray-50 dark:bg-gray-900">
<thead @class([
'bg-gray-50 dark:bg-gray-900',
'sticky top-0 z-10' => $sticky,
])>
<tr>
{{ $head }}
</tr>
</thead>
@endif
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<tbody @class([
'bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700',
'[&_tr:nth-child(even)]:bg-gray-50/50 dark:[&_tr:nth-child(even)]:bg-gray-900/25' => $striped,
'[&_td]:px-4 [&_td]:py-3 [&_td]:text-sm' => $dense,
'[&_td]:px-6 [&_td]:py-4' => ! $dense,
])>
{{ $slot }}
</tbody>
</table>
</div>
@@ -0,0 +1,18 @@
@props([
'icon' => 'fas fa-circle-info',
'title',
'message' => null,
])
<div {{ $attributes->merge(['class' => 'px-6 py-12 text-center']) }}>
<i class="{{ $icon }} mb-4 text-4xl text-gray-400 dark:text-gray-600" aria-hidden="true"></i>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ $title }}</h3>
@if($message)
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">{{ $message }}</p>
@endif
@if(trim($slot) !== '')
<div class="mt-4">
{{ $slot }}
</div>
@endif
</div>
@@ -0,0 +1,4 @@
{{-- Compact filter panel for admin list pages --}}
<div {{ $attributes->merge(['class' => 'px-4 sm:px-6 py-4 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700']) }}>
{{ $slot }}
</div>
@@ -13,7 +13,6 @@
$widthClass = $width ? "w-{$width}" : '';
@endphp
<th {{ $attributes->merge(['class' => "px-6 py-3 {$alignClass} text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider {$widthClass}"]) }}>
<th {{ $attributes->merge(['class' => "px-4 py-2.5 {$alignClass} text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide {$widthClass}"]) }}>
{{ $slot }}
</th>