Spaces:
Running
Running
Download cloud-sync.js from Honda219/comp-tracker: direct link, hf CLI and curl.
- Browser
- Download file 6.39 kB
-
https://huggingface.co/spaces/Honda219/comp-tracker/resolve/main/cloud-sync.js
- Command line
-
hf download hf://spaces/Honda219/comp-tracker/cloud-sync.js
-
curl -L -o cloud-sync.js https://huggingface.co/spaces/Honda219/comp-tracker/resolve/main/cloud-sync.js
6.39 kB
| import{createClient}from'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/+esm'; | |
| const cfg=window.COMP_TRACKER_CONFIG,db=createClient(cfg.supabaseUrl,cfg.supabasePublishableKey,{auth:{persistSession:true,autoRefreshToken:true,detectSessionInUrl:true}}),CK='compensationClients',SK='sortState',PK='compensationCloudPending';let user,channel,timer,remoteWrite=false,lastIds=new Set(); | |
| const nativeSet=localStorage.setItem.bind(localStorage);localStorage.setItem=function(k,v){nativeSet(k,v);if(remoteWrite||!user)return;if(k===CK)queueSave();if(k===SK)setTimeout(saveSettings,400)}; | |
| document.body.insertAdjacentHTML('beforeend',`<section id="cloudAuthGate"><form id="cloudLogin" class="cloud-card"><h2>Comp Tracker</h2><p>Sign in to your private synchronized records.</p><label>Email</label><input id="cloudEmail" type="email" autocomplete="username" required><label>Password</label><input id="cloudPassword" type="password" autocomplete="current-password" required><p id="cloudError" class="cloud-error" role="alert"></p><button>Sign in</button></form></section><aside id="cloudBar" class="cloud-offline" aria-live="polite"><span class="cloud-dot"></span><span id="cloudStatus">Offline</span><button id="cloudSignOut">Sign out</button></aside>`); | |
| const gate=document.querySelector('#cloudAuthGate'),statusEl=document.querySelector('#cloudStatus'),bar=document.querySelector('#cloudBar');function status(k,t){bar.className='cloud-'+k;statusEl.textContent=t}function fail(e){console.error(e);status('failed','Failed — will retry')} | |
| function norm(c,i=0){return{id:c.id||crypto.randomUUID(),clientName:String(c.clientName||'').trim(),loanAmount:Number(c.loanAmount),pointsCharged:Number(c.pointsCharged),lenderPrice:Number(c.lenderPrice),compTier:c.compTier,closingDate:c.closingDate,excludePoints:Boolean(c.excludePoints),sortOrder:Number.isInteger(c.sortOrder)?c.sortOrder:i}} | |
| function local(){try{return(JSON.parse(localStorage.getItem(CK))||[]).map(norm)}catch{return[]}}function row(c,i){c=norm(c,i);return{id:c.id,owner_id:user.id,client_name:c.clientName,loan_amount:c.loanAmount,points_charged:c.pointsCharged,lender_price:c.lenderPrice,comp_tier:c.compTier,closing_date:c.closingDate,exclude_points:c.excludePoints,sort_order:c.sortOrder}}function client(r){return{id:r.id,clientName:r.client_name,loanAmount:Number(r.loan_amount),pointsCharged:Number(r.points_charged),lenderPrice:Number(r.lender_price),compTier:r.comp_tier,closingDate:r.closing_date,excludePoints:r.exclude_points,sortOrder:r.sort_order}} | |
| function setLocal(items,reload=false){remoteWrite=true;nativeSet(CK,JSON.stringify(items));remoteWrite=false;if(reload)location.reload()}function queueSave(){nativeSet(PK,'true');clearTimeout(timer);if(!navigator.onLine)return status('offline','Offline — pending');status('saving','Saving…');timer=setTimeout(()=>save(local()).catch(fail),650)} | |
| async function save(items){if(!navigator.onLine)return status('offline','Offline — pending');status('saving','Saving…');items=items.map(norm);setLocal(items);if(!lastIds.size)lastIds=new Set(JSON.parse(sessionStorage.getItem('compLastIds')||'[]'));const ids=new Set(items.map(x=>x.id)),removed=[...lastIds].filter(id=>!ids.has(id));if(items.length){const{error}=await db.from('compensation_clients').upsert(items.map(row));if(error)throw error}if(removed.length){const{error}=await db.from('compensation_clients').delete().in('id',removed);if(error)throw error}lastIds=ids;sessionStorage.setItem('compLastIds',JSON.stringify([...ids]));localStorage.removeItem(PK);status('saved',`Saved ${new Date().toLocaleTimeString([],{hour:'numeric',minute:'2-digit'})}`)} | |
| async function load(){if(!navigator.onLine)return status('offline','Offline');status('saving','Loading…');const{data,error}=await db.from('compensation_clients').select('*').order('sort_order').order('created_at');if(error)throw error;const cloud=(data||[]).map(client),here=local();lastIds=new Set(cloud.map(x=>x.id));sessionStorage.setItem('compLastIds',JSON.stringify([...lastIds]));if(!cloud.length&&here.length){if(confirm(`Upload ${here.length} existing record(s) to your private Supabase account?`))return save(here)}else if(cloud.length&&!localStorage.getItem(PK)&&JSON.stringify(cloud)!==JSON.stringify(here))return setLocal(cloud,true);status('saved','Saved')} | |
| async function saveSettings(){if(!navigator.onLine||!user)return;const s=JSON.parse(localStorage.getItem(SK)||'{}'),{error}=await db.from('compensation_settings').upsert({owner_id:user.id,sort_column:s.column||null,sort_direction:s.direction==='desc'?'desc':'asc'});if(error)fail(error)}async function loadSettings(){const{data,error}=await db.from('compensation_settings').select('*').maybeSingle();if(error)throw error;if(data?.sort_column)nativeSet(SK,JSON.stringify({column:data.sort_column,direction:data.sort_direction}))} | |
| function subscribe(){if(channel)db.removeChannel(channel);channel=db.channel('compensation:'+user.id).on('postgres_changes',{event:'*',schema:'public',table:'compensation_clients',filter:`owner_id=eq.${user.id}`},()=>{if(!localStorage.getItem(PK))setTimeout(()=>load().catch(fail),400)}).subscribe()} | |
| async function enter(u){user=u;gate.classList.add('hidden');try{await loadSettings();await load();subscribe()}catch(e){fail(e)}}function leave(){user=null;if(channel)db.removeChannel(channel);gate.classList.remove('hidden');status('offline','Signed out')} | |
| document.querySelector('#cloudLogin').addEventListener('submit',async e=>{e.preventDefault();const b=e.currentTarget.querySelector('button'),out=document.querySelector('#cloudError');out.textContent='';b.disabled=true;b.textContent='Signing in…';const{error}=await db.auth.signInWithPassword({email:document.querySelector('#cloudEmail').value.trim(),password:document.querySelector('#cloudPassword').value});if(error)out.textContent=error.message;b.disabled=false;b.textContent='Sign in'});document.querySelector('#cloudSignOut').onclick=()=>db.auth.signOut(); | |
| addEventListener('offline',()=>user&&status('offline','Offline'));addEventListener('online',()=>user&&(localStorage.getItem(PK)?save(local()):load()).catch(fail)); | |
| db.auth.onAuthStateChange((_e,s)=>{if(s?.user&&user?.id!==s.user.id)enter(s.user);else if(!s?.user&&user)leave()}); | |
| const{data:{session}}=await db.auth.getSession();if(session?.user&&user?.id!==session.user.id)await enter(session.user);else if(!session?.user)leave(); | |