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

635 lines
31 KiB
PHP
Raw Permalink 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('layouts.app')
@section('title', 'Profile | TAKEONE')
@section('extra_styles')
<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">
<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>
@endif
@if($user->bio)
<p class="profile-bio">{{ $user->bio }}</p>
@endif
<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">{{ 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())
@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-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">
<h2 class="section-title"><i class="bi bi-play-circle"></i> Recent Videos</h2>
@if($user->videos->count() > 0)
<div class="video-grid">
@foreach($user->videos->take(4) as $video)
<div class="video-card">
<a href="{{ route('videos.show', $video) }}">
<div class="video-thumbnail">
<img src="{{ $video->thumbnail ? asset('storage/thumbnails/' . $video->thumbnail) : 'https://i.ytimg.com/vi/default.jpg' }}" alt="{{ $video->title }}">
<span class="video-duration">{{ $video->duration ?? '0:00' }}</span>
</div>
</a>
<div class="video-info">
<h3 class="video-title"><a href="{{ route('videos.show', $video) }}">{{ Str::limit($video->title, 50) }}</a></h3>
<div class="video-meta"><span>{{ number_format($video->view_count ?? 0) }} views</span><span> </span><span>{{ $video->created_at->diffForHumans() }}</span></div>
</div>
</div>
@endforeach
</div>
@if($user->videos->count() > 4)
<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-link-45deg"></i> Quick Links</h2>
<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