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 <[email protected]>
This commit is contained in:
ghassanandClaude Sonnet 4.6 committed 2026-05-13 13:24:32 +03:00
1 parent d44490dfe0
commit 0b2e95ea65
161 files changed
+27496 -7250

No files matched your search

+571 -127
View File
@@ -3,113 +3,409 @@
@section('title', 'Profile | TAKEONE')
@section('extra_styles')
<style>
.profile-header { background: var(--bg-secondary); border-radius: 12px; padding: 32px; margin-bottom: 24px; position: relative; overflow: hidden; }
.profile-banner { position: absolute; top: 0; left: 0; right: 0; height: 120px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); z-index: 0; }
.profile-content { position: relative; z-index: 1; margin-top: 60px; }
.profile-avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; border: 4px solid var(--bg-secondary); margin-bottom: 16px; }
.profile-name { font-size: 28px; font-weight: 600; margin-bottom: 4px; }
.profile-username { color: var(--text-secondary); margin-bottom: 12px; font-size: 14px; }
.profile-bio { color: var(--text-primary); margin-bottom: 16px; max-width: 600px; line-height: 1.6; }
.profile-meta { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 20px; color: var(--text-secondary); font-size: 14px; }
.profile-meta-item { display: flex; align-items: center; gap: 6px; }
.profile-stats { display: flex; gap: 32px; margin-top: 16px; padding-top: 20px; border-top: 1px solid var(--border-color); }
.profile-stat { text-align: center; }
.profile-stat-value { font-size: 22px; font-weight: 700; }
.profile-stat-label { font-size: 13px; color: var(--text-secondary); }
.social-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 16px; }
.social-link { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 50%; background: var(--bg-primary); color: var(--text-primary); text-decoration: none; transition: all 0.2s; font-size: 18px; }
.social-link:hover { transform: translateY(-2px); background: var(--brand-red); color: white; }
.social-link.twitter:hover { background: #1da1f2; }
.social-link.instagram:hover { background: #e4405f; }
.social-link.facebook:hover { background: #1877f2; }
.social-link.youtube:hover { background: #ff0000; }
.social-link.linkedin:hover { background: #0077b5; }
.social-link.tiktok:hover { background: #000000; }
.form-card { background: var(--bg-secondary); border-radius: 12px; padding: 24px; margin-bottom: 24px; }
.form-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; gap: 8px; }
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
.form-group { margin-bottom: 16px; }
.form-label { display: block; margin-bottom: 8px; font-weight: 500; font-size: 14px; }
.form-input, .form-textarea { width: 100%; background: #121212; border: 1px solid var(--border-color); border-radius: 8px; padding: 12px 16px; color: var(--text-primary); font-size: 14px; transition: border-color 0.2s; }
.form-input:focus, .form-textarea:focus { outline: none; border-color: var(--brand-red); }
.form-textarea { min-height: 100px; resize: vertical; }
.form-hint { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
.btn-primary { background: var(--brand-red); color: white; border: none; padding: 12px 32px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background 0.2s; display: inline-flex; align-items: center; gap: 8px; }
.btn-primary:hover { background: #cc1a1a; }
.btn-secondary { background: transparent; color: var(--text-primary); border: 1px solid var(--border-color); padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
.btn-secondary:hover { background: var(--bg-secondary); border-color: var(--text-secondary); }
.alert { padding: 12px 16px; border-radius: 8px; margin-bottom: 20px; }
.alert-success { background: rgba(34, 197, 94, 0.15); border: 1px solid #22c55e; color: #22c55e; }
.section-title { font-size: 20px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 8px; }
.video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
.empty-state { text-align: center; padding: 40px 20px; color: var(--text-secondary); }
.empty-state i { font-size: 48px; margin-bottom: 16px; opacity: 0.5; }
.w-100 { width: 100%; }
.mt-4 { margin-top: 24px; }
.text-center { text-align: center; }
.gap-3 { gap: 12px; }
.d-flex { display: flex; }
.flex-column { flex-direction: column; }
.video-card { background: var(--bg-primary); border-radius: 8px; overflow: hidden; transition: transform 0.2s; }
.video-card:hover { transform: translateY(-4px); }
.video-thumbnail { position: relative; aspect-ratio: 16/9; background: #000; }
.video-thumbnail img { width: 100%; height: 100%; object-fit: cover; }
.video-duration { position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
.video-info { padding: 12px; }
.video-title { font-size: 14px; font-weight: 500; margin-bottom: 6px; line-height: 1.4; }
.video-title a { color: var(--text-primary); text-decoration: none; }
.video-title a:hover { color: var(--brand-red); }
.video-meta { font-size: 12px; color: var(--text-secondary); }
@media (max-width: 768px) { .profile-stats { justify-content: center; } .profile-meta { justify-content: center; } .social-links { justify-content: center; } }
</style>
<style>
/* ── Profile Header ─────────────────────────────────────────────────────── */
.profile-header {
background: var(--bg-secondary);
border-radius: 16px;
overflow: hidden;
margin-bottom: 28px;
box-shadow: 0 4px 24px rgba(0,0,0,.25);
}
.profile-banner {
height: 160px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 40%, #0f3460 70%, #533483 100%);
position: relative;
}
.profile-banner-edit {
position: absolute;
top: 14px; right: 16px;
display: flex; align-items: center; gap: 6px;
padding: 7px 14px;
background: rgba(0,0,0,.45);
backdrop-filter: blur(6px);
border: 1px solid rgba(255,255,255,.15);
border-radius: 20px;
color: #fff;
font-size: 13px; font-weight: 500; font-family: inherit;
cursor: pointer; transition: background .2s, border-color .2s;
text-decoration: none;
}
.profile-banner-edit:hover { background: rgba(230,30,30,.7); border-color: rgba(230,30,30,.6); color: #fff; }
.profile-banner-edit i { font-size: 13px; }
.profile-content {
padding: 0 28px 28px;
text-align: center;
}
.profile-avatar-wrap {
display: inline-block;
position: relative;
margin-top: -52px;
margin-bottom: 14px;
}
.profile-avatar {
width: 108px; height: 108px;
border-radius: 50%;
object-fit: cover;
border: 4px solid var(--bg-secondary);
box-shadow: 0 6px 28px rgba(0,0,0,.5);
display: block;
}
.profile-name {
font-size: 24px; font-weight: 700;
letter-spacing: -.3px;
margin: 0 0 4px;
color: var(--text-primary);
}
.profile-username {
font-size: 14px; color: var(--text-secondary);
margin: 0 0 12px;
}
.profile-bio {
font-size: 14px; line-height: 1.7;
color: var(--text-secondary);
max-width: 480px;
margin: 0 auto 16px;
}
.profile-meta {
display: flex; flex-wrap: wrap;
align-items: center; justify-content: center;
gap: 6px 10px;
margin-bottom: 20px;
}
.profile-meta-item {
display: flex; align-items: center; gap: 5px;
font-size: 13px; color: var(--text-secondary);
background: rgba(255,255,255,.05);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 4px 10px;
}
.profile-meta-item i { font-size: 12px; }
.profile-meta-item a { color: inherit; text-decoration: none; }
.profile-meta-item a:hover { color: var(--text-primary); }
.profile-divider { height: 1px; background: var(--border-color); margin: 0 -28px 20px; }
.profile-stats {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.profile-stat {
padding: 8px 24px;
text-align: center;
border-right: 1px solid var(--border-color);
text-decoration: none; color: inherit;
transition: background .15s;
border-radius: 8px;
}
.profile-stat:last-child { border-right: none; }
.profile-stat:hover { background: rgba(255,255,255,.04); }
.profile-stat-value { display: block; font-size: 20px; font-weight: 700; line-height: 1.2; }
.profile-stat-label { display: block; font-size: 12px; color: var(--text-secondary); margin-top: 2px; text-transform: uppercase; letter-spacing: .5px; }
.social-links { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding-top: 4px; }
.social-link {
display: flex; align-items: center; justify-content: center;
width: 36px; height: 36px;
border-radius: 10px;
background: rgba(255,255,255,.06);
border: 1px solid var(--border-color);
color: var(--text-secondary);
text-decoration: none;
font-size: 16px;
transition: all .2s;
}
.social-link:hover { transform: translateY(-2px); color: #fff; border-color: transparent; background: var(--brand-red); }
.social-link.twitter:hover { background: #1da1f2; }
.social-link.instagram:hover { background: linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888); }
.social-link.facebook:hover { background: #1877f2; }
.social-link.youtube:hover { background: #ff0000; }
.social-link.linkedin:hover { background: #0077b5; }
.social-link.tiktok:hover { background: #010101; }
.social-link.whatsapp:hover { background: #25d366; }
.social-link.googlemap:hover { background: #ea4335; }
/* ── Page cards ─────────────────────────────────────────────────────────── */
.form-card { background: var(--bg-secondary); border-radius: 12px; padding: 24px; margin-bottom: 24px; }
.form-title { font-size: 16px; font-weight: 600; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; gap: 8px; }
.form-group { margin-bottom: 16px; }
.form-label { display: block; margin-bottom: 8px; font-weight: 500; font-size: 14px; }
.form-input, .form-textarea { width: 100%; background: #121212; border: 1px solid var(--border-color); border-radius: 8px; padding: 11px 14px; color: var(--text-primary); font-size: 14px; transition: border-color .2s; }
.form-input:focus, .form-textarea:focus { outline: none; border-color: var(--brand-red); }
.form-textarea { min-height: 90px; resize: vertical; }
.form-hint { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
.section-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 8px; }
/* ── Quick links sidebar ─────────────────────────────────────────────────── */
.quick-link {
display: flex; align-items: center; gap: 12px;
padding: 12px 14px; border-radius: 10px;
background: rgba(255,255,255,.03);
border: 1px solid var(--border-color);
color: var(--text-primary); text-decoration: none;
font-size: 14px; font-weight: 500;
transition: background .15s, border-color .15s, transform .15s;
margin-bottom: 8px;
}
.quick-link:last-child { margin-bottom: 0; }
.quick-link i { font-size: 16px; color: var(--text-secondary); width: 20px; text-align: center; transition: color .15s; }
.quick-link:hover { background: rgba(230,30,30,.08); border-color: rgba(230,30,30,.3); transform: translateX(3px); }
.quick-link:hover i { color: var(--brand-red); }
/* ── Videos ─────────────────────────────────────────────────────────────── */
.video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.video-card { background: var(--bg-primary); border-radius: 10px; overflow: hidden; transition: transform .2s, box-shadow .2s; }
.video-card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0,0,0,.3); }
.video-thumbnail { position: relative; aspect-ratio: 16/9; background: #000; }
.video-thumbnail img { width: 100%; height: 100%; object-fit: cover; }
.video-duration { position: absolute; bottom: 6px; right: 6px; background: rgba(0,0,0,.8); color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px; font-weight: 500; }
.video-info { padding: 12px; }
.video-title { font-size: 13px; font-weight: 500; margin-bottom: 6px; line-height: 1.4; }
.video-title a { color: var(--text-primary); text-decoration: none; }
.video-title a:hover { color: var(--brand-red); }
.video-meta { font-size: 12px; color: var(--text-secondary); }
.empty-state { text-align: center; padding: 40px 20px; color: var(--text-secondary); }
.empty-state i { font-size: 44px; display: block; margin-bottom: 12px; opacity: .4; }
.w-100 { width: 100%; }
.mt-4 { margin-top: 24px; }
.d-flex { display: flex; }
.flex-column { flex-direction: column; }
/* ── Edit Profile Modal ─────────────────────────────────────────────────── */
.ep-overlay {
display: none; position: fixed; inset: 0; z-index: 2000;
background: rgba(0,0,0,.75);
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
align-items: center; justify-content: center;
padding: 20px 16px;
}
.ep-overlay.is-open { display: flex; }
.ep-dialog {
display: flex; flex-direction: column;
background: var(--bg-secondary, #1e1e1e);
border: 1px solid var(--border-color, #303030);
border-radius: 16px;
width: 100%; max-width: 600px;
max-height: min(88vh, 720px);
box-shadow: 0 32px 80px rgba(0,0,0,.7);
animation: epIn .2s ease;
}
@keyframes epIn {
from { opacity: 0; transform: scale(.97) translateY(-8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.ep-header {
flex-shrink: 0;
display: flex; align-items: center; justify-content: space-between;
padding: 18px 20px 0;
}
.ep-header-title { font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.ep-close {
display: flex; align-items: center; justify-content: center;
width: 30px; height: 30px; border-radius: 50%;
border: none; background: transparent;
color: var(--text-secondary, #aaa); font-size: 16px; cursor: pointer;
transition: background .15s, color .15s;
}
.ep-close:hover { background: rgba(255,255,255,.1); color: var(--text-primary, #f1f1f1); }
.ep-tabs {
flex-shrink: 0;
display: flex; gap: 2px;
padding: 12px 20px 0;
border-bottom: 1px solid var(--border-color, #303030);
}
.ep-tab {
display: flex; align-items: center; gap: 6px;
padding: 8px 14px;
border: none; background: transparent;
color: var(--text-secondary, #aaa);
font-size: 13px; font-weight: 500; font-family: inherit;
cursor: pointer; border-radius: 8px 8px 0 0;
border-bottom: 2px solid transparent; margin-bottom: -1px;
transition: color .15s, border-color .15s, background .15s;
white-space: nowrap;
}
.ep-tab:hover { color: var(--text-primary, #f1f1f1); background: rgba(255,255,255,.05); }
.ep-tab.active { color: var(--brand-red, #e61e1e); border-bottom-color: var(--brand-red, #e61e1e); }
.ep-tab i { font-size: 13px; }
.ep-body {
flex: 1; min-height: 0;
overflow-y: auto;
padding: 22px 20px;
scrollbar-width: thin;
scrollbar-color: var(--border-color, #303030) transparent;
}
.ep-body::-webkit-scrollbar { width: 4px; }
.ep-body::-webkit-scrollbar-thumb { background: var(--border-color, #303030); border-radius: 2px; }
.ep-panel { display: none; }
.ep-panel.active { display: block; }
/* Form must be a flex child so it respects the dialog's max-height */
#epForm {
display: flex; flex-direction: column;
flex: 1; min-height: 0; overflow: hidden;
}
.ep-footer {
flex-shrink: 0;
padding: 14px 20px 18px;
border-top: 1px solid var(--border-color, #303030);
display: flex; justify-content: flex-end; gap: 8px;
}
.ep-2col { display: flex; gap: 12px; }
.ep-2col > .form-group { flex: 1; min-width: 0; margin-bottom: 0; }
/* Match dropdown button height to .form-input (padding: 11px 14px, no min-height) */
.ep-body .csd-btn { padding: 11px 14px; min-height: 0; }
.ep-body .dp-btn { padding: 11px 10px; min-height: 0; }
/* Avatar upload */
.avatar-upload-area { display: flex; flex-direction: column; align-items: center; gap: 20px; padding: 24px 0 8px; }
.avatar-preview-wrap { position: relative; width: 120px; height: 120px; }
.avatar-preview { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; border: 3px solid var(--border-color, #303030); display: block; }
.avatar-edit-btn {
position: absolute; bottom: 2px; right: 2px;
width: 32px; height: 32px; border-radius: 50%;
background: var(--brand-red, #e61e1e);
border: 2px solid var(--bg-secondary, #1e1e1e);
color: #fff; font-size: 13px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .15s;
}
.avatar-edit-btn:hover { background: #c01717; }
.avatar-file-input { display: none; }
.avatar-upload-hint { font-size: 13px; color: var(--text-secondary, #aaa); text-align: center; line-height: 1.7; }
.avatar-upload-hint strong { color: var(--text-primary, #f1f1f1); }
/* Toast */
.ep-toast {
position: fixed; bottom: 80px; left: 50%;
transform: translateX(-50%) translateY(16px);
background: #22c55e; color: #fff;
font-size: 14px; font-weight: 500;
padding: 10px 20px; border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,.4);
z-index: 3000; opacity: 0; pointer-events: none;
transition: opacity .25s, transform .25s;
white-space: nowrap;
}
.ep-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
@media (max-width: 768px) {
.profile-banner { height: 120px; }
.profile-content { padding: 0 18px 22px; }
.profile-stat { padding: 8px 14px; }
.profile-stat-value { font-size: 18px; }
.ep-overlay { align-items: flex-end; padding: 0; }
.ep-dialog { border-radius: 16px 16px 0 0; max-width: 100%; max-height: 92vh; }
}
</style>
@endsection
@php
// If re-displaying after a validation error, rebuild from old() input
$oldLinks = old('slink');
if ($oldLinks) {
$socialExisting = collect($oldLinks)
->filter(fn($e) => !empty($e['platform']) && !empty($e['value']))
->map(fn($e) => ['platform' => $e['platform'], 'value' => $e['value'], 'visibility' => $e['visibility'] ?? 'public'])
->values()->all();
} else {
$socialExisting = $user->socialLinks()->orderBy('sort_order')->get()
->map(fn($l) => ['platform' => $l->platform, 'value' => $l->value, 'visibility' => $l->visibility])
->all();
}
@endphp
@section('content')
<div class="profile-header">
<div class="profile-banner"></div>
<div class="profile-content text-center">
@if($user->avatar)
<img src="{{ asset('storage/avatars/' . $user->avatar) }}" alt="{{ $user->name }}" class="profile-avatar">
@else
<img src="https://i.pravatar.cc/150?u={{ $user->id }}" alt="{{ $user->name }}" class="profile-avatar">
@endif
<div class="profile-banner">
<button type="button" class="profile-banner-edit" id="openEditProfile">
<i class="bi bi-pencil-square"></i> Edit Profile
</button>
</div>
<div class="profile-content">
<div class="profile-avatar-wrap">
@if($user->avatar)
<img src="{{ asset('storage/avatars/' . $user->avatar) }}" alt="{{ $user->name }}" class="profile-avatar" id="pageAvatar">
@else
<img src="https://i.pravatar.cc/150?u={{ $user->id }}" alt="{{ $user->name }}" class="profile-avatar" id="pageAvatar">
@endif
</div>
<h1 class="profile-name">{{ $user->name }}</h1>
@if($user->username)
<p class="profile-username">@ {{ $user->username }}</p>
<p class="profile-username">@{{ $user->username }}</p>
@endif
@if($user->bio)
<p class="profile-bio">{{ $user->bio }}</p>
@endif
<div class="profile-meta justify-content-center">
<div class="profile-meta">
@if($user->location)
<div class="profile-meta-item"><i class="bi bi-geo-alt"></i><span>{{ $user->location }}</span></div>
@endif
@if($user->website)
<div class="profile-meta-item"><i class="bi bi-link-45deg"></i><a href="{{ $user->website_url }}" target="_blank" style="color: inherit;">{{ Str::limit($user->website, 30) }}</a></div>
<div class="profile-meta-item"><i class="bi bi-link-45deg"></i><a href="{{ $user->website_url }}" target="_blank">{{ Str::limit($user->website, 30) }}</a></div>
@endif
<div class="profile-meta-item"><i class="bi bi-calendar3"></i><span>Joined {{ $user->created_at->format('M Y') }}</span></div>
</div>
@if($user->hasSocialLinks())
<div class="social-links justify-content-center">
@if($user->twitter)<a href="https://twitter.com/{{ $user->twitter }}" target="_blank" class="social-link twitter" title="Twitter"><i class="bi bi-twitter-x"></i></a>@endif
@if($user->instagram)<a href="https://instagram.com/{{ $user->instagram }}" target="_blank" class="social-link instagram" title="Instagram"><i class="bi bi-instagram"></i></a>@endif
@if($user->facebook)<a href="https://facebook.com/{{ $user->facebook }}" target="_blank" class="social-link facebook" title="Facebook"><i class="bi bi-facebook"></i></a>@endif
@if($user->youtube)<a href="https://youtube.com/@{{ $user->youtube }}" target="_blank" class="social-link youtube" title="YouTube"><i class="bi bi-youtube"></i></a>@endif
@if($user->linkedin)<a href="https://linkedin.com/in/{{ $user->linkedin }}" target="_blank" class="social-link linkedin" title="LinkedIn"><i class="bi bi-linkedin"></i></a>@endif
@if($user->tiktok)<a href="https://tiktok.com/@{{ $user->tiktok }}" target="_blank" class="social-link tiktok" title="TikTok"><i class="bi bi-tiktok"></i></a>@endif
@if($user->website)<a href="{{ $user->website_url }}" target="_blank" class="social-link" title="Website"><i class="bi bi-globe"></i></a>@endif
@php
$socialIconMap = [
'twitter' => ['icon' => 'bi-twitter-x', 'class' => 'twitter', 'href' => fn($v) => "https://twitter.com/{$v}"],
'instagram' => ['icon' => 'bi-instagram', 'class' => 'instagram', 'href' => fn($v) => "https://instagram.com/{$v}"],
'facebook' => ['icon' => 'bi-facebook', 'class' => 'facebook', 'href' => fn($v) => "https://facebook.com/{$v}"],
'youtube' => ['icon' => 'bi-youtube', 'class' => 'youtube', 'href' => fn($v) => "https://youtube.com/@{$v}"],
'linkedin' => ['icon' => 'bi-linkedin', 'class' => 'linkedin', 'href' => fn($v) => "https://linkedin.com/in/{$v}"],
'tiktok' => ['icon' => 'bi-tiktok', 'class' => 'tiktok', 'href' => fn($v) => "https://tiktok.com/@{$v}"],
'whatsapp' => ['icon' => 'bi-whatsapp', 'class' => 'whatsapp', 'href' => fn($v) => "https://wa.me/" . preg_replace('/\D/', '', $v)],
'website' => ['icon' => 'bi-globe', 'class' => '', 'href' => fn($v) => preg_match('/^https?:\/\//', $v) ? $v : "https://{$v}"],
'google_location' => ['icon' => 'bi-geo-alt-fill', 'class' => 'googlemap', 'href' => fn($v) => $v],
'social_phone' => ['icon' => 'bi-telephone-fill', 'class' => '', 'href' => fn($v) => "tel:{$v}"],
'social_email' => ['icon' => 'bi-envelope-fill', 'class' => '', 'href' => fn($v) => "mailto:{$v}"],
];
@endphp
<div class="social-links">
@foreach($user->socialLinks as $link)
@if(isset($socialIconMap[$link->platform]))
@php $m = $socialIconMap[$link->platform]; @endphp
<a href="{{ $m['href']($link->value) }}"
target="{{ in_array($link->platform, ['social_phone','social_email']) ? '_self' : '_blank' }}"
class="social-link {{ $m['class'] }}"
title="{{ $link->value }}">
<i class="bi {{ $m['icon'] }}"></i>
</a>
@endif
@endforeach
</div>
@endif
<div class="profile-stats justify-content-center">
<a href="{{ route('channel', $user->id) }}" class="profile-stat"><div class="profile-stat-value">{{ $user->videos->count() }}</div><div class="profile-stat-label">Videos</div></a>
<div class="profile-stat"><div class="profile-stat-value">{{ number_format($user->subscriber_count) }}</div><div class="profile-stat-label">Subscribers</div></div>
<div class="profile-stat"><div class="profile-stat-value">{{ \DB::table('video_views')->whereIn('video_id', $user->videos->pluck('id'))->count() }}</div><div class="profile-stat-label">Views</div></div>
<div class="profile-stat"><div class="profile-stat-value">{{ $user->likes->count() }}</div><div class="profile-stat-label">Likes</div></div>
<div class="profile-divider"></div>
<div class="profile-stats">
<a href="{{ route('channel', $user->channel) }}" class="profile-stat">
<span class="profile-stat-value">{{ $user->videos->count() }}</span>
<span class="profile-stat-label">Videos</span>
</a>
<div class="profile-stat">
<span class="profile-stat-value">{{ number_format($user->subscriber_count) }}</span>
<span class="profile-stat-label">Subscribers</span>
</div>
<div class="profile-stat">
<span class="profile-stat-value">{{ \DB::table('video_views')->whereIn('video_id', $user->videos->pluck('id'))->count() }}</span>
<span class="profile-stat-label">Views</span>
</div>
<div class="profile-stat">
<span class="profile-stat-value">{{ $user->likes->count() }}</span>
<span class="profile-stat-label">Likes</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="form-card">
@@ -132,59 +428,207 @@
@endforeach
</div>
@if($user->videos->count() > 4)
<div class="text-center mt-4"><a href="{{ route('channel', $user->id) }}" class="btn-secondary">View All Videos <i class="bi bi-arrow-right"></i></a></div>
<div class="text-center mt-4"><a href="{{ route('channel', $user->channel) }}" class="action-btn">View All Videos <i class="bi bi-arrow-right"></i></a></div>
@endif
@else
<div class="empty-state"><i class="bi bi-camera-video"></i><p>No videos yet</p></div>
@endif
</div>
</div>
<div class="col-lg-4">
<div class="form-card">
<h2 class="form-title"><i class="bi bi-pencil-square"></i> Edit Profile</h2>
@if(session('success'))<div class="alert alert-success">{{ session('success') }}</div>@endif
<form method="POST" action="{{ route('profile.update') }}" enctype="multipart/form-data">
@csrf @method('PUT')
<div class="form-group"><label class="form-label">Name</label><input type="text" name="name" class="form-input" value="{{ old('name', $user->name) }}" required></div>
<div class="form-group"><label class="form-label">Avatar</label><input type="file" name="avatar" class="form-input" accept="image/*"><small class="form-hint">Max: 5MB</small></div>
<div class="form-group"><label class="form-label">Bio</label><textarea name="bio" class="form-textarea" placeholder="Tell us about yourself...">{{ old('bio', $user->bio) }}</textarea></div>
<div class="form-group"><label class="form-label">Location</label><input type="text" name="location" class="form-input" value="{{ old('location', $user->location) }}" placeholder="City, Country"></div>
<div class="form-group"><label class="form-label">Birthday</label><input type="date" name="birthday" class="form-input" value="{{ old('birthday', $user->birthday) }}"></div>
<button type="submit" class="btn-primary w-100"><i class="bi bi-check-lg"></i> Save Changes</button>
</form>
</div>
<div class="form-card">
<h2 class="form-title"><i class="bi bi-share"></i> Social Links</h2>
<form method="POST" action="{{ route('profile.update') }}" enctype="multipart/form-data">
@csrf @method('PUT')
<div class="form-row">
<div class="form-group"><label class="form-label"><i class="bi bi-twitter-x"></i> Twitter</label><input type="text" name="twitter" class="form-input" value="{{ old('twitter', $user->twitter) }}" placeholder="username"></div>
<div class="form-group"><label class="form-label"><i class="bi bi-instagram"></i> Instagram</label><input type="text" name="instagram" class="form-input" value="{{ old('instagram', $user->instagram) }}" placeholder="username"></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label"><i class="bi bi-facebook"></i> Facebook</label><input type="text" name="facebook" class="form-input" value="{{ old('facebook', $user->facebook) }}" placeholder="username"></div>
<div class="form-group"><label class="form-label"><i class="bi bi-youtube"></i> YouTube</label><input type="text" name="youtube" class="form-input" value="{{ old('youtube', $user->youtube) }}" placeholder="channel"></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label"><i class="bi bi-linkedin"></i> LinkedIn</label><input type="text" name="linkedin" class="form-input" value="{{ old('linkedin', $user->linkedin) }}" placeholder="username"></div>
<div class="form-group"><label class="form-label"><i class="bi bi-tiktok"></i> TikTok</label><input type="text" name="tiktok" class="form-input" value="{{ old('tiktok', $user->tiktok) }}" placeholder="username"></div>
</div>
<div class="form-group"><label class="form-label"><i class="bi bi-globe"></i> Website</label><input type="url" name="website" class="form-input" value="{{ old('website', $user->website) }}" placeholder="https://example.com"></div>
<button type="submit" class="btn-primary w-100"><i class="bi bi-check-lg"></i> Save Social Links</button>
</form>
</div>
<div class="form-card">
<h2 class="form-title"><i class="bi bi-link-45deg"></i> Quick Links</h2>
<div class="d-flex flex-column gap-3">
<a href="{{ route('channel', $user->id) }}" class="btn-secondary text-center"><i class="bi bi-play-btn"></i> View My Channel</a>
<a href="{{ route('settings') }}" class="btn-secondary text-center"><i class="bi bi-gear"></i> Account Settings</a>
<a href="{{ route('history') }}" class="btn-secondary text-center"><i class="bi bi-clock-history"></i> Watch History</a>
<a href="{{ route('liked') }}" class="btn-secondary text-center"><i class="bi bi-heart"></i> Liked Videos</a>
</div>
<a href="{{ route('channel', $user->channel) }}" class="quick-link"><i class="bi bi-play-btn"></i><span>View My Channel</span></a>
<a href="{{ route('settings') }}" class="quick-link"><i class="bi bi-gear"></i><span>Account Settings</span></a>
<a href="{{ route('history') }}" class="quick-link"><i class="bi bi-clock-history"></i><span>Watch History</span></a>
<a href="{{ route('liked') }}" class="quick-link"><i class="bi bi-heart"></i><span>Liked Videos</span></a>
</div>
</div>
</div>
{{-- ── Edit Profile Modal ───────────────────────────────────────────── --}}
<div class="ep-overlay" id="epOverlay" role="dialog" aria-modal="true" aria-labelledby="epTitle">
<div class="ep-dialog">
<div class="ep-header">
<span class="ep-header-title" id="epTitle"><i class="bi bi-pencil-square"></i> Edit Profile</span>
<button type="button" class="ep-close" id="closeEditProfile" aria-label="Close"><i class="bi bi-x-lg"></i></button>
</div>
<div class="ep-tabs">
<button type="button" class="ep-tab active" data-tab="info">
<i class="bi bi-person"></i> Profile
</button>
<button type="button" class="ep-tab" data-tab="picture">
<i class="bi bi-camera"></i> Picture
</button>
<button type="button" class="ep-tab" data-tab="social">
<i class="bi bi-share"></i> Social
</button>
</div>
<form method="POST" action="{{ route('profile.update') }}" enctype="multipart/form-data" id="epForm">
@csrf @method('PUT')
<div class="ep-body">
{{-- Tab: Profile Info --}}
<div class="ep-panel active" id="panel-info">
<div class="form-group"><label class="form-label">Name</label><input type="text" name="name" class="form-input" value="{{ old('name', $user->name) }}" required></div>
<div class="form-group"><label class="form-label">Bio</label><textarea name="bio" class="form-textarea" placeholder="Tell us about yourself...">{{ old('bio', $user->bio) }}</textarea></div>
<div class="ep-2col" style="margin-bottom:16px;">
<div class="form-group">
<x-date-picker
name="birthday"
label="Birthday"
value="{{ old('birthday', $user->birthday) }}"
:max-year="(int) date('Y')"
:min-year="1900"
/>
</div>
<div class="form-group">
<label class="form-label">Gender</label>
<select name="gender" class="form-input" style="cursor:pointer;">
<option value="">Prefer not to say</option>
<option value="male" {{ old('gender', $user->gender) === 'male' ? 'selected' : '' }}>Male</option>
<option value="female" {{ old('gender', $user->gender) === 'female' ? 'selected' : '' }}>Female</option>
<option value="prefer_not_to_say" {{ old('gender', $user->gender) === 'prefer_not_to_say' ? 'selected' : '' }}>Prefer not to say</option>
</select>
</div>
</div>
<div class="ep-2col" style="margin-bottom:16px;">
<div class="form-group">
<x-country-select
name="nationality"
label="Nationality"
placeholder="Select nationality"
value="{{ old('nationality', $user->nationality) }}"
/>
</div>
<div class="form-group">
<x-timezone-select
name="timezone"
label="Timezone"
placeholder="Select timezone"
value="{{ old('timezone', $user->timezone) }}"
/>
</div>
</div>
</div>
{{-- Tab: Picture --}}
<div class="ep-panel" id="panel-picture">
<div class="avatar-upload-area">
<div class="avatar-preview-wrap">
@if($user->avatar)
<img src="{{ asset('storage/avatars/' . $user->avatar) }}" class="avatar-preview" id="avatarPreview" alt="Avatar">
@else
<img src="https://i.pravatar.cc/150?u={{ $user->id }}" class="avatar-preview" id="avatarPreview" alt="Avatar">
@endif
<button type="button" class="avatar-edit-btn" id="avatarEditBtn" title="Change photo">
<i class="bi bi-camera-fill"></i>
</button>
</div>
<input type="file" name="avatar" id="avatarFileInput" class="avatar-file-input" accept="image/*">
<div class="avatar-upload-hint">
<strong>Click the camera icon</strong> to upload a new photo<br>
JPG, PNG or GIF &nbsp;·&nbsp; Max 5 MB<br>
<small style="color:var(--text-secondary)">Recommended: at least 200 × 200 px</small>
</div>
</div>
</div>
{{-- Tab: Social --}}
<div class="ep-panel" id="panel-social">
<x-social-links-editor :existing="$socialExisting" />
</div>
</div>{{-- .ep-body --}}
<div class="ep-footer">
<button type="button" class="action-btn" id="epCancelBtn"><span>Cancel</span></button>
<button type="submit" class="action-btn action-btn-primary"><i class="bi bi-check-lg"></i> <span>Save Changes</span></button>
</div>
</form>
</div>
</div>
<div class="ep-toast" id="epToast"></div>
@endsection
@section('scripts')
<script>
(function () {
const overlay = document.getElementById('epOverlay');
const openBtn = document.getElementById('openEditProfile');
const closeBtn = document.getElementById('closeEditProfile');
const cancelBtn = document.getElementById('epCancelBtn');
const toast = document.getElementById('epToast');
// ── Modal open / close ───────────────────────────────────────────────
function openModal(tab) {
overlay.classList.add('is-open');
document.body.style.overflow = 'hidden';
if (tab) switchTab(tab);
}
function closeModal() {
overlay.classList.remove('is-open');
document.body.style.overflow = '';
}
openBtn.addEventListener('click', () => openModal('info'));
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
overlay.addEventListener('click', e => { if (e.target === overlay) closeModal(); });
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && overlay.classList.contains('is-open')) closeModal();
});
// ── Tabs ─────────────────────────────────────────────────────────────
function switchTab(name) {
document.querySelectorAll('.ep-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === name));
document.querySelectorAll('.ep-panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + name));
}
document.querySelectorAll('.ep-tab').forEach(btn => {
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
});
// ── Avatar preview ───────────────────────────────────────────────────
const avatarEditBtn = document.getElementById('avatarEditBtn');
const avatarFileInput = document.getElementById('avatarFileInput');
const avatarPreview = document.getElementById('avatarPreview');
const pageAvatar = document.getElementById('pageAvatar');
avatarEditBtn.addEventListener('click', () => avatarFileInput.click());
avatarFileInput.addEventListener('change', () => {
const file = avatarFileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
avatarPreview.src = e.target.result;
if (pageAvatar) pageAvatar.src = e.target.result;
};
reader.readAsDataURL(file);
});
// ── Flash handling ───────────────────────────────────────────────────
@if(session('success'))
showToast(@json(session('success')), 'success');
@elseif($errors->any())
openModal('info');
@endif
function showToast(msg, type) {
toast.textContent = msg;
toast.style.background = type === 'success' ? '#22c55e' : '#e61e1e';
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3500);
}
}());
</script>
@endsection