From caa13eca593659b3ad46db4973a0c9349e09d76f Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Tue, 15 Sep 2026 16:57:56 +0300 Subject: [PATCH] feat: add "work locally without internet" option to login gate When Supabase is unreachable, employees were stuck on the login screen with no way in, even though the app is fully usable offline (orders, catalog, stock, etc. all live in localStorage already). Adds a low-key link on the login screen that sets a local-only flag and skips the auth gate entirely until a real cloud sign-in succeeds (which clears the flag). Deliberately does not touch any cloud/session state, so it can't trigger an automatic cloud pull that would overwrite data created while working offline. Co-Authored-By: Claude Sonnet 5 --- public/app-runtime.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app-runtime.js b/public/app-runtime.js index 529cd4b..28d12df 100644 --- a/public/app-runtime.js +++ b/public/app-runtime.js @@ -3549,6 +3549,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс } const PENDING_REGISTRATION_KEY='sunPendingRegistrationV23'; + const LOCAL_ONLY_KEY='sunLocalOnlyModeV1'; let registrationProvisioning=false; function savePendingRegistration(email,companyName){try{localStorage.setItem(PENDING_REGISTRATION_KEY,JSON.stringify({email:String(email||'').trim().toLowerCase(),companyName:String(companyName||'').trim()||'Новая компания',createdAt:new Date().toISOString()}));}catch(_){}} function getPendingRegistration(email=''){try{const raw=JSON.parse(localStorage.getItem(PENDING_REGISTRATION_KEY)||'null');if(!raw?.email)return null;if(email&&String(raw.email).toLowerCase()!==String(email).trim().toLowerCase())return null;return raw}catch(_){return null}} @@ -3605,7 +3606,8 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс if(inviteToken){ let gate=$('sunCloudAuthGateV3');if(!gate){document.body.classList.add('sun-cloud-auth-required');gate=document.createElement('div');gate.id='sunCloudAuthGateV3';gate.className='sun-cloud-auth-gate';gate.innerHTML='

Проверяю приглашение…

';document.body.appendChild(gate);renderInviteGate(gate,inviteToken);}return; } - if(st.signedIn&&ws){$('sunCloudAuthGateV3')?.remove();document.body.classList.remove('sun-cloud-auth-required');return;} + if(st.signedIn&&ws){$('sunCloudAuthGateV3')?.remove();document.body.classList.remove('sun-cloud-auth-required');try{localStorage.removeItem(LOCAL_ONLY_KEY)}catch(_){}return;} + if(!st.signedIn){try{if(localStorage.getItem(LOCAL_ONLY_KEY)==='1'){$('sunCloudAuthGateV3')?.remove();document.body.classList.remove('sun-cloud-auth-required');return;}}catch(_){}} if($('sunCloudAuthGateV3')){if(st.signedIn&&st.membershipsLoading){const e=$('sunGateErrorV3');if(e)e.textContent='Загружаю рабочую базу…';}return;} document.body.classList.add('sun-cloud-auth-required'); const gate=document.createElement('div');gate.id='sunCloudAuthGateV3';gate.className='sun-cloud-auth-gate'; @@ -3616,7 +3618,8 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс gate.querySelector('#sunGateRetryWorkspaceV3').onclick=async()=>{const e=gate.querySelector('#sunGateErrorV3');if(getPendingRegistration(ss.user.email||''))return finishPendingRegistration(gate,ss.user.email||'');if(!st.membershipsLoading){const companyName=String(gate.querySelector('#sunGateRecoveryCompanyV25')?.value||'').trim();if(!companyName){e.textContent='Введите название компании.';return;}savePendingRegistration(ss.user.email||'',companyName);return finishPendingRegistration(gate,ss.user.email||'');}e.textContent='Проверяю доступ…';try{const found=await cloud()?.reloadMemberships?.();if(found){gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();setTimeout(applyNavPermissions,100);}else e.textContent='Рабочая база для этого аккаунта не найдена.';}catch(err){e.textContent=errText(err,'Не удалось проверить доступ.');}}; gate.querySelector('#sunGateSignOutV3').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut();}catch(_){}location.reload();};return; } - gate.innerHTML=`
Caterium

Вход в Caterium

