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

173 lines
7.2 KiB
PHP

@props([
'name' => 'gender',
'id' => null,
'value' => null,
'label' => null,
'required' => false,
'class' => '',
'style' => '',
])
@php
$uid = 'gsd_' . ($id ?? $name) . '_' . substr(md5(uniqid()), 0, 8);
$inputId = $id ?? $name;
$options = [
['value' => 'male', 'symbol' => '♂', 'label' => 'Male', 'color' => '#4a9eff'],
['value' => 'female', 'symbol' => '♀', 'label' => 'Female', 'color' => '#ff6eb0'],
];
$selected = null;
$placeholder = 'Select gender';
foreach ($options as $opt) {
if ($opt['value'] === $value) { $selected = $opt; break; }
}
$isPlaceholder = !$selected;
@endphp
@once('gsd-styles')
<style>
.gsd-wrap { position: relative; }
.gsd-lbl { display: block; margin-bottom: 6px; font-weight: 500; font-size: 14px; color: var(--text-primary, #f1f1f1); }
.gsd-lbl .req { color: var(--brand-red, #e61e1e); margin-left: 2px; }
.gsd-btn {
display: flex; align-items: center; gap: 10px; width: 100%;
background: var(--bg-dark, #0f0f0f); border: 1px solid var(--border-color, #303030);
border-radius: 8px; padding: 10px 14px; color: var(--text-primary, #f1f1f1);
font-size: 14px; font-family: inherit; cursor: pointer; text-align: left;
transition: border-color .2s; outline: none; min-height: 46px; box-sizing: border-box;
}
.gsd-btn:hover, .gsd-btn:focus-visible { border-color: var(--brand-red, #e61e1e); }
.gsd-btn[aria-expanded="true"] { border-color: var(--brand-red, #e61e1e); }
.gsd-sym { font-size: 20px; line-height: 1; flex-shrink: 0; user-select: none; }
.gsd-val { flex: 1; }
.gsd-val.ph { color: var(--text-secondary, #aaa); }
.gsd-arr { flex-shrink: 0; font-size: 11px; color: var(--text-secondary, #aaa); margin-left: auto; transition: transform .2s; }
.gsd-btn[aria-expanded="true"] .gsd-arr { transform: rotate(180deg); }
.gsd-panel {
position: fixed; z-index: 1060; min-width: 180px;
background: var(--bg-secondary, #1e1e1e); border: 1px solid var(--border-color, #303030);
border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.55); overflow: hidden;
padding: 4px 0;
}
.gsd-opt {
display: flex; align-items: center; gap: 12px; padding: 10px 14px;
cursor: pointer; font-size: 14px; color: var(--text-primary, #f1f1f1);
transition: background .1s; outline: none; border: none; background: none;
width: 100%; text-align: left; font-family: inherit;
}
.gsd-opt:hover, .gsd-opt:focus { background: rgba(255,255,255,.07); }
.gsd-opt[aria-selected="true"] { background: rgba(255,255,255,.05); }
.gsd-opt-sym { font-size: 22px; line-height: 1; user-select: none; }
.gsd-opt-lbl { font-weight: 500; }
</style>
@endonce
@if($label)
<div class="gsd-wrap {{ $class }}" id="{{ $uid }}" @if($style) style="{{ $style }}" @endif>
<label class="gsd-lbl" for="{{ $inputId }}">
{{ $label }}@if($required)<span class="req">*</span>@endif
</label>
@else
<div class="gsd-wrap {{ $class }}" id="{{ $uid }}" @if($style) style="{{ $style }}" @endif>
@endif
<button type="button"
class="gsd-btn"
aria-haspopup="listbox"
aria-expanded="false"
aria-label="{{ $label ?? 'Select gender' }}">
@if($selected)
<span class="gsd-sym" style="color:{{ $selected['color'] }}">{{ $selected['symbol'] }}</span>
<span class="gsd-val">{{ $selected['label'] }}</span>
@else
<span class="gsd-sym" style="color:var(--text-secondary,#aaa)"></span>
<span class="gsd-val ph">{{ $placeholder }}</span>
@endif
<i class="bi bi-chevron-down gsd-arr"></i>
</button>
<div class="gsd-panel" hidden role="listbox">
@foreach($options as $opt)
<button type="button"
class="gsd-opt"
role="option"
data-v="{{ $opt['value'] }}"
aria-selected="{{ $value === $opt['value'] ? 'true' : 'false' }}">
<span class="gsd-opt-sym" style="color:{{ $opt['color'] }}">{{ $opt['symbol'] }}</span>
<span class="gsd-opt-lbl">{{ $opt['label'] }}</span>
</button>
@endforeach
</div>
<input type="hidden"
name="{{ $name }}"
id="{{ $inputId }}"
value="{{ $value }}"
@if($required) required @endif>
</div>
<script>
(function () {
var uid = '{{ $uid }}';
function boot() {
var root = document.getElementById(uid);
var btn = root.querySelector('.gsd-btn');
var panel = root.querySelector('.gsd-panel');
var hidden = root.querySelector('input[type=hidden]');
var symEl = btn.querySelector('.gsd-sym');
var valEl = btn.querySelector('.gsd-val');
function open() {
document.querySelectorAll('.gsd-panel:not([hidden])').forEach(function(p) { p.hidden = true; });
document.querySelectorAll('.gsd-btn[aria-expanded=true]').forEach(function(b) { b.setAttribute('aria-expanded','false'); });
panel.hidden = false;
btn.setAttribute('aria-expanded', 'true');
var r = btn.getBoundingClientRect();
var goUp = window.innerHeight - r.bottom < 120 && r.top > 120;
panel.style.left = r.left + 'px';
panel.style.width = Math.max(r.width, 180) + 'px';
if (goUp) {
panel.style.top = '';
panel.style.bottom = (window.innerHeight - r.top + 4) + 'px';
} else {
panel.style.top = (r.bottom + 4) + 'px';
panel.style.bottom = '';
}
}
function close() { panel.hidden = true; btn.setAttribute('aria-expanded', 'false'); }
function pick(opt) {
hidden.value = opt.dataset.v;
var sym = opt.querySelector('.gsd-opt-sym');
var lbl = opt.querySelector('.gsd-opt-lbl');
symEl.textContent = sym.textContent;
symEl.style.color = sym.style.color;
valEl.textContent = lbl.textContent;
valEl.classList.remove('ph');
panel.querySelectorAll('.gsd-opt').forEach(function(o) {
o.setAttribute('aria-selected', o === opt ? 'true' : 'false');
});
close(); btn.focus();
hidden.dispatchEvent(new Event('change', { bubbles: true }));
}
btn.addEventListener('click', function(e) { e.stopPropagation(); panel.hidden ? open() : close(); });
btn.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); panel.hidden ? open() : close(); } });
panel.addEventListener('mousedown', function(e) { e.stopPropagation(); });
panel.querySelectorAll('.gsd-opt').forEach(function(opt) {
opt.addEventListener('click', function() { pick(opt); });
opt.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(opt); }
if (e.key === 'Escape') { close(); btn.focus(); }
});
});
document.addEventListener('mousedown', function(e) { if (!root.contains(e.target)) close(); });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
}());
</script>