ghassan 0b2e95ea65 Add NAS file manager integration and all pending platform changes
- 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>
2026-05-13 13:24:32 +03:00

315 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('admin.layout')
@section('title', 'Edit Video')
@section('page_title', 'Edit Video')
@section('extra_styles')
<style>
.ef-grid {
display: grid;
grid-template-columns: 1fr 300px;
gap: 20px;
align-items: start;
}
@media (max-width: 900px) {
.ef-grid { grid-template-columns: 1fr; }
}
/* ── Form fields ── */
.ef-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.ef-field:last-of-type { margin-bottom: 0; }
.ef-label {
font-size: 12px; font-weight: 600; color: var(--text-2);
text-transform: uppercase; letter-spacing: .04em;
}
.ef-input, .ef-select, .ef-textarea {
width: 100%;
background: var(--bg-2);
border: 1px solid var(--border);
color: var(--text);
border-radius: 8px;
font-size: 13px;
outline: none;
transition: border-color .15s;
font-family: inherit;
}
.ef-input, .ef-select { height: 38px; padding: 0 12px; }
.ef-select { cursor: pointer; appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23888' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px;
}
.ef-textarea { height: 100px; padding: 10px 12px; resize: vertical; }
.ef-input:focus, .ef-select:focus, .ef-textarea:focus { border-color: var(--brand); }
.ef-input.is-invalid, .ef-select.is-invalid, .ef-textarea.is-invalid { border-color: #f87171; }
.ef-error { font-size: 12px; color: #f87171; margin-top: 2px; }
.ef-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
@media (max-width: 600px) { .ef-row-3 { grid-template-columns: 1fr; } }
/* ── Actions ── */
.ef-actions { display: flex; gap: 10px; margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border); }
/* ── Sidebar stats ── */
.ef-thumb {
width: 100%; border-radius: 10px; object-fit: cover; display: block;
background: var(--bg-2);
}
.ef-thumb-placeholder {
width: 100%; height: 160px; border-radius: 10px;
background: var(--bg-2); border: 1px solid var(--border);
display: flex; align-items: center; justify-content: center;
color: var(--text-3); font-size: 36px;
}
.ef-stat-row {
display: flex; justify-content: space-between; align-items: center;
padding: 9px 0; border-bottom: 1px solid rgba(255,255,255,.04);
font-size: 13px;
}
.ef-stat-row:last-child { border-bottom: none; padding-bottom: 0; }
.ef-stat-label { color: var(--text-2); }
.ef-stat-val { font-weight: 600; color: var(--text); }
/* ── Danger zone ── */
.ef-danger-zone {
padding: 16px;
border: 1px solid rgba(248,113,113,.25);
border-radius: 10px;
background: rgba(248,113,113,.05);
}
.ef-danger-title { font-size: 12px; font-weight: 700; color: #f87171; margin-bottom: 10px; text-transform: uppercase; letter-spacing: .05em; }
</style>
@endsection
@section('content')
{{-- ── Page header ── --}}
<div class="adm-page-header" style="margin-bottom:20px;">
<div style="display:flex;align-items:center;gap:12px;">
<a href="{{ route('admin.videos') }}" class="adm-btn adm-btn-sm">
<i class="bi bi-arrow-left"></i>
</a>
<h1 class="adm-page-title" style="margin:0;">
<i class="bi bi-pencil-square"></i> Edit Video
</h1>
</div>
</div>
{{-- ── Alerts ── --}}
@if(session('success'))
<div class="adm-alert adm-alert-success" style="margin-bottom:16px;">
<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" style="margin-bottom:16px;">
<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
<div class="ef-grid">
{{-- ── Left: form ── --}}
<div class="adm-card">
<div class="adm-card-header">
<div class="adm-card-title"><i class="bi bi-film"></i> Video Details</div>
</div>
<div class="adm-card-body">
<form method="POST" action="{{ route('admin.videos.update', $video) }}">
@csrf
@method('PUT')
{{-- Title --}}
<div class="ef-field">
<label class="ef-label" for="title">Title</label>
<input class="ef-input @error('title') is-invalid @enderror"
id="title" name="title" type="text"
value="{{ old('title', $video->title) }}" required>
@error('title')<div class="ef-error">{{ $message }}</div>@enderror
</div>
{{-- Description --}}
<div class="ef-field">
<label class="ef-label" for="description">Description</label>
<textarea class="ef-textarea @error('description') is-invalid @enderror"
id="description" name="description">{{ old('description', $video->description) }}</textarea>
@error('description')<div class="ef-error">{{ $message }}</div>@enderror
</div>
{{-- Visibility / Type / Status --}}
<div class="ef-row-3">
<div class="ef-field" style="margin-bottom:0;">
<label class="ef-label" for="visibility">Visibility</label>
<select class="ef-select @error('visibility') is-invalid @enderror" id="visibility" name="visibility">
<option value="public" {{ old('visibility', $video->visibility) === 'public' ? 'selected' : '' }}>Public</option>
<option value="unlisted" {{ old('visibility', $video->visibility) === 'unlisted' ? 'selected' : '' }}>Unlisted</option>
<option value="private" {{ old('visibility', $video->visibility) === 'private' ? 'selected' : '' }}>Private</option>
</select>
@error('visibility')<div class="ef-error">{{ $message }}</div>@enderror
</div>
<div class="ef-field" style="margin-bottom:0;">
<label class="ef-label" for="type">Type</label>
<select class="ef-select @error('type') is-invalid @enderror" id="type" name="type">
<option value="generic" {{ old('type', $video->type) === 'generic' ? 'selected' : '' }}>Generic</option>
<option value="music" {{ old('type', $video->type) === 'music' ? 'selected' : '' }}>Music</option>
<option value="match" {{ old('type', $video->type) === 'match' ? 'selected' : '' }}>Match</option>
</select>
@error('type')<div class="ef-error">{{ $message }}</div>@enderror
</div>
<div class="ef-field" style="margin-bottom:0;">
<label class="ef-label" for="status">Status</label>
<select class="ef-select @error('status') is-invalid @enderror" id="status" name="status">
<option value="pending" {{ old('status', $video->status) === 'pending' ? 'selected' : '' }}>Pending</option>
<option value="processing" {{ old('status', $video->status) === 'processing' ? 'selected' : '' }}>Processing</option>
<option value="ready" {{ old('status', $video->status) === 'ready' ? 'selected' : '' }}>Ready</option>
<option value="failed" {{ old('status', $video->status) === 'failed' ? 'selected' : '' }}>Failed</option>
</select>
@error('status')<div class="ef-error">{{ $message }}</div>@enderror
</div>
</div>
{{-- Download Access --}}
<div class="ef-field" style="margin-top:18px;">
<label class="ef-label" for="download_access">Who Can Download</label>
<select class="ef-select @error('download_access') is-invalid @enderror" id="download_access" name="download_access">
<option value="disabled" {{ old('download_access', $video->download_access) === 'disabled' ? 'selected' : '' }}>No one (disabled)</option>
<option value="everyone" {{ old('download_access', $video->download_access) === 'everyone' ? 'selected' : '' }}>Everyone (guests too)</option>
<option value="registered" {{ old('download_access', $video->download_access) === 'registered' ? 'selected' : '' }}>Registered members</option>
<option value="subscribers" {{ old('download_access', $video->download_access) === 'subscribers' ? 'selected' : '' }}>Subscribers only</option>
</select>
@error('download_access')<div class="ef-error">{{ $message }}</div>@enderror
</div>
<div class="ef-actions">
<button type="submit" class="adm-btn adm-btn-primary">
<i class="bi bi-check-circle-fill"></i> Save Changes
</button>
<a href="{{ route('admin.videos') }}" class="adm-btn">Cancel</a>
<a href="{{ route('videos.show', $video) }}" target="_blank" class="adm-btn" style="margin-left:auto;">
<i class="bi bi-play-circle"></i> View Video
</a>
</div>
</form>
</div>
</div>
{{-- ── Right: sidebar ── --}}
<div style="display:flex;flex-direction:column;gap:16px;">
{{-- Thumbnail --}}
<div class="adm-card">
<div class="adm-card-body">
@if($video->thumbnail)
<img src="{{ asset('storage/thumbnails/' . $video->thumbnail) }}"
alt="{{ $video->title }}" class="ef-thumb">
@else
<div class="ef-thumb-placeholder"><i class="bi bi-play-circle"></i></div>
@endif
</div>
</div>
{{-- Stats --}}
<div class="adm-card">
<div class="adm-card-header">
<div class="adm-card-title"><i class="bi bi-info-circle"></i> Video Info</div>
</div>
<div class="adm-card-body">
<div class="ef-stat-row">
<span class="ef-stat-label">Owner</span>
<a href="{{ route('channel', $video->user->channel) }}" target="_blank"
style="color:var(--brand);font-weight:600;font-size:13px;text-decoration:none;">
{{ $video->user->name }}
</a>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">Uploaded</span>
<span class="ef-stat-val">{{ $video->created_at->format('M d, Y') }}</span>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">Views</span>
<span class="ef-stat-val" style="color:#22d3ee;">
{{ number_format(\DB::table('video_views')->where('video_id', $video->id)->count()) }}
</span>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">Likes</span>
<span class="ef-stat-val" style="color:#f472b6;">
{{ number_format(\DB::table('video_likes')->where('video_id', $video->id)->count()) }}
</span>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">Duration</span>
<span class="ef-stat-val">{{ $video->duration ? gmdate('H:i:s', $video->duration) : '—' }}</span>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">Dimensions</span>
<span class="ef-stat-val">{{ $video->width ?? '—' }} × {{ $video->height ?? '—' }}</span>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">File size</span>
<span class="ef-stat-val">{{ $video->size ? number_format($video->size / 1024 / 1024, 1) . ' MB' : '—' }}</span>
</div>
<div class="ef-stat-row">
<span class="ef-stat-label">Orientation</span>
<span class="ef-stat-val" style="text-transform:capitalize;">{{ $video->orientation ?? '—' }}</span>
</div>
</div>
</div>
{{-- Danger zone --}}
<div class="ef-danger-zone">
<div class="ef-danger-title"><i class="bi bi-exclamation-triangle-fill me-1"></i> Danger Zone</div>
<button type="button" class="adm-btn adm-btn-danger" style="width:100%;"
onclick="openDelDialog()">
<i class="bi bi-trash3-fill"></i> Delete This Video
</button>
</div>
</div>
</div>
{{-- ── Delete confirmation dialog ── --}}
<div class="adm-dialog-overlay" id="delDialog">
<div class="adm-dialog">
<div class="adm-dialog-header">
<div class="adm-dialog-title">
<i class="bi bi-exclamation-triangle-fill"></i> Delete Video
</div>
<button type="button" class="adm-btn adm-btn-sm" onclick="closeDelDialog()"
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>{{ $video->title }}</strong>.</p>
<div class="adm-dialog-warning">
<i class="bi bi-info-circle-fill" style="flex-shrink:0;margin-top:1px;"></i>
The video file, thumbnail, and all associated data will be removed. This cannot be undone.
</div>
</div>
<div class="adm-dialog-footer">
<button type="button" class="adm-btn" onclick="closeDelDialog()">Cancel</button>
<form method="POST" action="{{ route('admin.videos.delete', $video) }}">
@csrf @method('DELETE')
<button type="submit" class="adm-btn adm-btn-danger" style="height:36px;">
<i class="bi bi-trash3-fill"></i> Delete Permanently
</button>
</form>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
function openDelDialog() { document.getElementById('delDialog').classList.add('open'); }
function closeDelDialog() { document.getElementById('delDialog').classList.remove('open'); }
document.getElementById('delDialog').addEventListener('click', e => { if (e.target === document.getElementById('delDialog')) closeDelDialog(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeDelDialog(); });
</script>
@endsection