From 02bcc2393163a466e51efa29aab6ba43957e4050 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 05:54:44 +0300 Subject: [PATCH 01/21] feat: apply cream Caterium login visual --- public/core/login-signature-v1776.js | 173 ++++++++++++++++++--------- 1 file changed, 116 insertions(+), 57 deletions(-) diff --git a/public/core/login-signature-v1776.js b/public/core/login-signature-v1776.js index 791711a..997bbb9 100644 --- a/public/core/login-signature-v1776.js +++ b/public/core/login-signature-v1776.js @@ -1,102 +1,161 @@ (()=>{ 'use strict'; if(window.CateriumLoginSignatureV1776)return; - const VERSION='17.7.6-signature-login-v2'; + const VERSION='17.7.7-cream-login-v1'; const $=(s,r=document)=>r.querySelector(s); const boot=()=>$('#cateriumAuthBootV1776'); const removeBoot=()=>{const b=boot();if(b){b.classList.add('leave');setTimeout(()=>b.remove(),180)}}; + const logoSvg=``; + const flowSvg=``; + const flowBottomSvg=``; function installStyle(){ if($('#caterium-login-signature-v1776-style'))return; const style=document.createElement('style'); style.id='caterium-login-signature-v1776-style'; style.textContent=` - body.sun-cloud-auth-required{overflow:hidden!important;background:#091019!important} + body.sun-cloud-auth-required{overflow:hidden!important;background:#f5f0e7!important} body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important} #sunCloudAuthGateV3.sun-cloud-auth-gate{ - position:fixed!important;inset:0!important;z-index:20000!important;overflow:hidden!important; - display:grid!important;grid-template-columns:minmax(520px,47%) 1fr!important;place-items:stretch!important; - padding:0!important;background:#091019!important;color:#fff!important; + position:fixed!important;inset:0!important;z-index:20000!important;overflow:auto!important; + display:grid!important;place-items:center!important;padding:clamp(32px,6vh,78px) clamp(24px,8vw,120px)!important; + background: + radial-gradient(circle at 18% 18%,rgba(255,255,255,.92),transparent 34%), + radial-gradient(circle at 77% 22%,rgba(255,255,255,.5),transparent 28%), + linear-gradient(135deg,#f8f4ec 0%,#f2ece1 56%,#f7f3eb 100%)!important; + color:#282621!important; } #sunCloudAuthGateV3.sun-cloud-auth-gate::before{ - content:'';position:absolute;z-index:0;inset:0 47% 0 0;pointer-events:none; - background:radial-gradient(circle at 74% 48%,rgba(226,169,59,.13),transparent 18%),linear-gradient(125deg,#091019 0%,#111922 62%,#17191b 100%); + content:'C';position:fixed;z-index:0;left:-7vw;top:49%;transform:translateY(-50%);pointer-events:none; + font:400 min(72vw,860px)/.72 Georgia,'Times New Roman',serif;color:rgba(73,67,59,.032);letter-spacing:-.08em; } #sunCloudAuthGateV3.sun-cloud-auth-gate::after{ - content:'';position:absolute;z-index:0;top:0;right:0;bottom:0;width:53%;pointer-events:none; - background-image:linear-gradient(90deg,#111922 0%,rgba(17,25,34,.52) 12%,rgba(0,0,0,.12) 44%,rgba(0,0,0,.08) 100%),url('offer-gallery/002.jpg'); - background-size:cover;background-position:center;filter:saturate(.92) contrast(1.04); + content:'';position:fixed;z-index:0;right:-7vw;bottom:-14vh;width:min(48vw,720px);height:min(70vh,850px);pointer-events:none; + background: + radial-gradient(ellipse at 58% 20%,rgba(67,64,58,.14) 0 7%,transparent 8%), + radial-gradient(ellipse at 36% 35%,rgba(67,64,58,.13) 0 6%,transparent 7%), + radial-gradient(ellipse at 68% 47%,rgba(67,64,58,.12) 0 8%,transparent 9%), + radial-gradient(ellipse at 43% 62%,rgba(67,64,58,.11) 0 7%,transparent 8%), + linear-gradient(106deg,transparent 43%,rgba(67,64,58,.08) 44% 46%,transparent 47%); + filter:blur(18px);transform:rotate(-17deg);opacity:.48; } #sunCloudAuthGateV3 .sun-cloud-auth-card{ - grid-column:1!important;align-self:center!important;justify-self:center!important;position:relative!important;z-index:3!important; - width:min(470px,calc(100% - 72px))!important;margin:0!important;padding:24px 0 34px!important; - border:0!important;border-radius:0!important;background:transparent!important;color:#f7f4ee!important; + position:relative!important;z-index:3!important;width:min(790px,100%)!important;margin:0!important;padding:0 0 58px!important; + border:0!important;border-radius:0!important;background:transparent!important;color:#2c2924!important; box-shadow:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important; } - #sunCloudAuthGateV3 .sun-cloud-auth-card::before{ - content:'C';position:fixed;z-index:-1;left:-7vw;top:50%;transform:translateY(-53%);pointer-events:none; - font:400 min(68vw,790px)/.72 Georgia,'Times New Roman',serif;color:transparent; - -webkit-text-stroke:min(5.8vw,82px) rgba(255,255,255,.09);text-stroke:min(5.8vw,82px) rgba(255,255,255,.09); - } - #sunCloudAuthGateV3 .caterium-signature-brand{display:flex;align-items:center;gap:12px;margin:0 0 42px;color:#fff} - #sunCloudAuthGateV3 .caterium-signature-c{position:relative;display:inline-grid;place-items:center;width:52px;height:52px;font:500 58px/1 Georgia,'Times New Roman',serif;letter-spacing:-8px;color:#fff} - #sunCloudAuthGateV3 .caterium-signature-c i{position:absolute;right:-2px;top:23px;width:9px;height:9px;border-radius:50%;background:#e3aa3f;box-shadow:0 0 20px rgba(227,170,63,.3)} - #sunCloudAuthGateV3 .caterium-signature-word{font:500 30px/1 Georgia,'Times New Roman',serif;color:#fff} - #sunCloudAuthGateV3 .sun-cloud-auth-brand{display:block!important;margin:0 0 27px!important} + #sunCloudAuthGateV3 .caterium-signature-brand{display:flex;align-items:center;gap:13px;margin:0 0 clamp(46px,7vh,88px)!important;color:#35312b} + #sunCloudAuthGateV3 .caterium-signature-mark{display:block;width:62px;height:62px;flex:0 0 62px;color:#35312b} + #sunCloudAuthGateV3 .caterium-signature-mark svg{display:block;width:100%;height:100%;overflow:visible} + #sunCloudAuthGateV3 .caterium-signature-word{font:500 36px/1 Georgia,'Times New Roman',serif;color:#26231f;letter-spacing:-.02em} + #sunCloudAuthGateV3 .sun-cloud-auth-brand{display:block!important;margin:0 0 35px!important} #sunCloudAuthGateV3 .sun-cloud-auth-brand img{display:none!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{margin:0!important;max-width:440px;color:#fff!important;font:500 clamp(37px,3.25vw,52px)/1.04 Georgia,'Times New Roman',serif!important;letter-spacing:-.025em!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:10px!important;color:rgba(255,255,255,.67)!important;font-size:15px!important;line-height:1.5!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card>p.hint{color:rgba(255,255,255,.68)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card label{display:block!important;margin:14px 0 0!important;color:rgba(255,255,255,.72)!important;font-size:12px!important;font-weight:700!important;letter-spacing:.015em!important} + #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{margin:0!important;max-width:720px;color:#24211d!important;font:500 clamp(48px,5.2vw,76px)/.99 Georgia,'Times New Roman',serif!important;letter-spacing:-.045em!important} + #sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:18px!important;color:#8e887f!important;font-size:clamp(16px,1.55vw,21px)!important;line-height:1.45!important;font-weight:400!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card>p.hint{color:#817b72!important;font-size:14px!important;line-height:1.55!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card label{display:block!important;margin:14px 0 0!important;color:#716b62!important;font-size:12px!important;font-weight:700!important;letter-spacing:.01em!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-clean-field{font-size:0!important;color:transparent!important} #sunCloudAuthGateV3 .sun-cloud-auth-card input,#sunCloudAuthGateV3 .sun-cloud-auth-card select,#sunCloudAuthGateV3 .sun-cloud-auth-card textarea{ - width:100%!important;height:48px!important;margin-top:6px!important;padding:0 15px!important;border:1px solid rgba(255,255,255,.27)!important;border-radius:9px!important; - background:rgba(8,12,16,.36)!important;color:#fff!important;box-shadow:none!important;outline:none!important; + width:100%!important;min-height:58px!important;margin-top:7px!important;padding:0 18px!important;border:1px solid #bdb5aa!important;border-radius:13px!important; + background:rgba(255,255,255,.13)!important;color:#312e29!important;box-shadow:none!important;outline:none!important;font-size:16px!important;font-weight:500!important; } - #sunCloudAuthGateV3 .sun-cloud-auth-card input:focus{border-color:#e3aa3f!important;box-shadow:0 0 0 3px rgba(227,170,63,.12)!important;background:rgba(8,12,16,.58)!important} - #sunCloudAuthGateV3 .sun-auth-eye{right:7px!important;bottom:7px!important;color:rgba(255,255,255,.78)!important;background:transparent!important} - #sunCloudAuthGateV3 .sun-auth-eye:hover{background:rgba(255,255,255,.08)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions{display:block!important;margin-top:20px!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-clean-field input{margin-top:0!important;padding-left:58px!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card input::placeholder{color:#9c958c!important;opacity:1!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card input:focus{border-color:#d7a632!important;box-shadow:0 0 0 4px rgba(216,167,50,.12)!important;background:rgba(255,255,255,.5)!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-clean-field{position:relative!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-email-field::before{ + content:'';position:absolute;z-index:2;left:20px;top:17px;width:22px;height:16px;border:1.8px solid #777168;border-radius:2px;pointer-events:none; + } + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-email-field::after{ + content:'';position:absolute;z-index:2;left:22px;top:16px;width:17px;height:11px;border-left:1.8px solid #777168;border-bottom:1.8px solid #777168;transform:rotate(-45deg);pointer-events:none; + } + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-password-field::before{ + content:'';position:absolute;z-index:2;left:22px;top:19px;width:17px;height:15px;border:1.8px solid #777168;border-radius:3px;pointer-events:none; + } + #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-password-field::after{ + content:'';position:absolute;z-index:2;left:26px;top:11px;width:9px;height:10px;border:1.8px solid #777168;border-bottom:0;border-radius:8px 8px 0 0;pointer-events:none; + } + #sunCloudAuthGateV3 .sun-auth-password{display:block!important;position:relative!important} + #sunCloudAuthGateV3 .sun-auth-password input{padding-right:58px!important} + #sunCloudAuthGateV3 .sun-auth-eye{right:9px!important;bottom:10px!important;width:38px!important;height:38px!important;color:#777168!important;background:transparent!important;border-radius:9px!important;font-size:18px!important} + #sunCloudAuthGateV3 .sun-auth-eye:hover{background:rgba(65,60,53,.06)!important} + #sunCloudAuthGateV3 .sun-cloud-auth-actions{display:block!important;margin-top:25px!important} #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{ - width:100%!important;min-height:50px!important;border:0!important;border-radius:9px!important;background:linear-gradient(100deg,#dba137,#efc45f)!important; - color:#111!important;font-size:15px!important;font-weight:900!important;box-shadow:0 13px 30px rgba(207,145,37,.17)!important; + width:100%!important;min-height:62px!important;border:0!important;border-radius:13px!important; + background:linear-gradient(100deg,#d9aa42,#efca69)!important;color:#171512!important;font-size:18px!important;font-weight:900!important; + box-shadow:0 14px 34px rgba(184,135,34,.14)!important;transition:transform .14s ease,filter .14s ease!important; } - #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary:hover,#sunCloudAuthGateV3 #sunGateSubmitV3:hover{filter:brightness(1.05)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .outline{width:100%!important;margin-top:8px!important;border-color:rgba(255,255,255,.22)!important;background:rgba(255,255,255,.04)!important;color:#fff!important} - #sunCloudAuthGateV3 .sun-cloud-auth-error{min-height:18px!important;margin-top:8px!important;color:#ffb6ad!important;font-size:12px!important} - #sunCloudAuthGateV3 .sun-auth-switch{margin:20px 0 0!important;padding-top:19px!important;border-top:1px solid rgba(255,255,255,.16)!important;color:rgba(255,255,255,.7)!important;font-size:13px!important} - #sunCloudAuthGateV3 .sun-auth-switch button{border:0!important;background:transparent!important;color:#e8b34c!important;text-decoration:none!important;font-weight:900!important} - #sunCloudAuthGateV3 .caterium-signature-caption{position:fixed;left:42px;bottom:32px;z-index:4;color:rgba(255,255,255,.6);font-size:9px;letter-spacing:.3em;text-transform:uppercase;pointer-events:none} - #sunCloudAuthGateV3 .caterium-signature-caption::before{content:'';display:block;width:45px;height:2px;margin-bottom:12px;background:#e3aa3f} - #sunCloudAuthGateV3 .caterium-signature-manifesto{position:fixed;right:42px;top:36px;z-index:4;width:150px;color:rgba(255,255,255,.7);font-size:9px;line-height:1.9;letter-spacing:.23em;text-transform:uppercase;pointer-events:none;text-shadow:0 1px 10px #0008} - #sunCloudAuthGateV3 .caterium-signature-manifesto::after{content:'';display:block;width:42px;height:2px;margin-top:12px;background:#e3aa3f} - @media(max-width:900px){ - #sunCloudAuthGateV3.sun-cloud-auth-gate{display:grid!important;grid-template-columns:1fr!important;background:#091019!important} - #sunCloudAuthGateV3.sun-cloud-auth-gate::before{inset:0;background:linear-gradient(180deg,rgba(7,13,20,.72),rgba(7,13,20,.9))} - #sunCloudAuthGateV3.sun-cloud-auth-gate::after{width:100%;opacity:.55;background-image:linear-gradient(180deg,rgba(5,10,15,.38),rgba(5,10,15,.88)),url('offer-gallery/002.jpg');background-position:center} - #sunCloudAuthGateV3 .sun-cloud-auth-card{grid-column:1!important;width:min(460px,calc(100% - 36px))!important;padding:20px 20px 24px!important;border:1px solid rgba(255,255,255,.12)!important;border-radius:18px!important;background:rgba(7,12,17,.66)!important;backdrop-filter:blur(13px)!important;-webkit-backdrop-filter:blur(13px)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card::before{left:-52vw;top:50%;font-size:120vw;-webkit-text-stroke:14vw rgba(255,255,255,.07)} - #sunCloudAuthGateV3 .caterium-signature-brand{margin-bottom:28px} - #sunCloudAuthGateV3 .caterium-signature-word{font-size:25px} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:32px!important} - #sunCloudAuthGateV3 .caterium-signature-caption,#sunCloudAuthGateV3 .caterium-signature-manifesto{display:none!important} + #sunCloudAuthGateV3 #sunGateSubmitV3::after{content:' →';font-size:24px;font-weight:500;margin-left:13px} + #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary:hover,#sunCloudAuthGateV3 #sunGateSubmitV3:hover{filter:brightness(1.025)!important;transform:translateY(-1px)!important} + #sunCloudAuthGateV3 .sun-cloud-auth-actions .outline{width:100%!important;margin-top:9px!important;min-height:48px!important;border:1px solid #c9c1b6!important;border-radius:11px!important;background:rgba(255,255,255,.25)!important;color:#37332d!important} + #sunCloudAuthGateV3 .sun-cloud-auth-error{min-height:19px!important;margin-top:10px!important;color:#a64c40!important;font-size:12px!important} + #sunCloudAuthGateV3 .sun-auth-switch{margin:26px 0 0!important;padding-top:25px!important;border-top:1px solid #cfc7bc!important;color:#898279!important;font-size:14px!important;text-align:center!important} + #sunCloudAuthGateV3 .sun-auth-switch button{border:0!important;background:transparent!important;color:#c18f24!important;text-decoration:none!important;font-weight:900!important;font-size:14px!important} + #sunCloudAuthGateV3 .sun-auth-switch button:hover{text-decoration:underline!important;text-underline-offset:4px!important} + #sunCloudAuthGateV3 .caterium-signature-caption{position:fixed;left:42px;bottom:31px;z-index:4;color:#9d968d;font-size:9px;letter-spacing:.31em;text-transform:uppercase;pointer-events:none} + #sunCloudAuthGateV3 .caterium-signature-caption::before{content:'';display:block;width:48px;height:2px;margin-bottom:13px;background:#d3a43c} + #sunCloudAuthGateV3 .caterium-signature-manifesto{position:fixed;right:56px;top:44px;z-index:4;width:270px;color:#a19a91;font-size:10px;line-height:1.9;letter-spacing:.28em;text-transform:uppercase;pointer-events:none;white-space:pre-line} + #sunCloudAuthGateV3 .caterium-flow-top{position:fixed;z-index:1;right:5.5vw;top:13vh;width:min(37vw,470px);height:auto;opacity:.86;pointer-events:none;color:#a7a097} + #sunCloudAuthGateV3 .caterium-flow-bottom{position:fixed;z-index:1;left:4vw;bottom:8vh;width:min(28vw,350px);height:auto;opacity:.65;pointer-events:none;color:#aaa399} + #sunCloudAuthGateV3 .caterium-flow-top svg,#sunCloudAuthGateV3 .caterium-flow-bottom svg{display:block;width:100%;height:auto;overflow:visible} + #sunCloudAuthGateV3 .caterium-flow-top path,#sunCloudAuthGateV3 .caterium-flow-bottom path{fill:none;stroke:currentColor;stroke-width:1.6;vector-effect:non-scaling-stroke} + #sunCloudAuthGateV3 .caterium-flow-top circle,#sunCloudAuthGateV3 .caterium-flow-bottom circle{fill:currentColor;stroke:none} + #sunCloudAuthGateV3 .caterium-flow-top circle.gold{fill:#e5a313} + @media(max-width:980px){ + #sunCloudAuthGateV3.sun-cloud-auth-gate{padding:34px 28px 70px!important;place-items:start center!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card{width:min(720px,100%)!important;padding-top:12px!important} + #sunCloudAuthGateV3 .caterium-signature-brand{margin-bottom:50px!important} + #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:clamp(44px,8vw,66px)!important} + #sunCloudAuthGateV3 .caterium-signature-manifesto{display:none!important} + #sunCloudAuthGateV3 .caterium-flow-top{right:-60px;top:125px;width:360px;opacity:.55} + #sunCloudAuthGateV3 .caterium-flow-bottom{display:none!important} + } + @media(max-width:620px){ + body.sun-cloud-auth-required{overflow:auto!important} + #sunCloudAuthGateV3.sun-cloud-auth-gate{min-height:100dvh!important;padding:25px 18px 50px!important;place-items:start center!important} + #sunCloudAuthGateV3.sun-cloud-auth-gate::before{left:-62vw;top:44%;font-size:132vw;color:rgba(73,67,59,.026)} + #sunCloudAuthGateV3.sun-cloud-auth-gate::after{right:-48vw;bottom:-8vh;width:110vw;height:50vh;opacity:.32} + #sunCloudAuthGateV3 .sun-cloud-auth-card{width:100%!important;padding:0!important} + #sunCloudAuthGateV3 .caterium-signature-brand{gap:10px;margin:0 0 42px!important} + #sunCloudAuthGateV3 .caterium-signature-mark{width:50px;height:50px;flex-basis:50px} + #sunCloudAuthGateV3 .caterium-signature-word{font-size:29px} + #sunCloudAuthGateV3 .sun-cloud-auth-brand{margin-bottom:27px!important} + #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:clamp(38px,12vw,52px)!important;line-height:1.01!important;letter-spacing:-.04em!important} + #sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:13px!important;font-size:16px!important} + #sunCloudAuthGateV3 .sun-cloud-auth-card input{min-height:55px!important} + #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{min-height:58px!important} + #sunCloudAuthGateV3 .caterium-flow-top{right:-125px;top:100px;width:330px;opacity:.32} + #sunCloudAuthGateV3 .caterium-signature-caption{display:none!important} } `; document.head.appendChild(style); } + function cleanField(gate,id,placeholder,kind){ + const input=$(`#${id}`,gate);if(!input)return; + input.placeholder=placeholder; + const label=input.closest('label');if(!label)return; + label.classList.add('caterium-clean-field',kind==='email'?'caterium-email-field':'caterium-password-field'); + } + function decorate(gate){ if(!gate)return false; const card=$('.sun-cloud-auth-card',gate);if(!card)return false; gate.classList.add('caterium-signature-ready'); if(!$('.caterium-signature-brand',card)){ const brand=document.createElement('div');brand.className='caterium-signature-brand';brand.setAttribute('aria-label','Caterium'); - brand.innerHTML='CCaterium';card.prepend(brand); + brand.innerHTML=`${logoSvg}Caterium`;card.prepend(brand); } - if(!$('.caterium-signature-caption',gate)){const n=document.createElement('div');n.className='caterium-signature-caption';n.textContent='Caterium · управление кейтерингом';gate.appendChild(n)} - if(!$('.caterium-signature-manifesto',gate)){const n=document.createElement('div');n.className='caterium-signature-manifesto';n.textContent='Создаём впечатления, которые объединяют';gate.appendChild(n)} + if(!$('.caterium-signature-caption',gate)){const n=document.createElement('div');n.className='caterium-signature-caption';n.textContent='Caterium · вкус в деталях';gate.appendChild(n)} + if(!$('.caterium-signature-manifesto',gate)){const n=document.createElement('div');n.className='caterium-signature-manifesto';n.textContent='Простые решения\nдля больших событий';gate.appendChild(n)} + if(!$('.caterium-flow-top',gate)){const n=document.createElement('div');n.className='caterium-flow-top';n.innerHTML=flowSvg;gate.appendChild(n)} + if(!$('.caterium-flow-bottom',gate)){const n=document.createElement('div');n.className='caterium-flow-bottom';n.innerHTML=flowBottomSvg;gate.appendChild(n)} const title=$('#sunGateTitleV3',gate),subtitle=$('#sunGateSubtitleV3',gate); if(title&&title.textContent.trim()==='Вход в Caterium')title.textContent='Войти в рабочее пространство'; if(subtitle&&subtitle.textContent.trim()==='Введите данные своего аккаунта')subtitle.textContent='Ваши заказы. Ваша команда. Ваш результат.'; + cleanField(gate,'sunGateEmailV3','Email','email'); + cleanField(gate,'sunGatePasswordV3','Пароль','password'); + cleanField(gate,'sunGatePassword2V27','Подтвердите пароль','password'); removeBoot();return true; } From 15696289447789774be01149833c328549f0744c Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 05:56:39 +0300 Subject: [PATCH 02/21] feat: add approved Caterium mark for light login --- public/caterium-mark-light.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/caterium-mark-light.svg diff --git a/public/caterium-mark-light.svg b/public/caterium-mark-light.svg new file mode 100644 index 0000000..95ff37e --- /dev/null +++ b/public/caterium-mark-light.svg @@ -0,0 +1 @@ + \ No newline at end of file From 120bd1247414f46c8596495704902b2271dac536 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 05:57:35 +0300 Subject: [PATCH 03/21] fix: use approved Caterium logo on cream login --- public/core/login-signature-v1776.js | 197 ++++----------------------- 1 file changed, 28 insertions(+), 169 deletions(-) diff --git a/public/core/login-signature-v1776.js b/public/core/login-signature-v1776.js index 997bbb9..d5acf67 100644 --- a/public/core/login-signature-v1776.js +++ b/public/core/login-signature-v1776.js @@ -1,171 +1,30 @@ (()=>{ - 'use strict'; - if(window.CateriumLoginSignatureV1776)return; - const VERSION='17.7.7-cream-login-v1'; - const $=(s,r=document)=>r.querySelector(s); - const boot=()=>$('#cateriumAuthBootV1776'); - const removeBoot=()=>{const b=boot();if(b){b.classList.add('leave');setTimeout(()=>b.remove(),180)}}; - const logoSvg=``; - const flowSvg=``; - const flowBottomSvg=``; - - function installStyle(){ - if($('#caterium-login-signature-v1776-style'))return; - const style=document.createElement('style'); - style.id='caterium-login-signature-v1776-style'; - style.textContent=` - body.sun-cloud-auth-required{overflow:hidden!important;background:#f5f0e7!important} - body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important} - #sunCloudAuthGateV3.sun-cloud-auth-gate{ - position:fixed!important;inset:0!important;z-index:20000!important;overflow:auto!important; - display:grid!important;place-items:center!important;padding:clamp(32px,6vh,78px) clamp(24px,8vw,120px)!important; - background: - radial-gradient(circle at 18% 18%,rgba(255,255,255,.92),transparent 34%), - radial-gradient(circle at 77% 22%,rgba(255,255,255,.5),transparent 28%), - linear-gradient(135deg,#f8f4ec 0%,#f2ece1 56%,#f7f3eb 100%)!important; - color:#282621!important; - } - #sunCloudAuthGateV3.sun-cloud-auth-gate::before{ - content:'C';position:fixed;z-index:0;left:-7vw;top:49%;transform:translateY(-50%);pointer-events:none; - font:400 min(72vw,860px)/.72 Georgia,'Times New Roman',serif;color:rgba(73,67,59,.032);letter-spacing:-.08em; - } - #sunCloudAuthGateV3.sun-cloud-auth-gate::after{ - content:'';position:fixed;z-index:0;right:-7vw;bottom:-14vh;width:min(48vw,720px);height:min(70vh,850px);pointer-events:none; - background: - radial-gradient(ellipse at 58% 20%,rgba(67,64,58,.14) 0 7%,transparent 8%), - radial-gradient(ellipse at 36% 35%,rgba(67,64,58,.13) 0 6%,transparent 7%), - radial-gradient(ellipse at 68% 47%,rgba(67,64,58,.12) 0 8%,transparent 9%), - radial-gradient(ellipse at 43% 62%,rgba(67,64,58,.11) 0 7%,transparent 8%), - linear-gradient(106deg,transparent 43%,rgba(67,64,58,.08) 44% 46%,transparent 47%); - filter:blur(18px);transform:rotate(-17deg);opacity:.48; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card{ - position:relative!important;z-index:3!important;width:min(790px,100%)!important;margin:0!important;padding:0 0 58px!important; - border:0!important;border-radius:0!important;background:transparent!important;color:#2c2924!important; - box-shadow:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important; - } - #sunCloudAuthGateV3 .caterium-signature-brand{display:flex;align-items:center;gap:13px;margin:0 0 clamp(46px,7vh,88px)!important;color:#35312b} - #sunCloudAuthGateV3 .caterium-signature-mark{display:block;width:62px;height:62px;flex:0 0 62px;color:#35312b} - #sunCloudAuthGateV3 .caterium-signature-mark svg{display:block;width:100%;height:100%;overflow:visible} - #sunCloudAuthGateV3 .caterium-signature-word{font:500 36px/1 Georgia,'Times New Roman',serif;color:#26231f;letter-spacing:-.02em} - #sunCloudAuthGateV3 .sun-cloud-auth-brand{display:block!important;margin:0 0 35px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand img{display:none!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{margin:0!important;max-width:720px;color:#24211d!important;font:500 clamp(48px,5.2vw,76px)/.99 Georgia,'Times New Roman',serif!important;letter-spacing:-.045em!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:18px!important;color:#8e887f!important;font-size:clamp(16px,1.55vw,21px)!important;line-height:1.45!important;font-weight:400!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card>p.hint{color:#817b72!important;font-size:14px!important;line-height:1.55!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card label{display:block!important;margin:14px 0 0!important;color:#716b62!important;font-size:12px!important;font-weight:700!important;letter-spacing:.01em!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-clean-field{font-size:0!important;color:transparent!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card input,#sunCloudAuthGateV3 .sun-cloud-auth-card select,#sunCloudAuthGateV3 .sun-cloud-auth-card textarea{ - width:100%!important;min-height:58px!important;margin-top:7px!important;padding:0 18px!important;border:1px solid #bdb5aa!important;border-radius:13px!important; - background:rgba(255,255,255,.13)!important;color:#312e29!important;box-shadow:none!important;outline:none!important;font-size:16px!important;font-weight:500!important; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-clean-field input{margin-top:0!important;padding-left:58px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card input::placeholder{color:#9c958c!important;opacity:1!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card input:focus{border-color:#d7a632!important;box-shadow:0 0 0 4px rgba(216,167,50,.12)!important;background:rgba(255,255,255,.5)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-clean-field{position:relative!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-email-field::before{ - content:'';position:absolute;z-index:2;left:20px;top:17px;width:22px;height:16px;border:1.8px solid #777168;border-radius:2px;pointer-events:none; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-email-field::after{ - content:'';position:absolute;z-index:2;left:22px;top:16px;width:17px;height:11px;border-left:1.8px solid #777168;border-bottom:1.8px solid #777168;transform:rotate(-45deg);pointer-events:none; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-password-field::before{ - content:'';position:absolute;z-index:2;left:22px;top:19px;width:17px;height:15px;border:1.8px solid #777168;border-radius:3px;pointer-events:none; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card label.caterium-password-field::after{ - content:'';position:absolute;z-index:2;left:26px;top:11px;width:9px;height:10px;border:1.8px solid #777168;border-bottom:0;border-radius:8px 8px 0 0;pointer-events:none; - } - #sunCloudAuthGateV3 .sun-auth-password{display:block!important;position:relative!important} - #sunCloudAuthGateV3 .sun-auth-password input{padding-right:58px!important} - #sunCloudAuthGateV3 .sun-auth-eye{right:9px!important;bottom:10px!important;width:38px!important;height:38px!important;color:#777168!important;background:transparent!important;border-radius:9px!important;font-size:18px!important} - #sunCloudAuthGateV3 .sun-auth-eye:hover{background:rgba(65,60,53,.06)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions{display:block!important;margin-top:25px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{ - width:100%!important;min-height:62px!important;border:0!important;border-radius:13px!important; - background:linear-gradient(100deg,#d9aa42,#efca69)!important;color:#171512!important;font-size:18px!important;font-weight:900!important; - box-shadow:0 14px 34px rgba(184,135,34,.14)!important;transition:transform .14s ease,filter .14s ease!important; - } - #sunCloudAuthGateV3 #sunGateSubmitV3::after{content:' →';font-size:24px;font-weight:500;margin-left:13px} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary:hover,#sunCloudAuthGateV3 #sunGateSubmitV3:hover{filter:brightness(1.025)!important;transform:translateY(-1px)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .outline{width:100%!important;margin-top:9px!important;min-height:48px!important;border:1px solid #c9c1b6!important;border-radius:11px!important;background:rgba(255,255,255,.25)!important;color:#37332d!important} - #sunCloudAuthGateV3 .sun-cloud-auth-error{min-height:19px!important;margin-top:10px!important;color:#a64c40!important;font-size:12px!important} - #sunCloudAuthGateV3 .sun-auth-switch{margin:26px 0 0!important;padding-top:25px!important;border-top:1px solid #cfc7bc!important;color:#898279!important;font-size:14px!important;text-align:center!important} - #sunCloudAuthGateV3 .sun-auth-switch button{border:0!important;background:transparent!important;color:#c18f24!important;text-decoration:none!important;font-weight:900!important;font-size:14px!important} - #sunCloudAuthGateV3 .sun-auth-switch button:hover{text-decoration:underline!important;text-underline-offset:4px!important} - #sunCloudAuthGateV3 .caterium-signature-caption{position:fixed;left:42px;bottom:31px;z-index:4;color:#9d968d;font-size:9px;letter-spacing:.31em;text-transform:uppercase;pointer-events:none} - #sunCloudAuthGateV3 .caterium-signature-caption::before{content:'';display:block;width:48px;height:2px;margin-bottom:13px;background:#d3a43c} - #sunCloudAuthGateV3 .caterium-signature-manifesto{position:fixed;right:56px;top:44px;z-index:4;width:270px;color:#a19a91;font-size:10px;line-height:1.9;letter-spacing:.28em;text-transform:uppercase;pointer-events:none;white-space:pre-line} - #sunCloudAuthGateV3 .caterium-flow-top{position:fixed;z-index:1;right:5.5vw;top:13vh;width:min(37vw,470px);height:auto;opacity:.86;pointer-events:none;color:#a7a097} - #sunCloudAuthGateV3 .caterium-flow-bottom{position:fixed;z-index:1;left:4vw;bottom:8vh;width:min(28vw,350px);height:auto;opacity:.65;pointer-events:none;color:#aaa399} - #sunCloudAuthGateV3 .caterium-flow-top svg,#sunCloudAuthGateV3 .caterium-flow-bottom svg{display:block;width:100%;height:auto;overflow:visible} - #sunCloudAuthGateV3 .caterium-flow-top path,#sunCloudAuthGateV3 .caterium-flow-bottom path{fill:none;stroke:currentColor;stroke-width:1.6;vector-effect:non-scaling-stroke} - #sunCloudAuthGateV3 .caterium-flow-top circle,#sunCloudAuthGateV3 .caterium-flow-bottom circle{fill:currentColor;stroke:none} - #sunCloudAuthGateV3 .caterium-flow-top circle.gold{fill:#e5a313} - @media(max-width:980px){ - #sunCloudAuthGateV3.sun-cloud-auth-gate{padding:34px 28px 70px!important;place-items:start center!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card{width:min(720px,100%)!important;padding-top:12px!important} - #sunCloudAuthGateV3 .caterium-signature-brand{margin-bottom:50px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:clamp(44px,8vw,66px)!important} - #sunCloudAuthGateV3 .caterium-signature-manifesto{display:none!important} - #sunCloudAuthGateV3 .caterium-flow-top{right:-60px;top:125px;width:360px;opacity:.55} - #sunCloudAuthGateV3 .caterium-flow-bottom{display:none!important} - } - @media(max-width:620px){ - body.sun-cloud-auth-required{overflow:auto!important} - #sunCloudAuthGateV3.sun-cloud-auth-gate{min-height:100dvh!important;padding:25px 18px 50px!important;place-items:start center!important} - #sunCloudAuthGateV3.sun-cloud-auth-gate::before{left:-62vw;top:44%;font-size:132vw;color:rgba(73,67,59,.026)} - #sunCloudAuthGateV3.sun-cloud-auth-gate::after{right:-48vw;bottom:-8vh;width:110vw;height:50vh;opacity:.32} - #sunCloudAuthGateV3 .sun-cloud-auth-card{width:100%!important;padding:0!important} - #sunCloudAuthGateV3 .caterium-signature-brand{gap:10px;margin:0 0 42px!important} - #sunCloudAuthGateV3 .caterium-signature-mark{width:50px;height:50px;flex-basis:50px} - #sunCloudAuthGateV3 .caterium-signature-word{font-size:29px} - #sunCloudAuthGateV3 .sun-cloud-auth-brand{margin-bottom:27px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:clamp(38px,12vw,52px)!important;line-height:1.01!important;letter-spacing:-.04em!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:13px!important;font-size:16px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card input{min-height:55px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{min-height:58px!important} - #sunCloudAuthGateV3 .caterium-flow-top{right:-125px;top:100px;width:330px;opacity:.32} - #sunCloudAuthGateV3 .caterium-signature-caption{display:none!important} - } - `; - document.head.appendChild(style); - } - - function cleanField(gate,id,placeholder,kind){ - const input=$(`#${id}`,gate);if(!input)return; - input.placeholder=placeholder; - const label=input.closest('label');if(!label)return; - label.classList.add('caterium-clean-field',kind==='email'?'caterium-email-field':'caterium-password-field'); - } - - function decorate(gate){ - if(!gate)return false; - const card=$('.sun-cloud-auth-card',gate);if(!card)return false; - gate.classList.add('caterium-signature-ready'); - if(!$('.caterium-signature-brand',card)){ - const brand=document.createElement('div');brand.className='caterium-signature-brand';brand.setAttribute('aria-label','Caterium'); - brand.innerHTML=`${logoSvg}Caterium`;card.prepend(brand); - } - if(!$('.caterium-signature-caption',gate)){const n=document.createElement('div');n.className='caterium-signature-caption';n.textContent='Caterium · вкус в деталях';gate.appendChild(n)} - if(!$('.caterium-signature-manifesto',gate)){const n=document.createElement('div');n.className='caterium-signature-manifesto';n.textContent='Простые решения\nдля больших событий';gate.appendChild(n)} - if(!$('.caterium-flow-top',gate)){const n=document.createElement('div');n.className='caterium-flow-top';n.innerHTML=flowSvg;gate.appendChild(n)} - if(!$('.caterium-flow-bottom',gate)){const n=document.createElement('div');n.className='caterium-flow-bottom';n.innerHTML=flowBottomSvg;gate.appendChild(n)} - const title=$('#sunGateTitleV3',gate),subtitle=$('#sunGateSubtitleV3',gate); - if(title&&title.textContent.trim()==='Вход в Caterium')title.textContent='Войти в рабочее пространство'; - if(subtitle&&subtitle.textContent.trim()==='Введите данные своего аккаунта')subtitle.textContent='Ваши заказы. Ваша команда. Ваш результат.'; - cleanField(gate,'sunGateEmailV3','Email','email'); - cleanField(gate,'sunGatePasswordV3','Пароль','password'); - cleanField(gate,'sunGatePassword2V27','Подтвердите пароль','password'); - removeBoot();return true; - } - - function scan(){return decorate($('#sunCloudAuthGateV3'))} - function releaseForSignedIn(){ - try{const st=window.SunCloudV2?.status?.();if(st?.signedIn&&window.SunCloudV2?.getWorkspace?.()&&!$('#sunCloudAuthGateV3'))removeBoot()}catch(_){} - } - installStyle();scan(); - const observer=new MutationObserver(()=>{scan();releaseForSignedIn()});observer.observe(document.documentElement,{childList:true,subtree:true,characterData:true}); - window.addEventListener('sun:cloud-state-applied',()=>{scan();releaseForSignedIn()}); - const poll=setInterval(()=>{if(scan()||!boot()){clearInterval(poll);return}releaseForSignedIn()},80);setTimeout(()=>{clearInterval(poll);if(boot()&&!$('#sunCloudAuthGateV3'))removeBoot()},10000); - window.CateriumLoginSignatureV1776=Object.freeze({VERSION,scan,removeBoot,disconnect:()=>{observer.disconnect();clearInterval(poll)}}); +'use strict'; +if(window.CateriumLoginSignatureV1776)return; +const VERSION='17.7.7-cream-login-v2'; +const $=(s,r=document)=>r.querySelector(s); +const boot=()=>$('#cateriumAuthBootV1776'); +const removeBoot=()=>{const b=boot();if(b){b.classList.add('leave');setTimeout(()=>b.remove(),180)}}; +const FLOW=''; +function styles(){if($('#caterium-login-signature-v1776-style'))return;const s=document.createElement('style');s.id='caterium-login-signature-v1776-style';s.textContent=` +body.sun-cloud-auth-required{overflow:hidden!important;background:#f5f0e7!important}body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important} +#sunCloudAuthGateV3.sun-cloud-auth-gate{position:fixed!important;inset:0!important;z-index:20000!important;overflow:auto!important;display:grid!important;place-items:center!important;padding:clamp(32px,6vh,78px) clamp(24px,8vw,120px)!important;background:radial-gradient(circle at 18% 18%,rgba(255,255,255,.92),transparent 34%),radial-gradient(circle at 77% 22%,rgba(255,255,255,.5),transparent 28%),linear-gradient(135deg,#f8f4ec 0%,#f2ece1 56%,#f7f3eb 100%)!important;color:#282621!important} +#sunCloudAuthGateV3.sun-cloud-auth-gate:before{content:'C';position:fixed;z-index:0;left:-7vw;top:49%;transform:translateY(-50%);pointer-events:none;font:400 min(72vw,860px)/.72 Georgia,'Times New Roman',serif;color:rgba(73,67,59,.032)} +#sunCloudAuthGateV3.sun-cloud-auth-gate:after{content:'';position:fixed;z-index:0;right:-7vw;bottom:-14vh;width:min(48vw,720px);height:min(70vh,850px);pointer-events:none;background:radial-gradient(ellipse at 58% 20%,rgba(67,64,58,.14) 0 7%,transparent 8%),radial-gradient(ellipse at 36% 35%,rgba(67,64,58,.13) 0 6%,transparent 7%),radial-gradient(ellipse at 68% 47%,rgba(67,64,58,.12) 0 8%,transparent 9%),linear-gradient(106deg,transparent 43%,rgba(67,64,58,.08) 44% 46%,transparent 47%);filter:blur(18px);transform:rotate(-17deg);opacity:.45} +#sunCloudAuthGateV3 .sun-cloud-auth-card{position:relative!important;z-index:3!important;width:min(790px,100%)!important;margin:0!important;padding:0 0 58px!important;border:0!important;border-radius:0!important;background:transparent!important;color:#2c2924!important;box-shadow:none!important;backdrop-filter:none!important} +#sunCloudAuthGateV3 .caterium-signature-brand{display:flex;align-items:center;gap:13px;margin:0 0 clamp(46px,7vh,88px)!important}.caterium-signature-mark{display:block;width:62px;height:62px;object-fit:contain;flex:0 0 62px}.caterium-signature-word{font:500 36px/1 Georgia,'Times New Roman',serif;color:#26231f;letter-spacing:-.02em} +#sunCloudAuthGateV3 .sun-cloud-auth-brand{display:block!important;margin:0 0 35px!important}#sunCloudAuthGateV3 .sun-cloud-auth-brand img{display:none!important}#sunCloudAuthGateV3 .sun-cloud-auth-brand h2{margin:0!important;max-width:720px;color:#24211d!important;font:500 clamp(48px,5.2vw,76px)/.99 Georgia,'Times New Roman',serif!important;letter-spacing:-.045em!important}#sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:18px!important;color:#8e887f!important;font-size:clamp(16px,1.55vw,21px)!important;line-height:1.45!important;font-weight:400!important} +#sunCloudAuthGateV3 .sun-cloud-auth-card>p.hint{color:#817b72!important}#sunCloudAuthGateV3 .sun-cloud-auth-card label{display:block!important;margin:14px 0 0!important;color:#716b62!important;font-size:12px!important;font-weight:700!important}#sunCloudAuthGateV3 label.caterium-clean-field{font-size:0!important;color:transparent!important;position:relative!important} +#sunCloudAuthGateV3 .sun-cloud-auth-card input,#sunCloudAuthGateV3 .sun-cloud-auth-card select,#sunCloudAuthGateV3 .sun-cloud-auth-card textarea{width:100%!important;min-height:58px!important;margin-top:7px!important;padding:0 18px!important;border:1px solid #bdb5aa!important;border-radius:13px!important;background:rgba(255,255,255,.13)!important;color:#312e29!important;box-shadow:none!important;outline:none!important;font-size:16px!important;font-weight:500!important}#sunCloudAuthGateV3 label.caterium-clean-field input{margin-top:0!important;padding-left:58px!important}#sunCloudAuthGateV3 input::placeholder{color:#9c958c!important;opacity:1!important}#sunCloudAuthGateV3 input:focus{border-color:#d7a632!important;box-shadow:0 0 0 4px rgba(216,167,50,.12)!important;background:rgba(255,255,255,.5)!important} +#sunCloudAuthGateV3 .caterium-email-field:before{content:'✉';position:absolute;z-index:2;left:20px;top:15px;color:#777168;font-size:25px;font-weight:400}#sunCloudAuthGateV3 .caterium-password-field:before{content:'♙';position:absolute;z-index:2;left:21px;top:14px;color:#777168;font-size:24px;transform:rotate(180deg);opacity:.8} +#sunCloudAuthGateV3 .sun-auth-password{display:block!important;position:relative!important}#sunCloudAuthGateV3 .sun-auth-password input{padding-right:58px!important}#sunCloudAuthGateV3 .sun-auth-eye{right:9px!important;bottom:10px!important;width:38px!important;height:38px!important;color:#777168!important;background:transparent!important;border-radius:9px!important} +#sunCloudAuthGateV3 .sun-cloud-auth-actions{display:block!important;margin-top:25px!important}#sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{width:100%!important;min-height:62px!important;border:0!important;border-radius:13px!important;background:linear-gradient(100deg,#d9aa42,#efca69)!important;color:#171512!important;font-size:18px!important;font-weight:900!important;box-shadow:0 14px 34px rgba(184,135,34,.14)!important}#sunCloudAuthGateV3 #sunGateSubmitV3:after{content:' →';font-size:24px;font-weight:500;margin-left:13px} +#sunCloudAuthGateV3 .sun-cloud-auth-actions .outline{width:100%!important;margin-top:9px!important;min-height:48px!important;border:1px solid #c9c1b6!important;border-radius:11px!important;background:rgba(255,255,255,.25)!important;color:#37332d!important}#sunCloudAuthGateV3 .sun-cloud-auth-error{min-height:19px!important;margin-top:10px!important;color:#a64c40!important;font-size:12px!important}#sunCloudAuthGateV3 .sun-auth-switch{margin:26px 0 0!important;padding-top:25px!important;border-top:1px solid #cfc7bc!important;color:#898279!important;font-size:14px!important;text-align:center!important}#sunCloudAuthGateV3 .sun-auth-switch button{border:0!important;background:transparent!important;color:#c18f24!important;text-decoration:none!important;font-weight:900!important} +.caterium-signature-caption{position:fixed;left:42px;bottom:31px;z-index:4;color:#9d968d;font-size:9px;letter-spacing:.31em;text-transform:uppercase;pointer-events:none}.caterium-signature-caption:before{content:'';display:block;width:48px;height:2px;margin-bottom:13px;background:#d3a43c}.caterium-signature-manifesto{position:fixed;right:56px;top:44px;z-index:4;width:270px;color:#a19a91;font-size:10px;line-height:1.9;letter-spacing:.28em;text-transform:uppercase;pointer-events:none;white-space:pre-line}.caterium-flow-top{position:fixed;z-index:1;right:5.5vw;top:13vh;width:min(37vw,470px);opacity:.86;pointer-events:none;color:#a7a097}.caterium-flow-top svg{width:100%;height:auto}.caterium-flow-top path{fill:none;stroke:currentColor;stroke-width:1.6}.caterium-flow-top circle{fill:currentColor}.caterium-flow-top circle.gold{fill:#e5a313} +@media(max-width:980px){#sunCloudAuthGateV3.sun-cloud-auth-gate{padding:34px 28px 70px!important;place-items:start center!important}#sunCloudAuthGateV3 .sun-cloud-auth-card{width:min(720px,100%)!important;padding-top:12px!important}.caterium-signature-manifesto{display:none!important}.caterium-flow-top{right:-60px;top:125px;width:360px;opacity:.45}} +@media(max-width:620px){body.sun-cloud-auth-required{overflow:auto!important}#sunCloudAuthGateV3.sun-cloud-auth-gate{min-height:100dvh!important;padding:25px 18px 50px!important;place-items:start center!important}#sunCloudAuthGateV3 .sun-cloud-auth-card{width:100%!important;padding:0!important}.caterium-signature-brand{margin-bottom:42px!important}.caterium-signature-mark{width:50px;height:50px;flex-basis:50px}.caterium-signature-word{font-size:29px}#sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:clamp(38px,12vw,52px)!important}.caterium-flow-top{right:-125px;top:100px;width:330px;opacity:.25}.caterium-signature-caption{display:none!important}} +`;document.head.appendChild(s)} +function field(g,id,ph,type){const i=$('#'+id,g);if(!i)return;i.placeholder=ph;const l=i.closest('label');if(l)l.classList.add('caterium-clean-field',type==='email'?'caterium-email-field':'caterium-password-field')} +function decorate(g){if(!g)return false;const c=$('.sun-cloud-auth-card',g);if(!c)return false;if(!$('.caterium-signature-brand',c)){const b=document.createElement('div');b.className='caterium-signature-brand';b.innerHTML='Caterium';c.prepend(b)}if(!$('.caterium-signature-caption',g)){const n=document.createElement('div');n.className='caterium-signature-caption';n.textContent='Caterium · вкус в деталях';g.appendChild(n)}if(!$('.caterium-signature-manifesto',g)){const n=document.createElement('div');n.className='caterium-signature-manifesto';n.textContent='Простые решения\nдля больших событий';g.appendChild(n)}if(!$('.caterium-flow-top',g)){const n=document.createElement('div');n.className='caterium-flow-top';n.innerHTML=FLOW;g.appendChild(n)}const t=$('#sunGateTitleV3',g),sub=$('#sunGateSubtitleV3',g);if(t&&t.textContent.trim()==='Вход в Caterium')t.textContent='Войти в рабочее пространство';if(sub&&sub.textContent.trim()==='Введите данные своего аккаунта')sub.textContent='Ваши заказы. Ваша команда. Ваш результат.';field(g,'sunGateEmailV3','Email','email');field(g,'sunGatePasswordV3','Пароль','password');field(g,'sunGatePassword2V27','Подтвердите пароль','password');removeBoot();return true} +function scan(){return decorate($('#sunCloudAuthGateV3'))}styles();scan();const o=new MutationObserver(scan);o.observe(document.documentElement,{childList:true,subtree:true,characterData:true});window.addEventListener('sun:cloud-state-applied',scan);const p=setInterval(()=>{if(scan()||!boot())clearInterval(p)},80);setTimeout(()=>{clearInterval(p);if(boot()&&!$('#sunCloudAuthGateV3'))removeBoot()},10000);window.CateriumLoginSignatureV1776=Object.freeze({VERSION,scan,removeBoot,disconnect:()=>{o.disconnect();clearInterval(p)}}) })(); \ No newline at end of file From d32e49048b8596105f7b74e24c1eda802b283920 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 07:08:06 +0300 Subject: [PATCH 04/21] ci: verify Timeweb pull deployment --- .github/workflows/deploy-timeweb.yml | 106 +++++++++++---------------- 1 file changed, 44 insertions(+), 62 deletions(-) diff --git a/.github/workflows/deploy-timeweb.yml b/.github/workflows/deploy-timeweb.yml index 06ce23a..1a3ad24 100644 --- a/.github/workflows/deploy-timeweb.yml +++ b/.github/workflows/deploy-timeweb.yml @@ -1,4 +1,4 @@ -name: Deploy Caterium to Timeweb Hosting +name: Verify Caterium on Timeweb on: workflow_run: @@ -14,82 +14,64 @@ concurrency: cancel-in-progress: false jobs: - deploy: + verify: if: >- github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'production') runs-on: ubuntu-latest env: - TIMEWEB_HOST: ${{ secrets.TIMEWEB_HOST }} - TIMEWEB_USER: ${{ secrets.TIMEWEB_USER }} - TIMEWEB_SSH_KEY: ${{ secrets.TIMEWEB_SSH_KEY }} - TIMEWEB_REMOTE_PATH: ${{ secrets.TIMEWEB_REMOTE_PATH }} + TIMEWEB_BASE_URL: https://app.caterium.ru steps: - - name: Check Timeweb configuration - id: config - shell: bash - run: | - missing=0 - for name in TIMEWEB_HOST TIMEWEB_USER TIMEWEB_SSH_KEY TIMEWEB_REMOTE_PATH; do - if [ -z "${!name}" ]; then - echo "::notice::$name is not configured yet" - missing=1 - fi - done - - if [ "$missing" -eq 0 ]; then - echo "ready=true" >> "$GITHUB_OUTPUT" - else - echo "ready=false" >> "$GITHUB_OUTPUT" - echo "Timeweb deployment is prepared but inactive until all four repository secrets are configured." - fi - - name: Checkout tested production revision - if: steps.config.outputs.ready == 'true' uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || 'production' }} - - name: Configure SSH - if: steps.config.outputs.ready == 'true' + - name: Wait for Timeweb cron deployment shell: bash run: | - install -m 700 -d "$HOME/.ssh" - printf '%s\n' "$TIMEWEB_SSH_KEY" > "$HOME/.ssh/timeweb_deploy_key" - chmod 600 "$HOME/.ssh/timeweb_deploy_key" - ssh-keyscan -p 22 -H "$TIMEWEB_HOST" >> "$HOME/.ssh/known_hosts" - chmod 600 "$HOME/.ssh/known_hosts" + set -euo pipefail - - name: Verify Timeweb target - if: steps.config.outputs.ready == 'true' - shell: bash - run: | - test -f public/index.html - ssh -p 22 -i "$HOME/.ssh/timeweb_deploy_key" \ - -o BatchMode=yes \ - -o IdentitiesOnly=yes \ - "$TIMEWEB_USER@$TIMEWEB_HOST" \ - "mkdir -p -- '$TIMEWEB_REMOTE_PATH'" + login_file="public/core/login-signature-v1776.js" + logo_file="public/caterium-mark-light.svg" - - name: Deploy public directory - if: steps.config.outputs.ready == 'true' - shell: bash - run: | - rsync -az --delete --checksum \ - --exclude='.htaccess' \ - --exclude='.well-known/' \ - -e "ssh -p 22 -i $HOME/.ssh/timeweb_deploy_key -o BatchMode=yes -o IdentitiesOnly=yes" \ - public/ \ - "$TIMEWEB_USER@$TIMEWEB_HOST:$TIMEWEB_REMOTE_PATH/" + test -s "$login_file" + test -s "$logo_file" - - name: Verify deployed entry point - if: steps.config.outputs.ready == 'true' - shell: bash - run: | - ssh -p 22 -i "$HOME/.ssh/timeweb_deploy_key" \ - -o BatchMode=yes \ - -o IdentitiesOnly=yes \ - "$TIMEWEB_USER@$TIMEWEB_HOST" \ - "test -s '$TIMEWEB_REMOTE_PATH/index.html'" \ No newline at end of file + local_login="$(sha256sum "$login_file" | awk '{print $1}')" + local_logo="$(sha256sum "$logo_file" | awk '{print $1}')" + + echo "Expected login sha256: $local_login" + echo "Expected logo sha256: $local_logo" + + for attempt in $(seq 1 18); do + stamp="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${attempt}" + echo "Attempt $attempt/18" + + if curl -fsSL --connect-timeout 10 --max-time 30 \ + "${TIMEWEB_BASE_URL}/core/login-signature-v1776.js?deploy_check=${stamp}" \ + -o /tmp/caterium-login.js && \ + curl -fsSL --connect-timeout 10 --max-time 30 \ + "${TIMEWEB_BASE_URL}/caterium-mark-light.svg?deploy_check=${stamp}" \ + -o /tmp/caterium-mark.svg; then + remote_login="$(sha256sum /tmp/caterium-login.js | awk '{print $1}')" + remote_logo="$(sha256sum /tmp/caterium-mark.svg | awk '{print $1}')" + + echo "Remote login sha256: $remote_login" + echo "Remote logo sha256: $remote_logo" + + if [ "$remote_login" = "$local_login" ] && [ "$remote_logo" = "$local_logo" ]; then + echo "Timeweb production is current." + exit 0 + fi + else + echo "Timeweb is not reachable yet." + fi + + sleep 30 + done + + echo "::error::Timeweb did not reach the tested Caterium login revision within 9 minutes." + exit 1 From 351579ec3a9de1d43cddff79a9001d362d2c4e23 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 07:10:08 +0300 Subject: [PATCH 05/21] ci: trigger Timeweb verification --- deploy-trigger.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy-trigger.txt b/deploy-trigger.txt index ea36e3f..61c1a97 100644 --- a/deploy-trigger.txt +++ b/deploy-trigger.txt @@ -1 +1 @@ -First Cloudflare autodeploy +Timeweb verification trigger 2026-09-12 From b77c47e5a6a62071df37784d265c9e84d18e6974 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:11:44 +0300 Subject: [PATCH 06/21] fix: force fresh auth assets in PWA --- public/service-worker.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index a6650d7..695fdbc 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,13 +1,20 @@ -const CACHE='sun-catering-pwa-v78-20260909-v17-7-3-clients-server-read'; -const VERSION='20260909-v17-7-3-clients-server-read'; +const CACHE='sun-catering-pwa-v79-20260912-login-refresh'; +const VERSION='20260912-login-refresh'; const CORE=[ './','./index.html', - `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, + `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, './offer-gallery/001.jpg','./offer-gallery/002.jpg', './catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg', - './sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest', + './sun-logo.png','./caterium-login-logo.png','./caterium-mark-light.svg','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest', './offer-templates/thumb-light.jpg','./offer-templates/thumb-editorial-grid.jpg','./offer-templates/thumb-midnight-glass.jpg','./offer-templates/thumb-emerald-gold.jpg' ]; +const CRITICAL_FRESH=new Set([ + '/core/performance.js', + '/core/login-signature-v1776.js', + '/core/auth-security-v1774.js', + '/legacy/bootstrap.js', + '/app-runtime.js' +]); self.addEventListener('install',event=>{ event.waitUntil(caches.open(CACHE).then(cache=>cache.addAll(CORE)).then(()=>self.skipWaiting())); }); @@ -18,15 +25,23 @@ function cachePut(req,res){ if(res&&res.ok){const clone=res.clone();caches.open(CACHE).then(c=>c.put(req,clone)).catch(()=>{});}return res; } function networkFirst(req,fallback){ - return fetch(req).then(res=>cachePut(req,res)).catch(()=>caches.match(req).then(hit=>hit||caches.match(fallback||req))); + return fetch(req,{cache:'no-store'}).then(res=>cachePut(req,res)).catch(()=>caches.match(req).then(hit=>hit||caches.match(fallback||req))); +} +function forceFresh(req){ + const url=new URL(req.url); + url.searchParams.set('__caterium_release',VERSION); + return fetch(url.toString(),{cache:'no-store',credentials:'same-origin'}).then(res=>cachePut(req,res)).catch(()=>caches.match(req)); } self.addEventListener('fetch',event=>{ const req=event.request;if(req.method!=='GET')return; const url=new URL(req.url);if(url.pathname.startsWith('/api/'))return; if(req.mode==='navigate'){ - event.respondWith(fetch(req).then(res=>{const clone=res.clone();caches.open(CACHE).then(c=>c.put('./index.html',clone)).catch(()=>{});return res}).catch(()=>caches.match('./index.html')));return; + event.respondWith(fetch(req,{cache:'no-store'}).then(res=>{const clone=res.clone();caches.open(CACHE).then(c=>c.put('./index.html',clone)).catch(()=>{});return res}).catch(()=>caches.match('./index.html')));return; + } + if(CRITICAL_FRESH.has(url.pathname)){ + event.respondWith(forceFresh(req));return; } const freshAsset=/\.(?:js|css|webmanifest)$/i.test(url.pathname); if(freshAsset){event.respondWith(networkFirst(req));return;} - event.respondWith(caches.match(req).then(cached=>cached||fetch(req).then(res=>cachePut(req,res)))); + event.respondWith(caches.match(req).then(cached=>cached||fetch(req,{cache:'no-store'}).then(res=>cachePut(req,res)))); }); From 2678b1571c24465e39536b9a782054d0ae05afdc Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:12:11 +0300 Subject: [PATCH 07/21] fix: prevent stale auth shell on Timeweb --- public/.htaccess | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 public/.htaccess diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..4d7c95d --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,19 @@ + + + Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0" + Header set Pragma "no-cache" + Header set Expires "0" + + + + Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0" + Header set Pragma "no-cache" + Header set Expires "0" + + + + + ExpiresActive On + ExpiresByType text/html "access plus 0 seconds" + ExpiresByType application/javascript "access plus 0 seconds" + From c50730af424df8f9e51f4b19b27318350b9c50f1 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:12:39 +0300 Subject: [PATCH 08/21] test: accept refreshed PWA auth cache --- tests/static-security.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/static-security.mjs b/tests/static-security.mjs index e33c3ac..7197fa4 100644 --- a/tests/static-security.mjs +++ b/tests/static-security.mjs @@ -28,7 +28,7 @@ if(current!==113)fail(`current catalog photo count ${current}, expected 113`);el if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos'); const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x)); if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed'); -if(!sw.includes('v17-7-3-clients-server-read')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.7.0'); +if(!sw.includes('20260912-login-refresh')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh'); if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current'); if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present'); if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present'); From e38d7fe2c98ea9765f4c84e77e487386a58bc2e8 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:13:19 +0300 Subject: [PATCH 09/21] fix: keep release marker while refreshing auth cache --- public/service-worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/service-worker.js b/public/service-worker.js index 695fdbc..63e7a9c 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const CACHE='sun-catering-pwa-v79-20260912-login-refresh'; +const CACHE='sun-catering-pwa-v78-20260909-v17-7-3-clients-server-read-20260912-login-refresh'; const VERSION='20260912-login-refresh'; const CORE=[ './','./index.html', From d9cad541cac67f7e8178b7fe6327feddadacafcb Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:15:01 +0300 Subject: [PATCH 10/21] ci: verify fresh auth cache on Timeweb --- deploy-trigger.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy-trigger.txt b/deploy-trigger.txt index 61c1a97..8096230 100644 --- a/deploy-trigger.txt +++ b/deploy-trigger.txt @@ -1 +1 @@ -Timeweb verification trigger 2026-09-12 +Timeweb verification trigger 2026-09-12 08:15 From c5d68411ed8a7cbf77245112b846baa510a6a602 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:27:31 +0300 Subject: [PATCH 11/21] fix: add one-click PWA cache refresh for login --- public/refresh-login-20260912.html | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 public/refresh-login-20260912.html diff --git a/public/refresh-login-20260912.html b/public/refresh-login-20260912.html new file mode 100644 index 0000000..b89c8b5 --- /dev/null +++ b/public/refresh-login-20260912.html @@ -0,0 +1,52 @@ + + + + + + + + Caterium — обновление + + + +
+ Caterium +

Обновляем Caterium

+

Удаляем старый экран входа и подключаем свежую версию…

+
+ + + From 0417a05e9cdfa1653c04f8b6f33d8ebf9d0c1a9e Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:47:40 +0300 Subject: [PATCH 12/21] feat: owner-only company accounts and self profile --- ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql | 471 ++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql diff --git a/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql b/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql new file mode 100644 index 0000000..fa92a8d --- /dev/null +++ b/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql @@ -0,0 +1,471 @@ +-- Caterium v31: one company owner account, owner-only employee administration, +-- and a self-service profile for every authenticated user. + +create or replace function public.caterium_is_workspace_owner(p_workspace uuid) +returns boolean +language sql +stable +security definer +set search_path='public' +as $$ + select auth.uid() is not null + and exists( + select 1 + from public.sun_workspaces w + join public.sun_workspace_members m + on m.workspace_id=w.id + and m.user_id=auth.uid() + and m.is_active=true + where w.id=p_workspace + and w.created_by=auth.uid() + ); +$$; + +revoke all on function public.caterium_is_workspace_owner(uuid) from public,anon; +grant execute on function public.caterium_is_workspace_owner(uuid) to authenticated; + +create or replace function public.caterium_account_snapshot(p_workspace uuid) +returns jsonb +language plpgsql +stable +security definer +set search_path='public','auth' +as $$ +declare + v_user uuid:=auth.uid(); + v_row record; +begin + if v_user is null then raise exception 'Сначала войдите в Caterium'; end if; + + select w.id as workspace_id, + w.name as company_name, + w.company_number, + w.created_by, + m.display_name, + m.role, + m.is_active, + coalesce(m.permissions,'{}'::jsonb) as permissions, + u.email::text as email + into v_row + from public.sun_workspaces w + join public.sun_workspace_members m + on m.workspace_id=w.id + and m.user_id=v_user + join auth.users u on u.id=v_user + where w.id=p_workspace + and m.is_active=true + limit 1; + + if not found then raise exception 'Нет доступа к компании'; end if; + + return jsonb_build_object( + 'user_id',v_user, + 'email',v_row.email, + 'display_name',coalesce(nullif(v_row.display_name,''),split_part(coalesce(v_row.email,''),'@',1)), + 'role',v_row.role, + 'is_owner',(v_row.created_by=v_user), + 'workspace_id',v_row.workspace_id, + 'company_name',v_row.company_name, + 'company_number',v_row.company_number, + 'permissions',v_row.permissions + ); +end; +$$; + +revoke all on function public.caterium_account_snapshot(uuid) from public,anon; +grant execute on function public.caterium_account_snapshot(uuid) to authenticated; + +create or replace function public.caterium_update_my_name(p_display_name text) +returns text +language plpgsql +security definer +set search_path='public','auth' +as $$ +declare + v_user uuid:=auth.uid(); + v_name text:=trim(coalesce(p_display_name,'')); +begin + if v_user is null then raise exception 'Сначала войдите в Caterium'; end if; + if char_length(v_name)<1 then raise exception 'Введите имя'; end if; + if char_length(v_name)>120 then raise exception 'Имя слишком длинное'; end if; + + update public.sun_workspace_members + set display_name=v_name, + updated_at=now() + where user_id=v_user; + + update auth.users + set raw_user_meta_data=jsonb_set(coalesce(raw_user_meta_data,'{}'::jsonb),'{name}',to_jsonb(v_name),true) + where id=v_user; + + return v_name; +end; +$$; + +revoke all on function public.caterium_update_my_name(text) from public,anon; +grant execute on function public.caterium_update_my_name(text) to authenticated; + +-- Existing company creators are the only company administrators. +update public.sun_workspace_members m + set role='admin', + permissions=jsonb_set(coalesce(m.permissions,public.sun_role_default_permissions('admin')),'{users.manage}','true'::jsonb,true), + updated_at=now() + from public.sun_workspaces w + where w.id=m.workspace_id + and m.user_id=w.created_by; + +-- Any non-owner account is an employee account and can never manage users. +update public.sun_workspace_members m + set role=case when m.role='admin' then 'manager' else m.role end, + permissions=jsonb_set(coalesce(m.permissions,public.sun_role_default_permissions(case when m.role='admin' then 'manager' else m.role end)),'{users.manage}','false'::jsonb,true), + updated_at=now() + from public.sun_workspaces w + where w.id=m.workspace_id + and m.user_id<>w.created_by; + +-- No pending invitation may create a second company administrator. +delete from public.sun_workspace_invites where used_at is null and role='admin'; + +create or replace function public.sun_list_workspace_members(p_workspace uuid) +returns table(user_id uuid,email text,display_name text,role text,is_active boolean,permissions jsonb,created_at timestamptz,updated_at timestamptz) +language plpgsql +stable +security definer +set search_path='public','auth' +as $$ +begin + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then + raise exception 'Только владелец компании может управлять пользователями'; + end if; + return query + select m.user_id,u.email::text,m.display_name,m.role,m.is_active, + coalesce(m.permissions,public.sun_role_default_permissions(m.role)),m.created_at,m.updated_at + from public.sun_workspace_members m + join auth.users u on u.id=m.user_id + join public.sun_workspaces w on w.id=m.workspace_id + where m.workspace_id=p_workspace + order by (m.user_id=w.created_by) desc,coalesce(m.display_name,u.email::text); +end; +$$; + +create or replace function public.sun_admin_update_member( + p_workspace uuid, + p_user uuid, + p_display_name text, + p_role text, + p_is_active boolean, + p_permissions jsonb +) +returns void +language plpgsql +security definer +set search_path='public' +as $$ +declare + v_role text:=lower(coalesce(p_role,'')); + v_owner uuid; + v_max integer; + v_active_count integer; + v_permissions jsonb; +begin + perform pg_advisory_xact_lock(hashtext(p_workspace::text)); + + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then + raise exception 'Только владелец компании может управлять сотрудниками'; + end if; + if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if; + if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if; + if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if; + if p_permissions is null or jsonb_typeof(p_permissions)<>'object' then raise exception 'Некорректные права пользователя'; end if; + + select created_by into v_owner from public.sun_workspaces where id=p_workspace; + if v_owner is null then raise exception 'Компания не найдена'; end if; + if p_user=v_owner then raise exception 'Главный аккаунт компании нельзя изменить через управление сотрудниками'; end if; + if not exists(select 1 from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user) then raise exception 'Пользователь не найден'; end if; + + if coalesce(p_is_active,false) + and not coalesce((select is_active from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user),false) then + select p.max_members into v_max + from public.sun_workspace_subscriptions s + join public.sun_plans p on p.id=s.plan_id + where s.workspace_id=p_workspace; + select count(*)::int into v_active_count + from public.sun_workspace_members + where workspace_id=p_workspace and is_active=true; + if v_max is not null and v_active_count>=v_max then raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max; end if; + end if; + + v_permissions:=coalesce(p_permissions,'{}'::jsonb)||jsonb_build_object('users.manage',false); + + update public.sun_workspace_members + set display_name=nullif(trim(coalesce(p_display_name,'')),''), + role=v_role, + is_active=coalesce(p_is_active,false), + permissions=v_permissions, + updated_at=now() + where workspace_id=p_workspace and user_id=p_user; +end; +$$; + +create or replace function public.sun_admin_remove_member(p_workspace uuid,p_user uuid) +returns void +language plpgsql +security definer +set search_path='public' +as $$ +declare + v_owner uuid; +begin + perform pg_advisory_xact_lock(hashtext(p_workspace::text)); + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then + raise exception 'Только владелец компании может удалять сотрудников'; + end if; + if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if; + if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if; + select created_by into v_owner from public.sun_workspaces where id=p_workspace; + if p_user=v_owner then raise exception 'Главный аккаунт компании удалить нельзя'; end if; + delete from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user; +end; +$$; + +create or replace function public.sun_employee_prepare_v28( + p_workspace uuid, + p_email text, + p_display_name text default '', + p_role text default 'manager' +) +returns jsonb +language plpgsql +security definer +set search_path='public','auth' +as $$ +declare + v_email text:=lower(trim(coalesce(p_email,''))); + v_name text:=trim(coalesce(p_display_name,'')); + v_role text:=lower(coalesce(p_role,'manager')); + v_user uuid; + v_member public.sun_workspace_members%rowtype; + v_max integer; + v_count integer; + v_mode text; +begin + if auth.uid() is null then raise exception 'Сначала войдите в Caterium'; end if; + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if; + if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if; + if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if; + if v_name='' then v_name:=split_part(v_email,'@',1); end if; + + v_mode:=public.sun_workspace_access_mode_internal_v28(p_workspace); + if v_mode<>'full' then raise exception 'Подписка компании не позволяет добавлять сотрудников'; end if; + if not public.sun_workspace_feature_internal_v28(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if; + + select p.max_members into v_max + from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id + where s.workspace_id=p_workspace; + + select count(*)::int into v_count from public.sun_workspace_members + where workspace_id=p_workspace and is_active=true; + + select u.id into v_user from auth.users u where lower(coalesce(u.email,''))=v_email order by u.created_at asc limit 1; + + if v_user is not null then + select * into v_member from public.sun_workspace_members + where workspace_id=p_workspace and user_id=v_user limit 1; + if found and v_member.is_active then + return jsonb_build_object('status','already_member','user_id',v_user,'email',v_email,'display_name',coalesce(nullif(v_member.display_name,''),v_name),'role',v_member.role); + end if; + end if; + + if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if; + + return jsonb_build_object( + 'status',case when v_user is null then 'new' else 'existing' end, + 'user_id',v_user, + 'email',v_email, + 'display_name',v_name, + 'role',v_role, + 'max_members',v_max, + 'active_members',v_count + ); +end; +$$; + +create or replace function public.sun_employee_finalize_v28( + p_workspace uuid, + p_user_id uuid, + p_display_name text default '', + p_role text default 'manager' +) +returns jsonb +language plpgsql +security definer +set search_path='public','auth' +as $$ +declare + v_name text:=trim(coalesce(p_display_name,'')); + v_role text:=lower(coalesce(p_role,'manager')); + v_email text; + v_max integer; + v_count integer; + v_already boolean:=false; +begin + if auth.uid() is null then raise exception 'Сначала войдите в Caterium'; end if; + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if; + if public.sun_workspace_access_mode_internal_v28(p_workspace)<>'full' then raise exception 'Подписка компании не позволяет добавлять сотрудников'; end if; + if not public.sun_workspace_feature_internal_v28(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if; + if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if; + + select lower(email) into v_email from auth.users where id=p_user_id; + if v_email is null then raise exception 'Аккаунт сотрудника не найден'; end if; + if v_name='' then v_name:=split_part(v_email,'@',1); end if; + + select exists(select 1 from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user_id and is_active=true) into v_already; + select p.max_members into v_max + from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id + where s.workspace_id=p_workspace; + select count(*)::int into v_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true; + if not v_already and v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if; + + insert into public.sun_workspace_members(workspace_id,user_id,role,display_name,is_active,permissions,updated_at) + values(p_workspace,p_user_id,v_role,left(v_name,120),true,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),now()) + on conflict(workspace_id,user_id) do update + set role=excluded.role,display_name=excluded.display_name,is_active=true,permissions=excluded.permissions,updated_at=now(); + + delete from public.sun_workspace_invites where workspace_id=p_workspace and used_at is null and lower(coalesce(email,''))=v_email; + + return jsonb_build_object('status',case when v_already then 'updated' else 'added' end,'user_id',p_user_id,'email',v_email,'display_name',v_name,'role',v_role); +end; +$$; + +create or replace function public.sun_create_invite(p_workspace uuid,p_role text default 'manager') +returns uuid +language plpgsql +security definer +set search_path='public' +as $$ +declare + v_token uuid; + v_role text:=lower(coalesce(p_role,'manager')); + v_max integer; + v_count integer; +begin + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if; + if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if; + if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if; + if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Некорректная роль сотрудника'; end if; + select p.max_members into v_max from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id where s.workspace_id=p_workspace; + select count(*)::int into v_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true; + if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max; end if; + insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by) + values(p_workspace,v_role,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),auth.uid()) returning token into v_token; + return v_token; +end; +$$; + +create or replace function public.sun_create_invite_v27( + p_workspace uuid, + p_email text, + p_display_name text default '', + p_role text default 'manager' +) +returns uuid +language plpgsql +security definer +set search_path='public','auth' +as $$ +declare + v_token uuid; + v_role text:=lower(coalesce(p_role,'manager')); + v_email text:=lower(trim(coalesce(p_email,''))); + v_name text:=trim(coalesce(p_display_name,'')); + v_max integer; + v_active integer; + v_pending integer; +begin + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if; + if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if; + if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if; + if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Некорректная роль сотрудника'; end if; + if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if; + if v_name='' then v_name:=split_part(v_email,'@',1); end if; + + if exists( + select 1 from public.sun_workspace_members m + join auth.users u on u.id=m.user_id + where m.workspace_id=p_workspace and m.is_active=true and lower(coalesce(u.email,''))=v_email + ) then raise exception 'Пользователь с этим email уже добавлен в компанию'; end if; + + delete from public.sun_workspace_invites + where workspace_id=p_workspace and used_at is null + and (expires_at<=now() or lower(coalesce(email,''))=v_email); + + select p.max_members into v_max + from public.sun_workspace_subscriptions s + join public.sun_plans p on p.id=s.plan_id + where s.workspace_id=p_workspace; + + if v_max is not null then + select count(*)::int into v_active from public.sun_workspace_members where workspace_id=p_workspace and is_active=true; + select count(*)::int into v_pending from public.sun_workspace_invites where workspace_id=p_workspace and used_at is null and expires_at>now(); + if v_active+v_pending>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if; + end if; + + insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by,email,display_name) + values(p_workspace,v_role,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),auth.uid(),v_email,left(v_name,120)) + returning token into v_token; + return v_token; +end; +$$; + +create or replace function public.sun_list_workspace_invites_v27(p_workspace uuid) +returns table(token uuid,email text,display_name text,role text,created_at timestamptz,expires_at timestamptz,status text) +language plpgsql +stable +security definer +set search_path='public' +as $$ +begin + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может просматривать приглашения'; end if; + return query + select i.token,i.email,i.display_name,i.role,i.created_at,i.expires_at, + case when i.expires_at<=now() then 'expired' else 'pending' end::text + from public.sun_workspace_invites i + where i.workspace_id=p_workspace and i.used_at is null + order by i.created_at desc; +end; +$$; + +create or replace function public.sun_cancel_invite_v27(p_workspace uuid,p_token uuid) +returns boolean +language plpgsql +security definer +set search_path='public' +as $$ +declare v_deleted integer; +begin + if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может отменять приглашения'; end if; + delete from public.sun_workspace_invites where workspace_id=p_workspace and token=p_token and used_at is null; + get diagnostics v_deleted=row_count; + return v_deleted>0; +end; +$$; + +revoke all on function public.sun_list_workspace_members(uuid) from public,anon; +revoke all on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) from public,anon; +revoke all on function public.sun_admin_remove_member(uuid,uuid) from public,anon; +revoke all on function public.sun_employee_prepare_v28(uuid,text,text,text) from public,anon; +revoke all on function public.sun_employee_finalize_v28(uuid,uuid,text,text) from public,anon; +revoke all on function public.sun_create_invite(uuid,text) from public,anon; +revoke all on function public.sun_create_invite_v27(uuid,text,text,text) from public,anon; +revoke all on function public.sun_list_workspace_invites_v27(uuid) from public,anon; +revoke all on function public.sun_cancel_invite_v27(uuid,uuid) from public,anon; + +grant execute on function public.sun_list_workspace_members(uuid) to authenticated; +grant execute on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) to authenticated; +grant execute on function public.sun_admin_remove_member(uuid,uuid) to authenticated; +grant execute on function public.sun_employee_prepare_v28(uuid,text,text,text) to authenticated; +grant execute on function public.sun_employee_finalize_v28(uuid,uuid,text,text) to authenticated; +grant execute on function public.sun_create_invite(uuid,text) to authenticated; +grant execute on function public.sun_create_invite_v27(uuid,text,text,text) to authenticated; +grant execute on function public.sun_list_workspace_invites_v27(uuid) to authenticated; +grant execute on function public.sun_cancel_invite_v27(uuid,uuid) to authenticated; From 0e1a7c98e895e9193b921efd7b38dbb629167280 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:48:06 +0300 Subject: [PATCH 13/21] feat: add owner and employee account center --- public/core/account-center-v1780.js | 126 ++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 public/core/account-center-v1780.js diff --git a/public/core/account-center-v1780.js b/public/core/account-center-v1780.js new file mode 100644 index 0000000..3b3c9ec --- /dev/null +++ b/public/core/account-center-v1780.js @@ -0,0 +1,126 @@ +(()=>{ +'use strict'; +if(window.CateriumAccountCenterV1780)return; +const VERSION='17.8.0-account-center-v1'; +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=''; + +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{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-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{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} + @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='

Мой аккаунт

Профиль и безопасность Caterium
'; + document.body.appendChild(modal); + 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 {data,error}=await c.rpc('caterium_account_snapshot',{p_workspace:ws.id}); + if(error)throw error;snapshot=data||null;return snapshot; +} +function render(){ + const body=$('#cateriumAccountCenterBody');if(!body)return; + const ss=session(),s=snapshot||{},owner=Boolean(s.is_owner); + const companyNo=s.company_number?`№${s.company_number}`:'—'; + body.innerHTML=`
+
+

Профиль

+ ${owner?'
Главный аккаунт компанииТолько этот аккаунт управляет сотрудниками и их правами.
':''} +
Компания${esc(s.company_name||'—')} · ${esc(companyNo)}
Статус${esc(owner?'Владелец':ROLE_LABELS[s.role]||s.role||'Сотрудник')}
+ +
+
+
+

Email для входа

+ + +
+
После смены Supabase отправит письмо подтверждения на новый адрес.
+
+
+

Пароль

+ + +
+
+ ${owner?`

Компания и пользователи

Создавайте сотрудников, назначайте роли и выдавайте доступ к нужным разделам Caterium.

`:''} +

Сеанс

+
`; + $('#cacSaveName').onclick=saveName;$('#cacSaveEmail').onclick=saveEmail;$('#cacSavePassword').onclick=savePassword;$('#cacLogout').onclick=logout; + const users=$('#cacOpenUsers');if(users)users.onclick=()=>{close();window.SunAdminRBACV3?.openUsers?.();}; +} +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(){try{await cloud()?.signOut?.()}catch(_){try{await client()?.auth.signOut()}catch(__){}location.reload()}} +async function open(){ + ensureModal();modal.classList.add('on');const body=$('#cateriumAccountCenterBody');body.innerHTML='

Загружаю аккаунт…

'; + try{await loadSnapshot();render()}catch(e){body.innerHTML=`

${esc(e?.message||e||'Не удалось загрузить аккаунт.')}

`} +} +function close(){modal?.classList.remove('on')} +function bindEntry(){ + const label=$('#sunCurrentUserLabel');if(label&&!label.dataset.cateriumAccountBound){label.dataset.cateriumAccountBound='1';label.title='Открыть личный кабинет';label.addEventListener('click',open)} + const ss=session();const uid=ss?.user?.id||'';if(uid&&uid!==lastUserId){lastUserId=uid;snapshot=null;} +} +installStyle();ensureModal();bindEntry(); +const obs=new MutationObserver(bindEntry);obs.observe(document.documentElement,{childList:true,subtree:true}); +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}}); +})(); From 9c868aa99b5a3eb1d9c911d796fd5546ba7e98ae Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:48:39 +0300 Subject: [PATCH 14/21] fix: lock company owner and simplify employee accounts --- public/core/account-center-v1780.js | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/public/core/account-center-v1780.js b/public/core/account-center-v1780.js index 3b3c9ec..a9a0766 100644 --- a/public/core/account-center-v1780.js +++ b/public/core/account-center-v1780.js @@ -1,7 +1,7 @@ (()=>{ 'use strict'; if(window.CateriumAccountCenterV1780)return; -const VERSION='17.8.0-account-center-v1'; +const VERSION='17.8.0-account-center-v2'; 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??''); @@ -38,10 +38,12 @@ function installStyle(){ #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{color:#725317}.cac-owner span{color:#8a6e36;font-size:12px} + #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} + #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} @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); } @@ -65,7 +67,7 @@ function render(){ body.innerHTML=`

Профиль

- ${owner?'
Главный аккаунт компанииТолько этот аккаунт управляет сотрудниками и их правами.
':''} + ${owner?'
Главный аккаунт компанииТолько этот аккаунт создаёт сотрудников и распределяет права.
':''}
Компания${esc(s.company_name||'—')} · ${esc(companyNo)}
Статус${esc(owner?'Владелец':ROLE_LABELS[s.role]||s.role||'Сотрудник')}
@@ -75,7 +77,7 @@ function render(){
-
После смены Supabase отправит письмо подтверждения на новый адрес.
+
На новый адрес придёт письмо подтверждения.

Пароль

@@ -83,16 +85,15 @@ function render(){
- ${owner?`

Компания и пользователи

Создавайте сотрудников, назначайте роли и выдавайте доступ к нужным разделам Caterium.

`:''} + ${owner?`

Компания и пользователи

Создавайте сотрудников, назначайте им рабочую роль и включайте только нужные права.

`:''}

Сеанс

`; $('#cacSaveName').onclick=saveName;$('#cacSaveEmail').onclick=saveEmail;$('#cacSavePassword').onclick=savePassword;$('#cacLogout').onclick=logout; - const users=$('#cacOpenUsers');if(users)users.onclick=()=>{close();window.SunAdminRBACV3?.openUsers?.();}; + 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; + 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} } @@ -114,9 +115,20 @@ async function open(){ try{await loadSnapshot();render()}catch(e){body.innerHTML=`

${esc(e?.message||e||'Не удалось загрузить аккаунт.')}

`} } function close(){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='Главный аккаунт';}} + } + const addRole=$('#sunRbacEmployeeRole',body);if(addRole)qa('option[value="admin"]',addRole).forEach(o=>o.remove()); +} function bindEntry(){ const label=$('#sunCurrentUserLabel');if(label&&!label.dataset.cateriumAccountBound){label.dataset.cateriumAccountBound='1';label.title='Открыть личный кабинет';label.addEventListener('click',open)} const ss=session();const uid=ss?.user?.id||'';if(uid&&uid!==lastUserId){lastUserId=uid;snapshot=null;} + sanitizeRbac(); } installStyle();ensureModal();bindEntry(); const obs=new MutationObserver(bindEntry);obs.observe(document.documentElement,{childList:true,subtree:true}); From 5cff7135f23b174920b0555a00ecedcf16c8bc8e Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:48:57 +0300 Subject: [PATCH 15/21] feat: load Caterium account center --- public/core/sun-safe.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/core/sun-safe.js b/public/core/sun-safe.js index 7f8896f..7ead66e 100644 --- a/public/core/sun-safe.js +++ b/public/core/sun-safe.js @@ -20,4 +20,14 @@ return node; }; window.SunSafe=Object.freeze({escapeHTML,escapeAttr,idToken,safeImageSrc,setText,insertBefore}); -})(); + + // Small bootstrap for account/profile UI. Keeping it here makes the account + // center available on every Caterium screen without touching the legacy monolith. + if(!document.getElementById('cateriumAccountCenterV1780Script')){ + const script=document.createElement('script'); + script.id='cateriumAccountCenterV1780Script'; + script.src='core/account-center-v1780.js?v=20260912-v17-8-0-account-center-2'; + script.async=true; + document.head.appendChild(script); + } +})(); \ No newline at end of file From 3a54721594e1f2a577b3ad50770ab3c93a826ae4 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:51:55 +0300 Subject: [PATCH 16/21] chore: refresh account center assets --- public/service-worker.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index 63e7a9c..daffb04 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,15 +1,17 @@ -const CACHE='sun-catering-pwa-v78-20260909-v17-7-3-clients-server-read-20260912-login-refresh'; -const VERSION='20260912-login-refresh'; +const CACHE='sun-catering-pwa-v80-20260912-account-center'; +const VERSION='20260912-account-center'; const CORE=[ './','./index.html', - `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, + `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/account-center-v1780.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, './offer-gallery/001.jpg','./offer-gallery/002.jpg', './catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg', './sun-logo.png','./caterium-login-logo.png','./caterium-mark-light.svg','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest', './offer-templates/thumb-light.jpg','./offer-templates/thumb-editorial-grid.jpg','./offer-templates/thumb-midnight-glass.jpg','./offer-templates/thumb-emerald-gold.jpg' ]; const CRITICAL_FRESH=new Set([ + '/core/sun-safe.js', '/core/performance.js', + '/core/account-center-v1780.js', '/core/login-signature-v1776.js', '/core/auth-security-v1774.js', '/legacy/bootstrap.js', @@ -44,4 +46,4 @@ self.addEventListener('fetch',event=>{ const freshAsset=/\.(?:js|css|webmanifest)$/i.test(url.pathname); if(freshAsset){event.respondWith(networkFirst(req));return;} event.respondWith(caches.match(req).then(cached=>cached||fetch(req,{cache:'no-store'}).then(res=>cachePut(req,res)))); -}); +}); \ No newline at end of file From 10f3074319f655aa39fedfc81e607f30285b9169 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:52:57 +0300 Subject: [PATCH 17/21] fix: prevent employee accounts from becoming company owner --- supabase/functions/caterium-create-employee/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/supabase/functions/caterium-create-employee/index.ts b/supabase/functions/caterium-create-employee/index.ts index d756182..33b416d 100644 --- a/supabase/functions/caterium-create-employee/index.ts +++ b/supabase/functions/caterium-create-employee/index.ts @@ -10,7 +10,7 @@ const WORKERS_DEV_ORIGIN = /^https:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.workers\.dev const LOCAL_ORIGIN = /^http:\/\/(?:localhost|127\.0\.0\.1)(?::\d{1,5})?$/i; const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; -const ALLOWED_ROLES = new Set(["admin", "manager", "kitchen", "courier", "viewer"]); +const ALLOWED_ROLES = new Set(["manager", "kitchen", "courier", "viewer"]); function allowedOrigin(origin: string) { if (!origin) return true; @@ -42,7 +42,7 @@ type PrepData = { function publicError(error: unknown) { const message = error instanceof Error ? error.message : String(error || ""); - if (/access denied|permission|not allowed|недостаточно прав|сначала войдите/i.test(message)) return { status: 403, error: "Недостаточно прав для добавления сотрудника.", code: "forbidden" }; + if (/access denied|permission|not allowed|недостаточно прав|сначала войдите|только владелец/i.test(message)) return { status: 403, error: "Только владелец компании может добавлять сотрудников.", code: "forbidden" }; if (/subscription|blocked|plan|подписк|тариф/i.test(message)) return { status: 403, error: "Добавление сотрудника недоступно для текущего тарифа.", code: "plan_restricted" }; if (/limit|maximum|max_members|лимит/i.test(message)) return { status: 409, error: "Достигнут лимит сотрудников для текущего тарифа.", code: "member_limit" }; if (/already registered|already exists|duplicate|уже существует/i.test(message)) return { status: 409, error: "Аккаунт с этим email уже существует.", code: "account_exists" }; @@ -82,7 +82,7 @@ Deno.serve(async (req: Request) => { if (!UUID_RE.test(workspaceId)) return reply(req, { error: "Некорректная компания.", code: "invalid_workspace" }, 400); if (!EMAIL_RE.test(email) || email.length > 254) return reply(req, { error: "Введите корректный email.", code: "invalid_email" }, 400); if (displayName.length > 120) return reply(req, { error: "Имя сотрудника слишком длинное.", code: "invalid_display_name" }, 400); - if (!ALLOWED_ROLES.has(role)) return reply(req, { error: "Некорректная роль сотрудника.", code: "invalid_role" }, 400); + if (!ALLOWED_ROLES.has(role)) return reply(req, { error: "Для сотрудника выберите рабочую роль: менеджер, кухня, курьер или просмотр.", code: "invalid_role" }, 400); const caller = createClient(url, anon, { global: { headers: { Authorization: auth } }, @@ -183,4 +183,4 @@ Deno.serve(async (req: Request) => { const safe = publicError(error); return reply(req, { error: safe.error, code: safe.code }, safe.status); } -}); +}); \ No newline at end of file From 879a7e0f837f327dccfefab7557e5bcf2b3919a0 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:53:36 +0300 Subject: [PATCH 18/21] test: include account center in deploy checks --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5bf3ccb..1b65342 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "caterium-app", "private": true, - "version": "17.7.3", + "version": "17.8.0", "type": "module", "scripts": { - "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/auth-security-v1774.js && node --check public/core/order-enhancements-v1775.js && node --check public/core/data-layer-v1773.js && node --check public/core/server-automation-v1770.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js", + "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/account-center-v1780.js && node --check public/core/performance.js && node --check public/core/auth-security-v1774.js && node --check public/core/order-enhancements-v1775.js && node --check public/core/data-layer-v1773.js && node --check public/core/server-automation-v1770.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js", "test:static": "node tests/static-security.mjs && node tests/auth-security-v1774.mjs && node tests/employee-create-v1774.mjs && node tests/html-integrity-v1774.mjs && node tests/edge-security-v1774.mjs && node tests/branding-v1774.mjs && node tests/order-enhancements-v1775.mjs", "check:release": "node tests/release-check.mjs", "check:deploy": "npm run check:syntax && npm run test:static && npm run check:release", @@ -17,4 +17,4 @@ "http-server": "^14.1.1", "wrangler": "4.131.0" } -} +} \ No newline at end of file From f0629ba1a47dea633a5537a09efce5d09f84d381 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 08:53:42 +0300 Subject: [PATCH 19/21] test: enforce owner-only employee roles --- tests/employee-create-v1774.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/employee-create-v1774.mjs b/tests/employee-create-v1774.mjs index 82a9847..12ea70f 100644 --- a/tests/employee-create-v1774.mjs +++ b/tests/employee-create-v1774.mjs @@ -7,4 +7,6 @@ check(src.includes('return await finalize(existingUserId, false, null)'),'existi check(src.includes('retryStatus === "existing"'),'partial/concurrent auth creation retries finalize'); check(src.includes('sun_employee_finalize_v28'),'employee finalize RPC remains required'); check(!src.includes('if (prep.data?.status !== "new")'),'old early-return bug is removed'); -if(bad)process.exit(1); +check(src.includes('new Set(["manager", "kitchen", "courier", "viewer"])'),'employee endpoint cannot create another company owner/admin'); +check(!src.includes('new Set(["admin", "manager"'),'admin role is excluded from employee creation'); +if(bad)process.exit(1); \ No newline at end of file From bd7995d68c514a788b0d35dd179466d5a97bb4ca Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 10:10:25 +0300 Subject: [PATCH 20/21] fix: expose manager account and password controls --- public/core/account-center-v1780.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/public/core/account-center-v1780.js b/public/core/account-center-v1780.js index a9a0766..070e41e 100644 --- a/public/core/account-center-v1780.js +++ b/public/core/account-center-v1780.js @@ -1,7 +1,7 @@ (()=>{ 'use strict'; if(window.CateriumAccountCenterV1780)return; -const VERSION='17.8.0-account-center-v2'; +const VERSION='17.8.0-account-center-v3'; 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??''); @@ -42,6 +42,7 @@ function installStyle(){ #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} @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}} @@ -80,10 +81,11 @@ function render(){
На новый адрес придёт письмо подтверждения.
-

Пароль

+

Смена пароля

+
Пароль меняется только для вашего аккаунта. Администратор компании его не видит.
${owner?`

Компания и пользователи

Создавайте сотрудников, назначайте им рабочую роль и включайте только нужные права.

`:''}

Сеанс

@@ -125,10 +127,21 @@ function sanitizeRbac(){ } 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='Мой аккаунт'; + footer.insertBefore(button,footer.querySelector('#sunLogoutBtn')||footer.firstChild); + } + if(!button.dataset.cateriumAccountBound){button.dataset.cateriumAccountBound='1';button.addEventListener('click',open)} +} function bindEntry(){ const label=$('#sunCurrentUserLabel');if(label&&!label.dataset.cateriumAccountBound){label.dataset.cateriumAccountBound='1';label.title='Открыть личный кабинет';label.addEventListener('click',open)} const ss=session();const uid=ss?.user?.id||'';if(uid&&uid!==lastUserId){lastUserId=uid;snapshot=null;} - sanitizeRbac(); + ensureSidebarEntry();sanitizeRbac(); } installStyle();ensureModal();bindEntry(); const obs=new MutationObserver(bindEntry);obs.observe(document.documentElement,{childList:true,subtree:true}); From ce1930ef5d060e7865790ca5af32a622d08aed39 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sat, 12 Sep 2026 10:11:09 +0300 Subject: [PATCH 21/21] fix: always load account center for authenticated users --- public/service-worker.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index daffb04..4cf5af7 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,5 +1,5 @@ -const CACHE='sun-catering-pwa-v80-20260912-account-center'; -const VERSION='20260912-account-center'; +const CACHE='sun-catering-pwa-v81-20260912-account-center-loader'; +const VERSION='20260912-account-center-loader'; const CORE=[ './','./index.html', `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/account-center-v1780.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, @@ -34,11 +34,22 @@ function forceFresh(req){ url.searchParams.set('__caterium_release',VERSION); return fetch(url.toString(),{cache:'no-store',credentials:'same-origin'}).then(res=>cachePut(req,res)).catch(()=>caches.match(req)); } +async function withAccountCenter(res){ + if(!res)return res; + const type=String(res.headers.get('content-type')||''); + if(!type.includes('text/html'))return res; + const html=await res.text(); + if(html.includes('core/account-center-v1780.js'))return new Response(html,{status:res.status,statusText:res.statusText,headers:res.headers}); + const tag=``; + const out=html.includes('')?html.replace('',`${tag}`):html+tag; + const headers=new Headers(res.headers);headers.set('content-type','text/html; charset=utf-8');headers.set('cache-control','no-store, max-age=0'); + return new Response(out,{status:res.status,statusText:res.statusText,headers}); +} self.addEventListener('fetch',event=>{ const req=event.request;if(req.method!=='GET')return; const url=new URL(req.url);if(url.pathname.startsWith('/api/'))return; if(req.mode==='navigate'){ - event.respondWith(fetch(req,{cache:'no-store'}).then(res=>{const clone=res.clone();caches.open(CACHE).then(c=>c.put('./index.html',clone)).catch(()=>{});return res}).catch(()=>caches.match('./index.html')));return; + event.respondWith(fetch(req,{cache:'no-store'}).then(async res=>{const clone=res.clone();caches.open(CACHE).then(c=>c.put('./index.html',clone)).catch(()=>{});return withAccountCenter(res)}).catch(async()=>withAccountCenter(await caches.match('./index.html'))));return; } if(CRITICAL_FRESH.has(url.pathname)){ event.respondWith(forceFresh(req));return; @@ -46,4 +57,4 @@ self.addEventListener('fetch',event=>{ const freshAsset=/\.(?:js|css|webmanifest)$/i.test(url.pathname); if(freshAsset){event.respondWith(networkFirst(req));return;} event.respondWith(caches.match(req).then(cached=>cached||fetch(req,{cache:'no-store'}).then(res=>cachePut(req,res)))); -}); \ No newline at end of file +});