- Installed p7h/nas-file-manager package via private VCS repo - Published config/nas-file-manager.php with super_admin middleware restriction - Added NAS env vars to .env.example - Created admin/nas-storage page with connection info panel and file browser widget - Added NAS Storage link to admin sidebar (super_admin only) - Added SuperAdminController@nasStorage method and admin.nas-storage route - Includes all accumulated branch changes: profile wall, 2FA, audit logs, settings panel, country/phone/timezone components, posts, slideshow, playlist shares, video downloads/shares, comment likes, notifications, social links, and more Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
245 lines
11 KiB
PHP
245 lines
11 KiB
PHP
@extends('admin.layout')
|
||
|
||
@section('title', 'Users')
|
||
|
||
@section('content')
|
||
|
||
{{-- ── Page header ──────────────────────────────────────────────────── --}}
|
||
<div class="adm-page-header">
|
||
<h1 class="adm-page-title"><i class="bi bi-people"></i> Users</h1>
|
||
</div>
|
||
|
||
{{-- ── Alerts ───────────────────────────────────────────────────────── --}}
|
||
@if(session('success'))
|
||
<div class="adm-alert adm-alert-success">
|
||
<i class="bi bi-check-circle-fill"></i>
|
||
<span>{{ session('success') }}</span>
|
||
<button class="adm-alert-close"><i class="bi bi-x"></i></button>
|
||
</div>
|
||
@endif
|
||
@if(session('error'))
|
||
<div class="adm-alert adm-alert-error">
|
||
<i class="bi bi-exclamation-circle-fill"></i>
|
||
<span>{{ session('error') }}</span>
|
||
<button class="adm-alert-close"><i class="bi bi-x"></i></button>
|
||
</div>
|
||
@endif
|
||
|
||
{{-- ── Filter card ──────────────────────────────────────────────────── --}}
|
||
<div class="adm-card">
|
||
<div class="adm-card-body" style="padding:16px 20px;">
|
||
<form method="GET" action="{{ route('admin.users') }}" class="adm-filter-form">
|
||
<div class="adm-filter-search">
|
||
<i class="bi bi-search"></i>
|
||
<input type="text" name="search" class="adm-input"
|
||
placeholder="Search name or email…"
|
||
value="{{ request('search') }}" autocomplete="off">
|
||
</div>
|
||
|
||
<select name="role" class="adm-select">
|
||
<option value="">All Roles</option>
|
||
<option value="user" {{ request('role') === 'user' ? 'selected' : '' }}>User</option>
|
||
<option value="admin" {{ request('role') === 'admin' ? 'selected' : '' }}>Admin</option>
|
||
<option value="super_admin" {{ request('role') === 'super_admin' ? 'selected' : '' }}>Super Admin</option>
|
||
</select>
|
||
|
||
<select name="sort" class="adm-select">
|
||
<option value="latest" {{ request('sort', 'latest') === 'latest' ? 'selected' : '' }}>Newest first</option>
|
||
<option value="oldest" {{ request('sort') === 'oldest' ? 'selected' : '' }}>Oldest first</option>
|
||
<option value="name_asc" {{ request('sort') === 'name_asc' ? 'selected' : '' }}>Name A–Z</option>
|
||
<option value="name_desc" {{ request('sort') === 'name_desc' ? 'selected' : '' }}>Name Z–A</option>
|
||
</select>
|
||
|
||
<button type="submit" class="adm-btn adm-btn-primary">
|
||
<i class="bi bi-funnel"></i> Filter
|
||
</button>
|
||
@if(request()->hasAny(['search','role','sort']))
|
||
<a href="{{ route('admin.users') }}" class="adm-btn">
|
||
<i class="bi bi-x-lg"></i> Clear
|
||
</a>
|
||
@endif
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── Users table ──────────────────────────────────────────────────── --}}
|
||
<div class="adm-card">
|
||
<div class="adm-card-header">
|
||
<div class="adm-card-title">
|
||
<i class="bi bi-people"></i>
|
||
All Users
|
||
<span class="adm-badge adm-badge-user">{{ $users->total() ?? $users->count() }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="adm-table-wrap">
|
||
<table class="adm-table">
|
||
<thead>
|
||
<tr>
|
||
<th>User</th>
|
||
<th>Role</th>
|
||
<th>Verified</th>
|
||
<th>Videos</th>
|
||
<th>Joined</th>
|
||
<th style="width:80px; text-align:right;">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse($users as $user)
|
||
<tr>
|
||
{{-- User cell --}}
|
||
<td>
|
||
<div class="adm-user-cell">
|
||
<img src="{{ $user->avatar_url }}" alt="{{ $user->name }}">
|
||
<div>
|
||
<div style="display:flex;align-items:center;gap:4px;">
|
||
<span class="adm-user-cell-name">{{ $user->name }}</span>
|
||
@if($user->id === auth()->id())
|
||
<span class="adm-user-cell-you">you</span>
|
||
@endif
|
||
</div>
|
||
<div class="adm-user-cell-email">{{ $user->email }}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
{{-- Role --}}
|
||
<td>
|
||
@if($user->role === 'super_admin')
|
||
<span class="adm-badge adm-badge-superadmin"><i class="bi bi-shield-fill"></i> Super Admin</span>
|
||
@elseif($user->role === 'admin')
|
||
<span class="adm-badge adm-badge-admin"><i class="bi bi-person-badge"></i> Admin</span>
|
||
@else
|
||
<span class="adm-badge adm-badge-user"><i class="bi bi-person"></i> User</span>
|
||
@endif
|
||
</td>
|
||
|
||
{{-- Verified --}}
|
||
<td>
|
||
@if($user->email_verified_at)
|
||
<span class="adm-badge adm-badge-verified"><i class="bi bi-check-circle-fill"></i> Verified</span>
|
||
@else
|
||
<span class="adm-badge adm-badge-unverified"><i class="bi bi-clock"></i> Pending</span>
|
||
@endif
|
||
</td>
|
||
|
||
{{-- Videos --}}
|
||
<td>
|
||
<a href="{{ route('channel', $user->channel) }}" target="_blank"
|
||
class="text-dim" style="text-decoration:none; font-size:13px;">
|
||
{{ $user->videos->count() }}
|
||
<i class="bi bi-box-arrow-up-right" style="font-size:10px; opacity:.5; margin-left:2px;"></i>
|
||
</a>
|
||
</td>
|
||
|
||
{{-- Joined --}}
|
||
<td class="text-muted-sm">{{ $user->created_at->format('M d, Y') }}</td>
|
||
|
||
{{-- Actions --}}
|
||
<td>
|
||
<div class="adm-row-actions" style="justify-content:flex-end;">
|
||
@if(!$user->email_verified_at)
|
||
<form method="POST" action="{{ route('admin.users.verify', $user->id) }}" style="display:inline;">
|
||
@csrf
|
||
<button type="submit" class="adm-btn adm-btn-sm adm-btn-verify" title="Manually verify account">
|
||
<i class="bi bi-patch-check-fill"></i>
|
||
</button>
|
||
</form>
|
||
@endif
|
||
@if($user->id !== auth()->id() && !$user->isSuperAdmin())
|
||
<form method="POST" action="{{ route('admin.users.impersonate', $user->id) }}" style="display:inline;">
|
||
@csrf
|
||
<button type="submit" class="adm-btn adm-btn-sm adm-btn-impersonate" title="Impersonate user">
|
||
<i class="bi bi-person-fill-gear"></i>
|
||
</button>
|
||
</form>
|
||
@endif
|
||
<a href="{{ route('admin.users.edit', $user->id) }}"
|
||
class="adm-btn adm-btn-sm" title="Edit user">
|
||
<i class="bi bi-pencil"></i>
|
||
</a>
|
||
@if($user->id !== auth()->id())
|
||
<button type="button"
|
||
class="adm-btn adm-btn-sm adm-btn-danger"
|
||
title="Delete user"
|
||
onclick="openDeleteDialog({{ $user->id }}, '{{ addslashes($user->name) }}')">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
@endif
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="6">
|
||
<div class="empty-state">
|
||
<i class="bi bi-people"></i>
|
||
<p>No users found</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{{-- Pagination --}}
|
||
@if($users instanceof \Illuminate\Pagination\LengthAwarePaginator && $users->hasPages())
|
||
<div style="padding:16px 20px; border-top:1px solid var(--border);">
|
||
{{ $users->onEachSide(1)->links() }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- ── Delete confirmation dialog ───────────────────────────────────── --}}
|
||
<div class="adm-dialog-overlay" id="deleteDialog">
|
||
<div class="adm-dialog">
|
||
<div class="adm-dialog-header">
|
||
<div class="adm-dialog-title">
|
||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||
Delete User
|
||
</div>
|
||
<button type="button" class="adm-btn adm-btn-sm" onclick="closeDeleteDialog()" style="border:none;background:none;color:var(--text-2);">
|
||
<i class="bi bi-x-lg"></i>
|
||
</button>
|
||
</div>
|
||
<div class="adm-dialog-body">
|
||
<p>You are about to permanently delete <strong id="dlgUserName"></strong>.</p>
|
||
<div class="adm-dialog-warning">
|
||
<i class="bi bi-info-circle-fill" style="flex-shrink:0;margin-top:1px;"></i>
|
||
All videos uploaded by this user will also be deleted. This cannot be undone.
|
||
</div>
|
||
</div>
|
||
<div class="adm-dialog-footer">
|
||
<button type="button" class="adm-btn" onclick="closeDeleteDialog()">Cancel</button>
|
||
<form id="deleteForm" method="POST">
|
||
@csrf @method('DELETE')
|
||
<button type="submit" class="adm-btn adm-btn-danger" style="height:36px;">
|
||
<i class="bi bi-trash"></i> Delete User
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@endsection
|
||
|
||
@section('scripts')
|
||
<script>
|
||
function openDeleteDialog(userId, userName) {
|
||
document.getElementById('dlgUserName').textContent = userName;
|
||
document.getElementById('deleteForm').action = '/admin/users/' + userId;
|
||
document.getElementById('deleteDialog').classList.add('open');
|
||
}
|
||
function closeDeleteDialog() {
|
||
document.getElementById('deleteDialog').classList.remove('open');
|
||
}
|
||
document.getElementById('deleteDialog').addEventListener('click', function(e) {
|
||
if (e.target === this) closeDeleteDialog();
|
||
});
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') closeDeleteDialog();
|
||
});
|
||
</script>
|
||
@endsection
|