${esc(r.client_email||'Любой email')} · Подписка: ${Number(r.trial_days)} дн.
Активировать до: ${esc(fmt(r.valid_until))} · Использовано: ${Number(r.use_count)} / ${Number(r.max_uses)} · ${r.is_active?'Включён':'Отключён'}
/* Developer invitations, using the existing server-enforced AAL2 RPCs. */
(()=>{
'use strict';
if(window.CateriumTrialPromoDeveloperV181)return;
const VERSION='20260921-promo-entry',TAB='sunDeveloperActiveTabV22';
const $=id=>document.getElementById(id),cloud=()=>window.SunCloudV2;
const plans={basic:'Базовый',professional:'Профессиональный',full:'Полный'};
const esc=v=>window.SunSafe.escapeHTML(String(v??'')),fmt=v=>v?new Date(v).toLocaleString('ru-RU'):'—';
let view=null,frame=0;
const identity=()=>JSON.stringify([cloud()?.getSession?.()?.user?.id,cloud()?.getWorkspace?.()?.id]);
const allowed=()=>Boolean(cloud()?.getSession?.()?.user&&window.SunDeveloperV22?.isPlatformAdmin?.()&&!cloud()?.isSupportMode?.()&&!document.body.classList.contains('sun-cloud-auth-required'));
const selected=()=>allowed()&&$('sun-developer-console-v22')?.classList.contains('on')&&localStorage.getItem(TAB)==='promos';
const current=v=>view===v&&v.identity===identity()&&selected();
async function rpc(name,args={}){const c=cloud()?.getClient?.();if(!c||!allowed())throw new Error('Войдите в кабинет разработчика и подтвердите 2FA.');const r=await c.rpc(name,args);if(r.error)throw r.error;return r.data;}
const message=e=>['PGRST202','42883'].includes(e?.code)?'Серверный модуль промокодов недоступен.':String(e?.message||e||'Не удалось выполнить операцию.');
function styles(){
if($('cateriumPromoDevStyle'))return;
const s=document.createElement('style');s.id='cateriumPromoDevStyle';s.textContent=`
#ctmPromoOpen{min-height:44px}#ctmPromoPanel{min-width:0}.ctm-promo-tools{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;align-items:end}.ctm-promo-tools label{display:grid;gap:6px;min-width:0;font-size:12px;font-weight:700}.ctm-promo-tools input,.ctm-promo-tools select{box-sizing:border-box;width:100%;min-width:0;min-height:44px;padding:9px;border:1px solid #d8dbe0;border-radius:9px;font:inherit;background:#fff}.ctm-promo-wide{grid-column:1/-1}.ctm-promo-note{font-size:13px;line-height:1.5;color:#697078}.ctm-promo-result{padding:14px;border:1px solid #d5bb77;border-radius:12px;background:#fffaec;margin-top:14px}.ctm-promo-result textarea{box-sizing:border-box;width:100%;min-height:175px;margin:10px 0;padding:10px;resize:vertical;font:13px/1.5 Arial,sans-serif}.ctm-promo-actions{display:flex;flex-wrap:wrap;gap:8px}.ctm-promo-actions button{min-height:44px}.ctm-promo-code{font-family:ui-monospace,monospace;overflow-wrap:anywhere}#ctmPromoError,#ctmPromoListError{color:#a23426}#ctmPromoError:empty,#ctmPromoListError:empty{display:none}.ctm-promo-list{display:grid;gap:10px;margin-top:15px}.ctm-promo-list article{padding:12px;border:1px solid #e0e0dc;border-radius:10px;min-width:0}.ctm-promo-list p{font-size:12px;line-height:1.5;margin:6px 0}
@media(max-width:600px){.ctm-promo-tools{grid-template-columns:minmax(0,1fr)}#ctmPromoOpen{width:100%}.ctm-promo-actions button{flex:1}}
`;document.head.append(s);
}
function invitation(data,args){return `Приглашение в Caterium\nРегистрация: https://app.caterium.ru/\nНа экране входа выберите регистрацию и введите промокод: ${data.code}\nТариф: ${plans[args.p_plan]}\nСрок подписки: ${args.p_trial_days} дн. с активации после подтверждения email\nАктивировать приглашение до: ${fmt(data.valid_until)}${args.p_email?'\nДля email: '+args.p_email:''}`;}
async function copy(text,status){try{await navigator.clipboard.writeText(text);if(status.isConnected)status.textContent='Скопировано.';}catch(_){if(status.isConnected)status.textContent='Выделите и скопируйте текст вручную: автоматическое копирование недоступно.';}}
async function loadList(v){
const serial=++v.listRequest,list=v.node.querySelector('#ctmPromoList'),error=v.node.querySelector('#ctmPromoListError');error.textContent='';list.setAttribute('aria-busy','true');
try{
const rows=await rpc('sun_dev_list_trial_promos',{p_limit:300});if(!current(v)||serial!==v.listRequest)return;
if(!Array.isArray(rows))throw new Error('Сервер вернул неполный список кодов.');
list.innerHTML=rows.map((r,i)=>` ${esc(r.client_email||'Любой email')} · Подписка: ${Number(r.trial_days)} дн. Активировать до: ${esc(fmt(r.valid_until))} · Использовано: ${Number(r.use_count)} / ${Number(r.max_uses)} · ${r.is_active?'Включён':'Отключён'}
Промокодов пока нет. Создайте первый выше.
'; list.querySelectorAll('[data-copy-code]').forEach(b=>b.onclick=()=>copy(rows[Number(b.dataset.copyCode)].code,v.node.querySelector('#ctmPromoCopyStatus'))); list.querySelectorAll('[data-toggle-code]').forEach(b=>b.onclick=async()=>{const row=rows[Number(b.dataset.toggleCode)];if(!current(v))return;b.disabled=true;try{await rpc('sun_dev_set_trial_promo_active',{p_promo:row.promo_id,p_active:!row.is_active});if(current(v))await loadList(v);}catch(e){if(current(v))error.textContent=message(e);}finally{b.disabled=false;}}); }catch(e){if(current(v)&&serial===v.listRequest)error.textContent=message(e);}finally{if(serial===v.listRequest)list.removeAttribute('aria-busy');} } async function create(v){ if(!current(v)||v.creating)return; const form=v.node.querySelector('form');if(!form.reportValidity())return; const q=id=>v.node.querySelector('#'+id); const args={p_code:q('ctmPromoCode').value.trim().toUpperCase()||null,p_email:q('ctmPromoEmail').value.trim()||null,p_plan:q('ctmPromoPlan').value,p_trial_days:Number(q('ctmPromoTrial').value),p_valid_days:Number(q('ctmPromoValid').value),p_max_uses:Number(q('ctmPromoUses').value),p_note:null}; if(!plans[args.p_plan])return; const button=q('ctmPromoCreate'),error=q('ctmPromoError');v.creating=true;button.disabled=true;error.textContent=''; try{ const data=await rpc('sun_dev_create_trial_promo',args);if(!current(v))return; if(!data?.code)throw new Error('Сервер не вернул созданный код. Проверьте список перед повторной попыткой.'); q('ctmPromoCreated').innerHTML='Выберите тариф и срок доступа. Промокод можно сгенерировать автоматически или задать вручную. Срок подписки начинается при активации после подтверждения email, а срок действия кода — при его создании.