Введите данные своего аккаунта
${passwordField('sunGatePasswordV3','Пароль','current-password')}

Нет аккаунта?

`;document.body.appendChild(gate);bindPasswordEyes(gate); + gate.innerHTML=`
Caterium

Вход в Caterium

Введите данные своего аккаунта
${passwordField('sunGatePasswordV3','Пароль','current-password')}

Нет аккаунта?

`;document.body.appendChild(gate);bindPasswordEyes(gate); + gate.querySelector('#sunGateLocalOnlyV1').onclick=()=>{try{localStorage.setItem(LOCAL_ONLY_KEY,'1')}catch(_){}location.reload();}; const emailInput=gate.querySelector('#sunGateEmailV3'),passwordInput=gate.querySelector('#sunGatePasswordV3'),password2Input=gate.querySelector('#sunGatePassword2V27'),companyInput=gate.querySelector('#sunGateCompanyV3'),submitBtn=gate.querySelector('#sunGateSubmitV3'),gateError=gate.querySelector('#sunGateErrorV3');let registerMode=false; const setMode=(registration)=>{registerMode=Boolean(registration);gate.querySelector('#sunGateRegisterFieldsV27').hidden=!registerMode;gate.querySelector('#sunGateConfirmWrapV27').hidden=!registerMode;gate.querySelector('#sunGateTitleV3').textContent=registerMode?'Создать аккаунт Caterium':'Вход в Caterium';gate.querySelector('#sunGateSubtitleV3').textContent=registerMode?'Заполните данные — после регистрации Caterium откроется автоматически':'Введите данные своего аккаунта';submitBtn.textContent=registerMode?'Создать аккаунт':'Войти';gate.querySelector('#sunGateSwitchTextV27').textContent=registerMode?'Уже есть аккаунт?':'Нет аккаунта?';gate.querySelector('#sunGateSwitchV27').textContent=registerMode?'Войти':'Создать аккаунт';passwordInput.autocomplete=registerMode?'new-password':'current-password';gateError.textContent='';if(registerMode)setTimeout(()=>companyInput.focus(),30);else setTimeout(()=>emailInput.focus(),30)}; async function auth(){const c=client(),email=emailInput.value.trim(),password=passwordInput.value,password2=password2Input.value,companyName=String(companyInput.value||'').trim();if(!c){gateError.textContent='Облачный сервис не подключён.';return;}if(registerMode&&!companyName){gateError.textContent='Введите название компании.';return;}if(!email||password.length<6){gateError.textContent='Введите email и пароль минимум из 6 символов.';return;}if(registerMode&&password!==password2){gateError.textContent='Пароли не совпадают.';return;}submitBtn.disabled=true;gateError.textContent=registerMode?'Создаю аккаунт…':'Выполняю вход…';try{let r;if(registerMode){savePendingRegistration(email,companyName);const existing=await c.auth.signInWithPassword({email,password});if(!existing.error&&existing.data.session){r=existing;}else{r=await c.auth.signUp({email,password,options:{data:{company_name:companyName,registration_source:'caterium_public_signup'}}});if(r.error)throw r.error;if(!r.data.session){const retry=await c.auth.signInWithPassword({email,password});if(retry.error){clearPendingRegistration();throw new Error('Этот email уже зарегистрирован. Войдите в аккаунт или восстановите пароль.');}r=retry;}}}else r=await c.auth.signInWithPassword({email,password});if(r.error)throw r.error;const authUser=r.data.user||r.data.session?.user||null;const userId=authUser?.id||'';if(userId)sessionStorage.setItem(`sunCloudQuickPinUnlockedV3:${userId}`,'1');gateError.textContent=registerMode?'Аккаунт готов. Создаю компанию…':'Вход выполнен. Загружаю рабочую базу…';setTimeout(async()=>{await cloud()?.reloadMemberships?.();if(registerMode&&workspace()){clearPendingRegistration();gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();return;}if(getPendingRegistration(email)&&!workspace()){await finishPendingRegistration(gate,email);return;}if(workspace()){gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();}else{gate.remove();ensureAuthGate();}},180);}catch(err){gateError.textContent=errText(err,'Не удалось выполнить операцию.')}finally{submitBtn.disabled=false}}