From b0c781421c407cc9b17179b05c61c1f57d54dad6 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Fri, 10 Jul 2026 22:35:32 +0200 Subject: [PATCH] Update admin views and controllers --- .../Controllers/Admin/AdminUserController.php | 42 ++--- .../Requests/Admin/AdminUserListRequest.php | 66 ++++++++ app/Models/Release.php | 2 + app/Models/User.php | 88 ++++++++--- app/Providers/AppServiceProvider.php | 16 +- app/View/Composers/AdminDataComposer.php | 66 ++++++++ .../js/alpine/components/admin/features.js | 143 ----------------- .../js/alpine/components/admin/user-list.js | 146 ++++++++++++++++++ resources/js/alpine/lazy-loader.js | 2 +- resources/views/admin/users/index.blade.php | 44 +----- 10 files changed, 374 insertions(+), 241 deletions(-) create mode 100644 app/Http/Requests/Admin/AdminUserListRequest.php create mode 100644 app/View/Composers/AdminDataComposer.php create mode 100644 resources/js/alpine/components/admin/user-list.js diff --git a/app/Http/Controllers/Admin/AdminUserController.php b/app/Http/Controllers/Admin/AdminUserController.php index cb23105cd..d2cbfdaef 100644 --- a/app/Http/Controllers/Admin/AdminUserController.php +++ b/app/Http/Controllers/Admin/AdminUserController.php @@ -7,6 +7,7 @@ namespace App\Http\Controllers\Admin; use App\Enums\SignupError; use App\Enums\UserRole; use App\Http\Controllers\BasePageController; +use App\Http\Requests\Admin\AdminUserListRequest; use App\Models\Invitation; use App\Models\User; use App\Models\UserDownload; @@ -28,43 +29,23 @@ class AdminUserController extends BasePageController /** * @throws \Throwable */ - public function index(Request $request): View + public function index(AdminUserListRequest $request): View { $this->setAdminPrefs(); $meta_title = $title = 'User List'; - $roles = Role::pluck('name', 'id')->toArray(); + $roles = Role::query()->orderBy('name')->pluck('name', 'id')->toArray(); $ordering = getUserBrowseOrdering(); - $orderBy = $request->has('ob') && \in_array($request->input('ob'), $ordering, false) ? $request->input('ob') : ''; - $page = $request->has('page') && is_numeric($request->input('page')) ? $request->input('page') : 1; - $offset = ($page - 1) * config('nntmux.items_per_page'); + $orderBy = $request->orderBy(); + $variables = $request->filters(); - $variables = [ - 'username' => $request->has('username') ? $request->input('username') : '', - 'email' => $request->has('email') ? $request->input('email') : '', - 'host' => $request->has('host') ? $request->input('host') : '', - 'role' => $request->has('role') ? $request->input('role') : '', - 'verified' => in_array($request->input('verified'), ['0', '1'], true) ? $request->input('verified') : '', - 'created_from' => $request->has('created_from') ? $request->input('created_from') : '', - 'created_to' => $request->has('created_to') ? $request->input('created_to') : '', - ]; - - $result = User::getRange( - $offset, - (int) config('nntmux.items_per_page'), + $results = User::adminListPaginator( + $variables, $orderBy, - $variables['username'], - $variables['email'], - $variables['host'], - $variables['role'], - $variables['created_from'], - $variables['created_to'], - $variables['verified'] - ); - - $results = $this->paginate($result, User::getCount($variables['role'], $variables['username'], $variables['host'], $variables['email'], $variables['created_from'], $variables['created_to'], $variables['verified']), config('nntmux.items_per_page'), $page, $request->url(), $request->query()); + (int) config('nntmux.items_per_page'), + )->withQueryString(); // Build order by URLs $orderByUrls = []; @@ -392,8 +373,9 @@ class AdminUserController extends BasePageController $user->delete(); Cache::forget(AdminDashboardSnapshotService::CACHE_KEY); - // Redirect with username to display in notification - return redirect()->to('admin/user-list?deleted=1&username='.urlencode($username)); + return redirect() + ->route('admin.user-list') + ->with('success', 'User "'.$username.'" has been deleted successfully.'); } if ($request->has('redir')) { diff --git a/app/Http/Requests/Admin/AdminUserListRequest.php b/app/Http/Requests/Admin/AdminUserListRequest.php new file mode 100644 index 000000000..85ed0edf7 --- /dev/null +++ b/app/Http/Requests/Admin/AdminUserListRequest.php @@ -0,0 +1,66 @@ +user(); + + return $user instanceof User && $user->hasRole('Admin'); + } + + /** + * @return array> + */ + public function rules(): array + { + return [ + 'username' => ['nullable', 'string', 'max:255'], + 'email' => ['nullable', 'string', 'max:255'], + 'host' => ['nullable', 'string', 'max:255'], + 'role' => ['nullable', 'integer', 'exists:roles,id'], + 'verified' => ['nullable', 'in:0,1'], + 'created_from' => ['nullable', 'date_format:Y-m-d'], + 'created_to' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:created_from'], + 'ob' => ['nullable', 'string', 'in:'.implode(',', getUserBrowseOrdering())], + 'page' => ['nullable', 'integer', 'min:1'], + ]; + } + + /** + * @return array{username: string, email: string, host: string, role: string, verified: string, created_from: string, created_to: string} + */ + public function filters(): array + { + return [ + 'username' => $this->scalarInput('username'), + 'email' => $this->scalarInput('email'), + 'host' => $this->scalarInput('host'), + 'role' => $this->scalarInput('role'), + 'verified' => $this->scalarInput('verified'), + 'created_from' => $this->scalarInput('created_from'), + 'created_to' => $this->scalarInput('created_to'), + ]; + } + + public function orderBy(): string + { + $orderBy = $this->scalarInput('ob'); + + return \in_array($orderBy, getUserBrowseOrdering(), true) ? $orderBy : ''; + } + + private function scalarInput(string $key): string + { + $value = $this->input($key, ''); + + return is_scalar($value) ? trim((string) $value) : ''; + } +} diff --git a/app/Models/Release.php b/app/Models/Release.php index 0b67a4511..7b2d9bfe2 100644 --- a/app/Models/Release.php +++ b/app/Models/Release.php @@ -506,6 +506,8 @@ class Release extends Model 'video:id,title,tvdb,trakt,tvrage,tvmaze,source', 'video.tvInfo:videos_id,summary,image', 'episode:id,title,firstaired,se_complete', + 'releaseGroup:releases_id,groups_id', + 'releaseGroup.group:id,name', ]); if (is_array($guid)) { diff --git a/app/Models/User.php b/app/Models/User.php index 6b2055596..dd6510417 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -15,6 +15,7 @@ use Carbon\CarbonImmutable; use Illuminate\Auth\MustVerifyEmail as MustVerifyEmailTrait; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; @@ -755,20 +756,15 @@ final class User extends Authenticatable implements CanResetPasswordContract, Ha ?string $createdTo = '', ?string $verified = '', ): int { - return self::query() - ->withTrashed() - ->excludeSharing() - ->when($role, fn (Builder $q) => $q->where('roles_id', $role)) - ->when($username, fn (Builder $q) => $q->where('username', 'like', "%{$username}%")) - ->when($host, fn (Builder $q) => $q->where('host', 'like', "%{$host}%")) - ->when($email, fn (Builder $q) => $q->where('email', 'like', "%{$email}%")) - ->when($createdFrom, fn (Builder $q) => $q->where('created_at', '>=', "{$createdFrom} 00:00:00")) - ->when($createdTo, fn (Builder $q) => $q->where('created_at', '<=', "{$createdTo} 23:59:59")) - ->when($verified === '1', fn (Builder $q) => $q->where(function (Builder $verifiedQuery): void { - $verifiedQuery->where('verified', true) - ->orWhereNotNull('email_verified_at'); - })) - ->when($verified === '0', fn (Builder $q) => $q->where('verified', false)->whereNull('email_verified_at')) + return self::adminListBaseQuery([ + 'role' => (string) $role, + 'username' => (string) $username, + 'host' => (string) $host, + 'email' => (string) $email, + 'created_from' => (string) $createdFrom, + 'created_to' => (string) $createdTo, + 'verified' => (string) $verified, + ]) ->count(); } @@ -1355,6 +1351,57 @@ final class User extends Authenticatable implements CanResetPasswordContract, Ha ): Collection { $order = self::getBrowseOrder($orderBy); + return self::adminListBaseQuery([ + 'username' => (string) $userName, + 'email' => (string) $email, + 'host' => (string) $host, + 'role' => (string) $role, + 'created_from' => (string) $createdFrom, + 'created_to' => (string) $createdTo, + 'verified' => (string) $verified, + ]) + ->withCount([ + 'requests as daily_api_count' => fn (Builder $q) => $q->where('timestamp', '>', now()->subDay()), + 'downloads as daily_download_count' => fn (Builder $q) => $q->where('timestamp', '>', now()->subDay()), + ]) + ->with('roles:id,name') + ->orderBy($order[0], $order[1]) + ->when($start !== false, fn (Builder $q) => $q->offset($start)->limit($offset)) + ->get(); + } + + /** + * @param array{username?: string, email?: string, host?: string, role?: string, verified?: string, created_from?: string, created_to?: string} $filters + * @return LengthAwarePaginator + */ + public static function adminListPaginator(array $filters, string $orderBy, int $perPage): LengthAwarePaginator + { + $order = self::getBrowseOrder($orderBy); + + return self::adminListBaseQuery($filters) + ->withCount([ + 'requests as daily_api_count' => fn (Builder $q) => $q->where('timestamp', '>', now()->subDay()), + 'downloads as daily_download_count' => fn (Builder $q) => $q->where('timestamp', '>', now()->subDay()), + ]) + ->with('roles:id,name') + ->orderBy($order[0], $order[1]) + ->paginate($perPage); + } + + /** + * @param array{username?: string, email?: string, host?: string, role?: string, verified?: string, created_from?: string, created_to?: string} $filters + * @return Builder + */ + private static function adminListBaseQuery(array $filters): Builder + { + $username = $filters['username'] ?? ''; + $email = $filters['email'] ?? ''; + $host = $filters['host'] ?? ''; + $role = $filters['role'] ?? ''; + $verified = $filters['verified'] ?? ''; + $createdFrom = $filters['created_from'] ?? ''; + $createdTo = $filters['created_to'] ?? ''; + return self::query() ->withTrashed() ->select('users.*') @@ -1363,7 +1410,7 @@ final class User extends Authenticatable implements CanResetPasswordContract, Ha 'rolename' ) ->excludeSharing() - ->when($userName, fn (Builder $q) => $q->where('username', 'like', "%{$userName}%")) + ->when($username, fn (Builder $q) => $q->where('username', 'like', "%{$username}%")) ->when($email, fn (Builder $q) => $q->where('email', 'like', "%{$email}%")) ->when($host, fn (Builder $q) => $q->where('host', 'like', "%{$host}%")) ->when($role, fn (Builder $q) => $q->where('roles_id', $role)) @@ -1373,15 +1420,7 @@ final class User extends Authenticatable implements CanResetPasswordContract, Ha $verifiedQuery->where('verified', true) ->orWhereNotNull('email_verified_at'); })) - ->when($verified === '0', fn (Builder $q) => $q->where('verified', false)->whereNull('email_verified_at')) - ->withCount([ - 'requests as daily_api_count' => fn (Builder $q) => $q->where('timestamp', '>', now()->subDay()), - 'downloads as daily_download_count' => fn (Builder $q) => $q->where('timestamp', '>', now()->subDay()), - ]) - ->with('roles:id,name') - ->orderBy($order[0], $order[1]) - ->when($start !== false, fn (Builder $q) => $q->offset($start)->limit($offset)) - ->get(); + ->when($verified === '0', fn (Builder $q) => $q->where('verified', false)->whereNull('email_verified_at')); } /** @@ -1400,6 +1439,7 @@ final class User extends Authenticatable implements CanResetPasswordContract, Ha 'createdat' => 'created_at', 'lastlogin' => 'lastlogin', 'apiaccess' => 'apiaccess', + 'apirequests' => 'daily_api_count', 'grabs' => 'grabs', 'role' => 'rolename', 'rolechangedate' => 'rolechangedate', diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 29c1ced03..7f8d5cfd2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -29,6 +29,7 @@ use App\Observers\ReleaseObserver; use App\Observers\RolePromotionObserver; use App\Observers\SteamAppObserver; use App\Observers\VideoObserver; +use App\View\Composers\AdminDataComposer; use App\View\Composers\GlobalDataComposer; use Illuminate\Auth\Events\Login; use Illuminate\Pagination\Paginator; @@ -46,19 +47,22 @@ class AppServiceProvider extends ServiceProvider { Paginator::useTailwind(); - // Share global data with layouts and all admin views - // Admin child views need direct registration because @section blocks - // are evaluated before the layout composer runs - // Same for search.* so category menu data (e.g. parentcatlist) exists in @section('content') + // Share public-site global data with layouts that need menu/category data. + // search.* needs direct registration because @section blocks are evaluated + // before the layout composer runs. view()->composer([ 'layouts.main', - 'layouts.admin', 'layouts.guest', 'layouts.app', - 'admin.*', 'search.*', ], GlobalDataComposer::class); + // Admin pages use a leaner composer and do not need public-site menu data. + view()->composer([ + 'layouts.admin', + 'admin.*', + ], AdminDataComposer::class); + Gate::define('viewPulse', function (User $user) { return $user->hasRole('Admin'); }); diff --git a/app/View/Composers/AdminDataComposer.php b/app/View/Composers/AdminDataComposer.php new file mode 100644 index 000000000..a28ea2e61 --- /dev/null +++ b/app/View/Composers/AdminDataComposer.php @@ -0,0 +1,66 @@ +with([ + 'serverroot' => url('/'), + 'site' => $this->rememberWithCacheFallback('site_settings_array', self::CACHE_TTL, function () { + return Settings::query() + ->pluck('value', 'name') + ->map(fn ($value) => Settings::convertValue($value)) + ->all(); + }), + 'userdata' => $user, + 'loggedin' => $user !== null, + 'isadmin' => $isNntmuxUser && $user->hasRole('Admin'), + 'ismod' => $isNntmuxUser && $user->hasRole('Moderator'), + ]); + } + + /** + * @param callable(): mixed $callback + */ + private function rememberWithCacheFallback(string $key, int|\DateInterval $ttl, callable $callback): mixed + { + try { + if (Cache::has($key)) { + return Cache::get($key); + } + + $value = $callback(); + Cache::put($key, $value, $ttl); + + return $value; + } catch (\Throwable $e) { + if (config('app.debug')) { + Log::debug('AdminDataComposer cache bypassed: '.$e->getMessage()); + } + + return $callback(); + } + } +} diff --git a/resources/js/alpine/components/admin/features.js b/resources/js/alpine/components/admin/features.js index 9f32e4c37..afd3082b7 100644 --- a/resources/js/alpine/components/admin/features.js +++ b/resources/js/alpine/components/admin/features.js @@ -216,149 +216,6 @@ Alpine.data('verifyUser', () => ({ } })); -// User List Scroll Sync -Alpine.data('adminUserList', () => ({ - allChecked: false, - - init() { - const top = document.getElementById('topScroll'); - const bottom = document.getElementById('bottomScroll'); - const content = document.getElementById('topScrollContent'); - const table = bottom?.querySelector('table'); - if (top && bottom && content && table) { - const sync = () => { content.style.width = table.scrollWidth + 'px'; }; - sync(); - window.addEventListener('resize', sync); - top.addEventListener('scroll', function() { if (!top._syncing) { bottom._syncing = true; bottom.scrollLeft = top.scrollLeft; bottom._syncing = false; } }); - bottom.addEventListener('scroll', function() { if (!bottom._syncing) { top._syncing = true; top.scrollLeft = bottom.scrollLeft; bottom._syncing = false; } }); - } - - const selectAll = this.$el.querySelector('#selectAllUserList'); - const form = this.$el.querySelector('#bulkUserActionForm'); - - if (selectAll) { - selectAll.addEventListener('change', () => this.toggleAll()); - } - - this.$el.querySelectorAll('.user-list-checkbox').forEach(cb => { - cb.addEventListener('change', () => this.onCheckboxChange()); - }); - - if (form) { - form.addEventListener('submit', e => this.submitBulkAction(e)); - } - }, - - _checkboxes() { - return Array.from(this.$el.querySelectorAll('.user-list-checkbox:not(:disabled)')); - }, - - _checkedBoxes() { - return this._checkboxes().filter(cb => cb.checked); - }, - - _checkedUnverifiedBoxes() { - return this._checkedBoxes().filter(cb => cb.dataset.isVerified === '0'); - }, - - _setValidationError(message) { - const errEl = document.getElementById('validationError'); - const errMsg = document.getElementById('validationErrorMessage'); - if (errEl && errMsg) { - errMsg.textContent = message; - errEl.classList.remove('hidden'); - } - }, - - _clearValidationError() { - const errEl = document.getElementById('validationError'); - if (errEl) errEl.classList.add('hidden'); - }, - - toggleAll() { - const selectAll = this.$el.querySelector('#selectAllUserList'); - this._checkboxes().forEach(cb => { cb.checked = selectAll?.checked ?? false; }); - this.onCheckboxChange(); - }, - - onCheckboxChange() { - const selectAll = this.$el.querySelector('#selectAllUserList'); - const boxes = this._checkboxes(); - const checked = this._checkedBoxes(); - this.allChecked = boxes.length > 0 && checked.length === boxes.length; - - if (selectAll) { - selectAll.checked = this.allChecked; - selectAll.indeterminate = checked.length > 0 && checked.length < boxes.length; - } - }, - - submitBulkAction(e) { - e.preventDefault(); - this._clearValidationError(); - - const form = this.$el.querySelector('#bulkUserActionForm'); - const action = this.$el.querySelector('#bulkUserAction')?.value; - const checked = this._checkedBoxes(); - const checkedCount = checked.length; - - if (!action) { this._setValidationError('Please select an action.'); return; } - if (checkedCount === 0) { this._setValidationError('Please select at least one non-admin active user.'); return; } - - const actions = { - delete: { - title: 'Soft Delete Users', - actionText: 'soft-delete', - type: 'danger', - confirmText: 'Soft Delete' - }, - verify: { - title: 'Verify Users', - actionText: 'mark as verified', - type: 'success', - confirmText: 'Verify' - }, - resend_verification: { - title: 'Resend Verification Emails', - actionText: 'send account verification emails to', - type: 'warning', - confirmText: 'Send Emails' - } - }; - - const config = actions[action]; - if (!config) { this._setValidationError('Please select a valid action.'); return; } - - let targetCount = checkedCount; - let message = 'Are you sure you want to ' + config.actionText + ' ' + targetCount + ' user(s)?'; - - if (action === 'resend_verification') { - const unverifiedCount = this._checkedUnverifiedBoxes().length; - const skippedCount = checkedCount - unverifiedCount; - - if (unverifiedCount === 0) { - this._setValidationError('Please select at least one not verified user to resend verification email.'); - return; - } - - targetCount = unverifiedCount; - message = 'Are you sure you want to send account verification emails to ' + targetCount + ' not verified user(s)?'; - - if (skippedCount > 0) { - message += ' ' + skippedCount + ' already verified user(s) will be skipped.'; - } - } - - showConfirm({ - title: config.title, - message: message, - type: config.type, - confirmText: config.confirmText, - onConfirm: function() { if (form) form.submit(); } - }); - } -})); - // Admin deleted users Alpine.data('adminDeletedUsers', () => ({ allChecked: false, diff --git a/resources/js/alpine/components/admin/user-list.js b/resources/js/alpine/components/admin/user-list.js new file mode 100644 index 000000000..5f072d0fc --- /dev/null +++ b/resources/js/alpine/components/admin/user-list.js @@ -0,0 +1,146 @@ +/** + * Alpine.data('adminUserList') - Admin user list bulk selection and scroll sync. + */ +import Alpine from '@alpinejs/csp'; + +Alpine.data('adminUserList', () => ({ + allChecked: false, + + init() { + const top = document.getElementById('topScroll'); + const bottom = document.getElementById('bottomScroll'); + const content = document.getElementById('topScrollContent'); + const table = bottom?.querySelector('table'); + if (top && bottom && content && table) { + const sync = () => { content.style.width = table.scrollWidth + 'px'; }; + sync(); + window.addEventListener('resize', sync); + top.addEventListener('scroll', function() { if (!top._syncing) { bottom._syncing = true; bottom.scrollLeft = top.scrollLeft; bottom._syncing = false; } }); + bottom.addEventListener('scroll', function() { if (!bottom._syncing) { top._syncing = true; top.scrollLeft = bottom.scrollLeft; bottom._syncing = false; } }); + } + + const selectAll = this.$el.querySelector('#selectAllUserList'); + const form = this.$el.querySelector('#bulkUserActionForm'); + + if (selectAll) { + selectAll.addEventListener('change', () => this.toggleAll()); + } + + this.$el.querySelectorAll('.user-list-checkbox').forEach(cb => { + cb.addEventListener('change', () => this.onCheckboxChange()); + }); + + if (form) { + form.addEventListener('submit', e => this.submitBulkAction(e)); + } + }, + + _checkboxes() { + return Array.from(this.$el.querySelectorAll('.user-list-checkbox:not(:disabled)')); + }, + + _checkedBoxes() { + return this._checkboxes().filter(cb => cb.checked); + }, + + _checkedUnverifiedBoxes() { + return this._checkedBoxes().filter(cb => cb.dataset.isVerified === '0'); + }, + + _setValidationError(message) { + const errEl = document.getElementById('validationError'); + const errMsg = document.getElementById('validationErrorMessage'); + if (errEl && errMsg) { + errMsg.textContent = message; + errEl.classList.remove('hidden'); + } + }, + + _clearValidationError() { + const errEl = document.getElementById('validationError'); + if (errEl) errEl.classList.add('hidden'); + }, + + toggleAll() { + const selectAll = this.$el.querySelector('#selectAllUserList'); + this._checkboxes().forEach(cb => { cb.checked = selectAll?.checked ?? false; }); + this.onCheckboxChange(); + }, + + onCheckboxChange() { + const selectAll = this.$el.querySelector('#selectAllUserList'); + const boxes = this._checkboxes(); + const checked = this._checkedBoxes(); + this.allChecked = boxes.length > 0 && checked.length === boxes.length; + + if (selectAll) { + selectAll.checked = this.allChecked; + selectAll.indeterminate = checked.length > 0 && checked.length < boxes.length; + } + }, + + submitBulkAction(e) { + e.preventDefault(); + this._clearValidationError(); + + const form = this.$el.querySelector('#bulkUserActionForm'); + const action = this.$el.querySelector('#bulkUserAction')?.value; + const checked = this._checkedBoxes(); + const checkedCount = checked.length; + + if (!action) { this._setValidationError('Please select an action.'); return; } + if (checkedCount === 0) { this._setValidationError('Please select at least one non-admin active user.'); return; } + + const actions = { + delete: { + title: 'Soft Delete Users', + actionText: 'soft-delete', + type: 'danger', + confirmText: 'Soft Delete', + }, + verify: { + title: 'Verify Users', + actionText: 'mark as verified', + type: 'success', + confirmText: 'Verify', + }, + resend_verification: { + title: 'Resend Verification Emails', + actionText: 'send account verification emails to', + type: 'warning', + confirmText: 'Send Emails', + }, + }; + + const config = actions[action]; + if (!config) { this._setValidationError('Please select a valid action.'); return; } + + let targetCount = checkedCount; + let message = 'Are you sure you want to ' + config.actionText + ' ' + targetCount + ' user(s)?'; + + if (action === 'resend_verification') { + const unverifiedCount = this._checkedUnverifiedBoxes().length; + const skippedCount = checkedCount - unverifiedCount; + + if (unverifiedCount === 0) { + this._setValidationError('Please select at least one not verified user to resend verification email.'); + return; + } + + targetCount = unverifiedCount; + message = 'Are you sure you want to send account verification emails to ' + targetCount + ' not verified user(s)?'; + + if (skippedCount > 0) { + message += ' ' + skippedCount + ' already verified user(s) will be skipped.'; + } + } + + showConfirm({ + title: config.title, + message: message, + type: config.type, + confirmText: config.confirmText, + onConfirm: function() { if (form) form.submit(); }, + }); + }, +})); diff --git a/resources/js/alpine/lazy-loader.js b/resources/js/alpine/lazy-loader.js index dcbf5fc91..94c49c6a0 100644 --- a/resources/js/alpine/lazy-loader.js +++ b/resources/js/alpine/lazy-loader.js @@ -51,7 +51,7 @@ const lazyComponentMap = { 'adminGroups': () => import('./components/admin/groups.js'), 'adminFeatures': () => import('./components/admin/features.js'), 'adminUserEdit': () => import('./components/admin/features.js'), // same file - 'adminUserList': () => import('./components/admin/features.js'), // same file + 'adminUserList': () => import('./components/admin/user-list.js'), 'adminDeletedUsers': () => import('./components/admin/features.js'), // same file 'adminInvitations': () => import('./components/admin/features.js'), // same file 'adminRegexForm': () => import('./components/admin/features.js'), // same file diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php index 97639e602..01e31241a 100644 --- a/resources/views/admin/users/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -3,20 +3,16 @@ @section('content')
- -
-
-

- {{ $title }} -

+ + Add New User -
-
+ + -
+
@@ -95,34 +91,9 @@
-
- - - @if(request()->has('deleted') && request()->input('deleted') == 1) -
-

- - User "{{ request()->input('username') }}" has been deleted successfully. -

-
- @endif - - @if(session('success')) -
-

- {{ session('success') }} -

-
- @endif - - @if(session('error')) -
-

- {{ session('error') }} -

-
- @endif +
+ @if($errors->any())

@@ -489,4 +460,3 @@ @endsection {{-- Scripts moved to resources/js/csp-safe.js --}} -