takeone-youtube-clone/resources/views/components/social-links-editor.blade.php
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

325 lines
16 KiB
PHP

@props([
'existing' => [], // [ ['platform'=>'twitter','value'=>'handle','visibility'=>'public'], ... ]
])
@php
$uid = 'sle_' . substr(md5(uniqid()), 0, 8);
@endphp
@once('sle-styles')
<style>
.sle-entry { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.sle-entry-input { flex: 1; min-width: 0; }
.sle-icon-btn {
flex-shrink: 0; display: flex; align-items: center; justify-content: center;
width: 38px; height: 38px; border-radius: 8px; background: transparent;
border: 1px solid var(--border-color, #303030);
font-size: 15px; cursor: pointer; transition: border-color .15s, color .15s, background .15s;
}
.sle-remove-btn { color: var(--text-secondary, #aaa); }
.sle-remove-btn:hover { border-color: #e61e1e; color: #e61e1e; background: rgba(230,30,30,.08); }
/* Visibility button colour per state */
.sle-vis-btn[data-vis="public"] { color: #22c55e; border-color: rgba(34,197,94,.35); }
.sle-vis-btn[data-vis="registered"] { color: #3b82f6; border-color: rgba(59,130,246,.35); }
.sle-vis-btn[data-vis="subscribers"] { color: #f59e0b; border-color: rgba(245,158,11,.35); }
.sle-vis-btn[data-vis="only_me"] { color: #6b7280; border-color: rgba(107,114,128,.35); }
.sle-empty {
text-align: center; padding: 28px 16px;
color: var(--text-secondary, #aaa); font-size: 14px;
}
.sle-empty i { font-size: 30px; display: block; margin-bottom: 8px; opacity: .45; }
/* Shared popup panel */
.sle-popup-wrap { position: relative; }
.sle-popup {
position: fixed; z-index: 2100;
background: var(--bg-secondary, #1e1e1e); border: 1px solid var(--border-color, #303030);
border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.6); padding: 4px 0;
}
.sle-pop-opt {
display: flex; align-items: center; gap: 9px; padding: 8px 13px;
cursor: pointer; font-size: 13px; color: var(--text-primary, #f1f1f1);
transition: background .1s; white-space: nowrap;
}
.sle-pop-opt:hover { background: rgba(255,255,255,.07); }
.sle-pop-opt.active { font-weight: 600; }
.sle-pop-opt i { font-size: 14px; flex-shrink: 0; width: 16px; text-align: center; }
/* Platform picker panel is scrollable */
.sle-popup.plat-panel {
overflow-y: auto; max-height: 260px;
scrollbar-width: thin; scrollbar-color: var(--border-color, #303030) transparent;
}
.sle-popup.plat-panel::-webkit-scrollbar { width: 4px; }
.sle-popup.plat-panel::-webkit-scrollbar-thumb { background: var(--border-color, #303030); border-radius: 2px; }
/* Platform picker button */
.sle-sp-wrap { position: relative; flex: 0 0 160px; }
.sle-sp-btn {
display: flex; align-items: center; gap: 8px; width: 100%;
background: var(--bg-dark, #0f0f0f); border: 1px solid var(--border-color, #303030);
border-radius: 8px; padding: 9px 10px; color: var(--text-primary, #f1f1f1);
font-size: 13px; font-family: inherit; cursor: pointer; text-align: left;
transition: border-color .2s; outline: none; min-height: 38px; box-sizing: border-box;
}
.sle-sp-btn:hover, .sle-sp-btn:focus-visible { border-color: var(--brand-red, #e61e1e); }
.sle-sp-btn[aria-expanded="true"] { border-color: var(--brand-red, #e61e1e); }
.sle-sp-ico { font-size: 15px; flex-shrink: 0; }
.sle-sp-val { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sle-sp-val.ph { color: var(--text-secondary, #aaa); }
.sle-sp-arr { font-size: 10px; color: var(--text-secondary, #aaa); flex-shrink: 0; transition: transform .2s; }
.sle-sp-btn[aria-expanded="true"] .sle-sp-arr { transform: rotate(180deg); }
</style>
@endonce
@once('sle-script')
<script>
(function () {
if (window.SocialLinksEditor) return;
// Close all popups when clicking outside
document.addEventListener('click', e => {
if (!e.target.closest('.sle-popup-wrap') && !e.target.closest('.sle-sp-wrap')) {
document.querySelectorAll('.sle-popup').forEach(p => { p.hidden = true; });
document.querySelectorAll('[aria-expanded="true"].sle-sp-btn').forEach(b => b.setAttribute('aria-expanded','false'));
}
});
window.SocialLinksEditor = class {
constructor(uid, existing) {
this.root = document.getElementById(uid);
this.entriesEl = this.root.querySelector('[data-sle-entries]');
this.addBtn = this.root.querySelector('[data-sle-add]');
this._idx = 0;
this.PLATFORMS = [
{ key: 'twitter', label: 'Twitter / X', icon: 'bi-twitter-x', placeholder: 'username', type: 'text', defaultVis: 'public' },
{ key: 'instagram', label: 'Instagram', icon: 'bi-instagram', placeholder: 'username', type: 'text', defaultVis: 'public' },
{ key: 'facebook', label: 'Facebook', icon: 'bi-facebook', placeholder: 'username', type: 'text', defaultVis: 'public' },
{ key: 'youtube', label: 'YouTube', icon: 'bi-youtube', placeholder: 'channel', type: 'text', defaultVis: 'public' },
{ key: 'linkedin', label: 'LinkedIn', icon: 'bi-linkedin', placeholder: 'username', type: 'text', defaultVis: 'public' },
{ key: 'tiktok', label: 'TikTok', icon: 'bi-tiktok', placeholder: 'username', type: 'text', defaultVis: 'public' },
{ key: 'whatsapp', label: 'WhatsApp', icon: 'bi-whatsapp', placeholder: '97312345678 (no + or spaces)', type: 'text', defaultVis: 'subscribers' },
{ key: 'website', label: 'Website', icon: 'bi-globe', placeholder: 'https://example.com', type: 'url', defaultVis: 'public' },
{ key: 'google_location', label: 'Google Maps', icon: 'bi-geo-alt-fill', placeholder: 'https://maps.google.com/...', type: 'url', defaultVis: 'public' },
{ key: 'social_phone', label: 'Phone', icon: 'bi-telephone-fill', placeholder: '+97312345678', type: 'text', defaultVis: 'subscribers' },
{ key: 'social_email', label: 'Email', icon: 'bi-envelope-fill', placeholder: 'email@example.com', type: 'email', defaultVis: 'subscribers' },
];
this.VISIBILITIES = [
{ key: 'public', label: 'Public', icon: 'bi-globe2', color: '#22c55e', hint: 'Everyone including guests' },
{ key: 'registered', label: 'Registered', icon: 'bi-person-check-fill', color: '#3b82f6', hint: 'Logged-in users only' },
{ key: 'subscribers', label: 'Subscribers', icon: 'bi-star-fill', color: '#f59e0b', hint: 'Your subscribers only' },
{ key: 'only_me', label: 'Only me', icon: 'bi-lock-fill', color: '#6b7280', hint: 'Hidden from everyone else' },
];
existing.forEach(({ platform, value, visibility }) => {
if (platform && value) {
this.entriesEl.appendChild(this._buildEntry(platform, value, visibility || 'public'));
}
});
this._renderEmpty();
this.addBtn.addEventListener('click', () => {
const row = this._buildEntry('', '', 'public');
this.entriesEl.appendChild(row);
this._renderEmpty();
row.querySelector('.sle-sp-btn').click();
});
}
_platform(key) { return this.PLATFORMS.find(p => p.key === key) || null; }
_visibility(key) { return this.VISIBILITIES.find(v => v.key === key) || this.VISIBILITIES[0]; }
_renderEmpty() {
let empty = this.entriesEl.querySelector('.sle-empty');
if (this.entriesEl.querySelectorAll('.sle-entry').length === 0) {
if (!empty) {
empty = document.createElement('div');
empty.className = 'sle-empty';
empty.innerHTML = '<i class="bi bi-share"></i>No social accounts added yet.';
this.entriesEl.appendChild(empty);
}
} else {
if (empty) empty.remove();
}
}
_closeAllPopups() {
document.querySelectorAll('.sle-popup').forEach(p => { p.hidden = true; });
document.querySelectorAll('[aria-expanded="true"].sle-sp-btn').forEach(b => b.setAttribute('aria-expanded','false'));
}
_togglePopup(trigger, panel) {
const wasOpen = !panel.hidden;
this._closeAllPopups();
if (!wasOpen) {
panel.hidden = false;
if (trigger.classList.contains('sle-sp-btn')) trigger.setAttribute('aria-expanded','true');
const r = trigger.getBoundingClientRect();
const isRight = panel.classList.contains('pop-right');
const goUp = window.innerHeight - r.bottom < 270 && r.top > 270;
panel.style.minWidth = r.width + '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 = '';
}
if (isRight) {
panel.style.left = '';
panel.style.right = (window.innerWidth - r.right) + 'px';
} else {
panel.style.left = r.left + 'px';
panel.style.right = '';
}
}
}
_buildEntry(key, value, visibility) {
const idx = this._idx++;
const p = this._platform(key);
const vis = this._visibility(visibility || (p ? p.defaultVis : 'public'));
const row = document.createElement('div');
row.className = 'sle-entry';
// ── 1. Platform picker ───────────────────────────────────────
const spWrap = document.createElement('div');
spWrap.className = 'sle-sp-wrap';
const spBtn = document.createElement('button');
spBtn.type = 'button'; spBtn.className = 'sle-sp-btn'; spBtn.setAttribute('aria-expanded','false');
const spIco = document.createElement('i');
spIco.className = p ? `sle-sp-ico bi ${p.icon}` : 'sle-sp-ico bi bi-plus-circle';
const spVal = document.createElement('span');
spVal.className = p ? 'sle-sp-val' : 'sle-sp-val ph';
spVal.textContent = p ? p.label : 'Platform';
const spArr = document.createElement('i');
spArr.className = 'bi bi-chevron-down sle-sp-arr';
spBtn.append(spIco, spVal, spArr);
const platInput = document.createElement('input');
platInput.type = 'hidden'; platInput.name = `slink[${idx}][platform]`; platInput.value = key || '';
const spPanel = document.createElement('div');
spPanel.className = 'sle-popup plat-panel'; spPanel.hidden = true;
this.PLATFORMS.forEach(pl => {
const opt = document.createElement('div');
opt.className = `sle-pop-opt${pl.key === key ? ' active' : ''}`;
opt.innerHTML = `<i class="bi ${pl.icon}"></i>${pl.label}`;
opt.addEventListener('click', () => {
spIco.className = `sle-sp-ico bi ${pl.icon}`;
spVal.textContent = pl.label; spVal.classList.remove('ph');
spPanel.querySelectorAll('.sle-pop-opt').forEach(o => o.classList.remove('active'));
opt.classList.add('active');
platInput.value = pl.key;
inp.type = pl.type;
inp.placeholder = pl.placeholder;
inp.disabled = false;
inp.value = '';
// Apply default visibility for this platform
const defVis = this._visibility(pl.defaultVis);
visInput.value = defVis.key;
visBtn.dataset.vis = defVis.key;
visBtn.innerHTML = `<i class="bi ${defVis.icon}"></i>`;
visBtn.title = defVis.label + ' — ' + defVis.hint;
this._closeAllPopups();
inp.focus();
});
spPanel.appendChild(opt);
});
spBtn.addEventListener('click', e => { e.stopPropagation(); this._togglePopup(spBtn, spPanel); });
spWrap.append(spBtn, platInput, spPanel);
// ── 2. Value input ───────────────────────────────────────────
const inp = document.createElement('input');
inp.className = 'form-input sle-entry-input';
inp.type = p ? p.type : 'text';
inp.name = `slink[${idx}][value]`;
inp.placeholder = p ? p.placeholder : 'select platform first';
inp.disabled = !key;
inp.value = value || '';
// ── 3. Visibility picker ─────────────────────────────────────
const visWrap = document.createElement('div');
visWrap.className = 'sle-popup-wrap';
const visInput = document.createElement('input');
visInput.type = 'hidden'; visInput.name = `slink[${idx}][visibility]`; visInput.value = vis.key;
const visBtn = document.createElement('button');
visBtn.type = 'button';
visBtn.className = 'sle-icon-btn sle-vis-btn';
visBtn.dataset.vis = vis.key;
visBtn.title = vis.label + ' — ' + vis.hint;
visBtn.innerHTML = `<i class="bi ${vis.icon}"></i>`;
const visPanel = document.createElement('div');
visPanel.className = 'sle-popup pop-right'; visPanel.hidden = true;
this.VISIBILITIES.forEach(v => {
const opt = document.createElement('div');
opt.className = `sle-pop-opt${v.key === vis.key ? ' active' : ''}`;
opt.style.color = v.key === vis.key ? v.color : '';
opt.innerHTML = `<i class="bi ${v.icon}" style="color:${v.color}"></i>`
+ `<span><strong>${v.label}</strong><br><small style="color:var(--text-secondary,#aaa)">${v.hint}</small></span>`;
opt.addEventListener('click', () => {
visInput.value = v.key;
visBtn.dataset.vis = v.key;
visBtn.innerHTML = `<i class="bi ${v.icon}"></i>`;
visBtn.title = v.label + ' — ' + v.hint;
visPanel.querySelectorAll('.sle-pop-opt').forEach(o => {
o.classList.remove('active'); o.style.color = '';
});
opt.classList.add('active'); opt.style.color = v.color;
visPanel.hidden = true;
});
visPanel.appendChild(opt);
});
visBtn.addEventListener('click', e => { e.stopPropagation(); this._togglePopup(visBtn, visPanel); });
visWrap.append(visInput, visBtn, visPanel);
// ── 4. Remove button ─────────────────────────────────────────
const rm = document.createElement('button');
rm.type = 'button'; rm.className = 'sle-icon-btn sle-remove-btn'; rm.title = 'Remove';
rm.innerHTML = '<i class="bi bi-trash"></i>';
rm.addEventListener('click', () => { row.remove(); this._renderEmpty(); });
row.append(spWrap, inp, visWrap, rm);
return row;
}
};
}());
</script>
@endonce
<div id="{{ $uid }}">
<div data-sle-entries></div>
<div style="margin-top:4px;">
<button type="button" data-sle-add class="action-btn">
<i class="bi bi-plus-lg"></i> <span>Add Account</span>
</button>
</div>
</div>
<script>
(function () {
function boot() { new SocialLinksEditor('{{ $uid }}', @json($existing)); }
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
}());
</script>