Keep notification-read state personal to user/company, preserve server read-only sections during staff synchronization, and respect separate order-create/edit/delete rights. Add visible mobile header session actions and a sticky logout that survives profile RPC failure; scope asynchronous profile/branding to user and workspace. Targeted browser suites and isolated SQL recovery tests passed in run 35506145405, iPhone screenshots reviewed. Full main QA remains required before production promotion. Workspace branding RPC migration is included but has NOT been applied to production Supabase; older servers retain safe owner-only fallback. No live membership/business-data repair is claimed without identifying the reported employee.
180 lines
16 KiB
JavaScript
180 lines
16 KiB
JavaScript
(()=>{
|
||
'use strict';
|
||
if(window.CateriumAccountCenterV1780)return;
|
||
const VERSION='17.8.0-employee-session-20260920';
|
||
const $=(s,r=document)=>r.querySelector(s);
|
||
const qa=(s,r=document)=>[...r.querySelectorAll(s)];
|
||
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'');
|
||
const cloud=()=>window.SunCloudV2||null;
|
||
const client=()=>cloud()?.getClient?.()||null;
|
||
const workspace=()=>cloud()?.getWorkspace?.()||null;
|
||
const session=()=>cloud()?.getSession?.()||null;
|
||
const toast=(text,type='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){console.log('[AccountCenter]',text)}};
|
||
const ROLE_LABELS={admin:'Владелец',manager:'Менеджер',kitchen:'Кухня',courier:'Курьер',viewer:'Просмотр'};
|
||
let snapshot=null;
|
||
let modal=null;
|
||
let lastUserId='';
|
||
let snapshotScope='',loadSequence=0,openSequence=0;
|
||
const accountScope=()=>JSON.stringify([session()?.user?.id||'',workspace()?.id||'']);
|
||
const profileIcon='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a8 8 0 0 1 16 0v2"/></svg>';
|
||
const logoutIcon='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M10 4H4v16h6M14 8l4 4-4 4M8 12h12"/></svg>';
|
||
|
||
function installStyle(){
|
||
if($('#caterium-account-center-style'))return;
|
||
const s=document.createElement('style');s.id='caterium-account-center-style';s.textContent=`
|
||
#cateriumAccountCenter{position:fixed;inset:0;z-index:21000;display:none;place-items:center;padding:22px;background:#102c3d99;backdrop-filter:blur(5px)}
|
||
#cateriumAccountCenter.on{display:grid}
|
||
#cateriumAccountCenter .cac-shell{width:min(980px,100%);max-height:min(860px,92vh);overflow:auto;background:#f6f2ea;border-radius:22px;box-shadow:0 28px 90px #0004;color:#2f2b25}
|
||
#cateriumAccountCenter .cac-head{position:sticky;top:0;z-index:2;background:#f6f2ea;display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:28px 30px 20px;border-bottom:1px solid #d9d1c5}
|
||
#cateriumAccountCenter h2{margin:0;font:500 38px/1.04 Georgia,'Times New Roman',serif;letter-spacing:-.03em;color:#27231f}
|
||
#cateriumAccountCenter .cac-head-actions{display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||
#cateriumAccountCenter .cac-sub{margin-top:7px;color:#8a8379;font-size:14px}
|
||
#cateriumAccountCenter .cac-close{border:0;background:transparent;font-size:31px;color:#5c564d;padding:0 4px}
|
||
#cateriumAccountCenter .cac-body{padding:24px 30px 30px}
|
||
#cateriumAccountCenter .cac-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||
#cateriumAccountCenter .cac-card{background:#fffaf2;border:1px solid #ddd4c7;border-radius:16px;padding:20px}
|
||
#cateriumAccountCenter .cac-card.wide{grid-column:1/-1}
|
||
#cateriumAccountCenter .cac-card h3{margin:0 0 14px;color:#302b25;font-size:17px}
|
||
#cateriumAccountCenter .cac-meta{display:grid;gap:8px;margin:0 0 16px}.cac-meta div{display:flex;justify-content:space-between;gap:20px;padding-bottom:8px;border-bottom:1px dashed #e3dbcf}.cac-meta span{color:#8c857b}.cac-meta b{color:#332f29;text-align:right}
|
||
#cateriumAccountCenter label{display:flex;flex-direction:column;gap:6px;margin:11px 0;color:#777066;font-size:12px;font-weight:700}
|
||
#cateriumAccountCenter input{min-height:48px;border:1px solid #cfc6ba;border-radius:11px;background:#fff;padding:0 13px;color:#2e2a25;font-size:15px;outline:none}
|
||
#cateriumAccountCenter input:focus{border-color:#d0a13c;box-shadow:0 0 0 4px rgba(208,161,60,.11)}
|
||
#cateriumAccountCenter .cac-actions{display:flex;gap:9px;flex-wrap:wrap;margin-top:14px}
|
||
#cateriumAccountCenter button.primary{border:0;border-radius:11px;background:linear-gradient(100deg,#d9aa42,#efca69);color:#171512;padding:12px 16px;font-weight:900}
|
||
#cateriumAccountCenter button.outline{border:1px solid #c9c1b6;border-radius:11px;background:#fffaf2;color:#37332d;padding:11px 15px;font-weight:800}
|
||
#cateriumAccountCenter .cac-note{font-size:12px;color:#8a8379;line-height:1.55;margin-top:9px}
|
||
#cateriumAccountCenter .cac-owner{display:flex;align-items:center;gap:10px;padding:11px 13px;background:#fbf2d9;border:1px solid #ead39a;border-radius:12px;margin-bottom:12px}.cac-owner b{display:block;color:#725317}.cac-owner span{color:#8a6e36;font-size:12px}
|
||
#cateriumAccountCenter .cac-company-actions{display:flex;gap:10px;flex-wrap:wrap}
|
||
#sunCurrentUserLabel{cursor:pointer}
|
||
#sunCurrentUserLabel:hover{text-decoration:underline;text-underline-offset:3px}
|
||
#cateriumAccountButton{display:flex!important}
|
||
#sun-cloud-users-modal [data-caterium-owner-row="1"]{background:#fff9e8;border:1px solid #ead39a;border-radius:10px;padding:10px}
|
||
#sun-cloud-users-modal .caterium-owner-chip{display:inline-flex;align-items:center;border-radius:999px;background:#f4dfad;color:#6d5118;padding:5px 9px;font-size:11px;font-weight:900}
|
||
#cateriumMobileAccountActions{display:none}
|
||
#cateriumMobileAccountActions button svg,#cateriumAccountButton svg{width:20px;height:20px;flex-shrink:0}
|
||
@media(max-width:900px){
|
||
body.sun-enterprise-sidebar header #cateriumMobileAccountActions{display:flex!important;width:100%;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;margin:6px 0 0}
|
||
#cateriumMobileAccountActions button{display:flex!important;align-items:center;justify-content:center;gap:7px;min-height:44px;border:1px solid #b8b4a9;border-radius:10px;padding:8px 12px;background:#faf7ef;color:#333c30;font:700 13px Arial;touch-action:manipulation}
|
||
#cateriumAccountCenter h2{font-size:27px}
|
||
#cateriumAccountCenter .cac-head-actions{gap:8px}
|
||
#cacHeaderLogout{min-height:44px;font-size:12px;padding:8px!important;white-space:nowrap}
|
||
}
|
||
@media print{#cateriumMobileAccountActions,#cateriumAccountCenter{display:none!important}}
|
||
@media(max-width:720px){#cateriumAccountCenter{padding:0;place-items:stretch}#cateriumAccountCenter .cac-shell{width:100%;max-height:none;height:100dvh;border-radius:0}#cateriumAccountCenter .cac-grid{grid-template-columns:1fr}#cateriumAccountCenter .cac-card.wide{grid-column:auto}#cateriumAccountCenter .cac-head,#cateriumAccountCenter .cac-body{padding-left:18px;padding-right:18px}}
|
||
`;document.head.appendChild(s);
|
||
}
|
||
|
||
function ensureModal(){
|
||
if(modal)return modal;
|
||
modal=document.createElement('div');modal.id='cateriumAccountCenter';modal.innerHTML='<div class="cac-shell"><div class="cac-head"><div><h2>Мой аккаунт</h2><div class="cac-sub">Профиль и безопасность Caterium</div></div><div class="cac-head-actions"><button class="outline" id="cacHeaderLogout" type="button">Выйти</button><button class="cac-close" type="button" aria-label="Закрыть">×</button></div></div><div class="cac-body" id="cateriumAccountCenterBody"></div></div>';
|
||
document.body.appendChild(modal);
|
||
$('#cacHeaderLogout',modal).onclick=logout;
|
||
modal.addEventListener('click',e=>{if(e.target===modal||e.target.closest('.cac-close'))close();});
|
||
return modal;
|
||
}
|
||
async function loadSnapshot(){
|
||
const c=client(),ws=workspace();if(!c||!ws?.id)return null;
|
||
const scope=accountScope(),request=++loadSequence;
|
||
const {data,error}=await c.rpc('caterium_account_snapshot',{p_workspace:ws.id});
|
||
if(scope!==accountScope()||request!==loadSequence)return null;
|
||
if(error)throw error;snapshot=data||null;snapshotScope=scope;return snapshot;
|
||
}
|
||
function render(){
|
||
const body=$('#cateriumAccountCenterBody');if(!body)return;
|
||
const ss=session(),ws=workspace(),s=snapshotScope===accountScope()?(snapshot||{}):{},owner=Boolean(s.is_owner);
|
||
const companyNo=s.company_number?`№${s.company_number}`:'—';
|
||
body.innerHTML=`<div class="cac-grid">
|
||
<section class="cac-card">
|
||
<h3>Профиль</h3>
|
||
${owner?'<div class="cac-owner"><div><b>Главный аккаунт компании</b><span>Только этот аккаунт создаёт сотрудников и распределяет права.</span></div></div>':''}
|
||
<div class="cac-meta"><div><span>Компания</span><b>${esc(s.company_name||ws?.name||'—')} · ${esc(companyNo)}</b></div><div><span>Статус</span><b>${esc(owner?'Владелец':ROLE_LABELS[s.role||ws?.role]||s.role||ws?.role||'Сотрудник')}</b></div></div>
|
||
<label>Имя<input id="cacName" value="${esc(s.display_name||ss?.user?.user_metadata?.name||'')}"></label>
|
||
<div class="cac-actions"><button class="primary" id="cacSaveName" type="button">Сохранить имя</button></div>
|
||
</section>
|
||
<section class="cac-card">
|
||
<h3>Email для входа</h3>
|
||
<label>Текущий email<input value="${esc(ss?.user?.email||s.email||'')}" disabled></label>
|
||
<label>Новый email<input id="cacEmail" type="email" autocomplete="email" placeholder="new@email.com"></label>
|
||
<div class="cac-actions"><button class="primary" id="cacSaveEmail" type="button">Изменить email</button></div>
|
||
<div class="cac-note">На новый адрес придёт письмо подтверждения.</div>
|
||
</section>
|
||
<section class="cac-card">
|
||
<h3>Смена пароля</h3>
|
||
<label>Новый пароль<input id="cacPassword" type="password" autocomplete="new-password" minlength="8"></label>
|
||
<label>Повторите пароль<input id="cacPassword2" type="password" autocomplete="new-password" minlength="8"></label>
|
||
<div class="cac-actions"><button class="primary" id="cacSavePassword" type="button">Сменить пароль</button></div>
|
||
<div class="cac-note">Пароль меняется только для вашего аккаунта. Администратор компании его не видит.</div>
|
||
</section>
|
||
${owner?`<section class="cac-card"><h3>Компания и пользователи</h3><p class="cac-note">Создавайте сотрудников, назначайте им рабочую роль и включайте только нужные права.</p><div class="cac-company-actions"><button class="primary" id="cacOpenUsers" type="button">Пользователи и права</button></div></section>`:''}
|
||
<section class="cac-card wide"><h3>Сеанс</h3><div class="cac-actions"><button class="outline" id="cacLogout" type="button">Выйти из аккаунта</button></div></section>
|
||
</div>`;
|
||
$('#cacSaveName').onclick=saveName;$('#cacSaveEmail').onclick=saveEmail;$('#cacSavePassword').onclick=savePassword;$('#cacLogout').onclick=logout;
|
||
const users=$('#cacOpenUsers');if(users)users.onclick=()=>{close();window.SunAdminRBACV3?.openUsers?.();setTimeout(sanitizeRbac,100);};
|
||
}
|
||
async function saveName(){
|
||
const name=String($('#cacName')?.value||'').trim();if(!name)return toast('Введите имя.','warn');
|
||
const c=client();if(!c)return;const btn=$('#cacSaveName');btn.disabled=true;
|
||
try{const {data,error}=await c.rpc('caterium_update_my_name',{p_display_name:name});if(error)throw error;const r=await c.auth.updateUser({data:{name:data||name}});if(r.error)throw r.error;await cloud()?.reloadMemberships?.();await loadSnapshot();render();window.SunAdminRBACV3?.apply?.();toast('Имя изменено.','success');}
|
||
catch(e){toast(e?.message||String(e),'error',6500)}finally{btn.disabled=false}
|
||
}
|
||
async function saveEmail(){
|
||
const email=String($('#cacEmail')?.value||'').trim().toLowerCase();if(!email)return toast('Введите новый email.','warn');
|
||
const c=client();if(!c)return;const btn=$('#cacSaveEmail');btn.disabled=true;
|
||
try{const {error}=await c.auth.updateUser({email});if(error)throw error;$('#cacEmail').value='';toast('На новый email отправлено письмо подтверждения.','success',6500)}catch(e){toast(e?.message||String(e),'error',6500)}finally{btn.disabled=false}
|
||
}
|
||
async function savePassword(){
|
||
const p=String($('#cacPassword')?.value||''),p2=String($('#cacPassword2')?.value||'');
|
||
if(p.length<8)return toast('Пароль должен быть не короче 8 символов.','warn');if(p!==p2)return toast('Пароли не совпадают.','warn');
|
||
const c=client();if(!c)return;const btn=$('#cacSavePassword');btn.disabled=true;
|
||
try{const {error}=await c.auth.updateUser({password:p});if(error)throw error;$('#cacPassword').value='';$('#cacPassword2').value='';toast('Пароль изменён.','success')}
|
||
catch(e){toast(e?.message||String(e),'error',6500)}finally{btn.disabled=false}
|
||
}
|
||
async function logout(){if(typeof cloud()?.signOut==='function')return cloud().signOut();try{await client()?.auth.signOut({scope:'local'})}finally{location.reload()}}
|
||
async function open(){
|
||
ensureModal();const request=++openSequence,scope=accountScope();modal.classList.add('on');render();
|
||
const body=$('#cateriumAccountCenterBody');body.setAttribute('aria-busy','true');
|
||
try{await loadSnapshot();if(request===openSequence&&scope===accountScope())render()}
|
||
catch(e){if(request===openSequence&&scope===accountScope()){render();const note=document.createElement('p');note.className='cac-note';note.textContent='Не удалось обновить данные профиля. Выход из аккаунта доступен.';body.prepend(note)}}
|
||
finally{if(request===openSequence)body.removeAttribute('aria-busy')}
|
||
}
|
||
function close(){openSequence++;modal?.classList.remove('on')}
|
||
function sanitizeRbac(){
|
||
const body=$('#sunRbacBody');if(!body)return;
|
||
qa('select option[value="admin"]',body).forEach(o=>o.remove());
|
||
qa('[data-rbac-perm="users.manage"]',body).forEach(i=>{i.checked=false;i.disabled=true;const row=i.closest('label');if(row)row.title='Это право принадлежит только главному аккаунту компании';});
|
||
if(snapshot?.is_owner&&snapshot?.user_id){
|
||
const btn=body.querySelector(`[data-rbac-edit="${CSS.escape(String(snapshot.user_id))}"]`);
|
||
if(btn){const row=btn.closest('.sun-rbac-member');if(row){row.dataset.cateriumOwnerRow='1';const role=row.querySelector('.role-chip');if(role)role.textContent='Владелец';btn.outerHTML='<span class="caterium-owner-chip">Главный аккаунт</span>';}}
|
||
}
|
||
const addRole=$('#sunRbacEmployeeRole',body);if(addRole)qa('option[value="admin"]',addRole).forEach(o=>o.remove());
|
||
}
|
||
function ensureSidebarEntry(){
|
||
const footer=$('.sun-side-footer');if(!footer||!session()?.user)return;
|
||
let button=$('#cateriumAccountButton');
|
||
if(!button){
|
||
button=document.createElement('button');
|
||
button.id='cateriumAccountButton';button.type='button';button.className='sun-side-action';button.title='Профиль, email и пароль';
|
||
button.innerHTML=profileIcon+'<span>Мой аккаунт</span>';
|
||
footer.insertBefore(button,footer.querySelector('#sunLogoutBtn')||footer.firstChild);
|
||
}
|
||
if(!button.dataset.cateriumAccountBound){button.dataset.cateriumAccountBound='1';button.addEventListener('click',open)}
|
||
}
|
||
function ensureMobileEntry(){
|
||
const header=$('body > header');if(!header)return;
|
||
let bar=$('#cateriumMobileAccountActions');
|
||
if(!session()?.user){bar?.remove();return}
|
||
if(!bar){bar=document.createElement('div');bar.id='cateriumMobileAccountActions';bar.innerHTML='<button id="cateriumMobileAccountButton" type="button" aria-label="Мой аккаунт">'+profileIcon+'<span>Профиль</span></button><button id="cateriumMobileLogout" type="button" aria-label="Выйти из аккаунта">'+logoutIcon+'<span>Выйти</span></button>';header.appendChild(bar);$('#cateriumMobileAccountButton',bar).onclick=open;$('#cateriumMobileLogout',bar).onclick=logout;}
|
||
}
|
||
function bindEntry(){
|
||
const label=$('#sunCurrentUserLabel');if(label&&!label.dataset.cateriumAccountBound){label.dataset.cateriumAccountBound='1';label.title='Открыть личный кабинет';label.addEventListener('click',open)}
|
||
const uid=accountScope();if(uid!==lastUserId){lastUserId=uid;snapshot=null;snapshotScope='';loadSequence++;close();}
|
||
ensureSidebarEntry();ensureMobileEntry();sanitizeRbac();
|
||
}
|
||
installStyle();ensureModal();bindEntry();
|
||
const obs=new MutationObserver(bindEntry);obs.observe(document.documentElement,{childList:true,subtree:true});
|
||
window.addEventListener('sun:cloud-tenant-changing',()=>{snapshot=null;snapshotScope='';loadSequence++;close()});
|
||
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(bindEntry,0));
|
||
setInterval(()=>{if(!document.hidden)bindEntry()},3000);
|
||
window.CateriumAccountCenterV1780=Object.freeze({VERSION,open,close,reload:async()=>{await loadSnapshot();render();return snapshot}});
|
||
})();
|