/* Portfolio data layer. Reads from the database when the backend is configured, otherwise from the content hard-coded in Shared.jsx.txt and CityPage.jsx.txt. Every function resolves to the same shapes the pages already use. */ (function(){ const staticCities=()=>window.VEDUTA_CITIES.map(([name,slug,tags])=>({slug,name,tags,seasonal:false,season:null,emoji:null,subtitle:null,hero:'assets/cities/'+slug+'/hero.jpg'})) .concat(window.VEDUTA_SEASONALS.map(([emoji,name,subtitle,season,slug])=>({slug,name,tags:(window.VEDUTA_CITY_DETAIL[name]||{}).tags||[],seasonal:true,season,emoji,subtitle,hero:'assets/cities/'+slug+'/hero.jpg'}))); const staticCity=(name)=>{ const d=window.VEDUTA_CITY_DETAIL[name]; if(!d)return null; const base='assets/cities/'+d.slug+'/'; return {name,slug:d.slug,tags:d.tags,blurb:d.blurb,season:d.season||null,hero:base+'hero.jpg', props:d.props.map(([n,de,p,tags],i)=>({id:null,name:n,description:de,price:p,tags:tags||[],image:base+'p'+(i+1)+'.jpg'})), rest:d.rest.map(([n,v,p])=>({id:null,name:n,note:v,band:p,url:null})), exc:d.exc.map(([t,de,p,rating,time,loc],i)=>({id:null,title:t,description:de,price:p,rating,duration:time,location:loc,image:base+'e'+(i+1)+'.jpg'}))}; }; const enabled=()=>!!(window.vedutaAuth&&window.vedutaAuth.state.enabled&&window.vedutaAuth.state.client); const sb=()=>window.vedutaAuth.state.client; let citiesCache=null; const data={ /* Waits until the backend bootstrap has decided whether it is configured. */ ready(){ const st=window.vedutaAuth.state; if(st.ready)return Promise.resolve(st.enabled); return new Promise(res=>{let done=false;const off=window.vedutaAuth.subscribe(s=>{if(s.ready&&!done){done=true;res(s.enabled);setTimeout(()=>off&&off(),0);}});}); }, async listCities(){ await data.ready(); if(!enabled())return staticCities(); if(citiesCache)return citiesCache; const {data:rows,error}=await sb().from('cities').select('slug,name,tags,seasonal,season,emoji,subtitle,hero_image').eq('published',true).order('sort_order'); if(error||!rows||!rows.length)return staticCities(); citiesCache=rows.map(r=>({slug:r.slug,name:r.name,tags:r.tags||[],seasonal:r.seasonal,season:r.season,emoji:r.emoji,subtitle:r.subtitle,hero:r.hero_image||('assets/cities/'+r.slug+'/hero.jpg')})); return citiesCache; }, async getCity(name){ await data.ready(); const fallback=staticCity(name); if(!enabled())return fallback; const slug=fallback?fallback.slug:name.toLowerCase().replace(/\s+/g,'-'); const c=sb(); const [city,props,rest,exc]=await Promise.all([ c.from('cities').select('*').eq('slug',slug).eq('published',true).maybeSingle(), c.from('properties').select('*').eq('city_slug',slug).eq('published',true).order('sort_order'), c.from('restaurants').select('*').eq('city_slug',slug).eq('published',true).order('sort_order'), c.from('excursions').select('*').eq('city_slug',slug).eq('published',true).order('sort_order') ]); if(city.error||!city.data)return fallback; const r=city.data; return {name:r.name,slug:r.slug,tags:r.tags||[],blurb:r.blurb,season:r.season,hero:r.hero_image||('assets/cities/'+r.slug+'/hero.jpg'),credit:r.hero_credit||null, props:(props.data||[]).map((p,i)=>({id:p.id,name:p.name,description:p.description,price:p.price_note,tags:p.tags||[],image:p.image||('assets/cities/'+r.slug+'/p'+(i+1)+'.jpg')})), rest:(rest.data||[]).map(x=>({id:x.id,name:x.name,note:x.note,band:x.price_band,url:x.url})), exc:(exc.data||[]).map((e,i)=>({id:e.id,title:e.title,description:e.description,price:e.price_note,rating:e.rating==null?null:Number(e.rating),duration:e.duration,location:e.location,image:e.image||('assets/cities/'+r.slug+'/e'+(i+1)+'.jpg')}))}; }, /* Member data (requires a signed-in user). */ async myProfile(){ if(!enabled()||!window.vedutaAuth.state.user)return null; const {data:row}=await sb().from('profiles').select('*').eq('id',window.vedutaAuth.state.user.id).maybeSingle(); return row||null; }, async saveProfile(fields){ if(!enabled()||!window.vedutaAuth.state.user)return {ok:false,error:'Not signed in.'}; const {error}=await sb().from('profiles').update(fields).eq('id',window.vedutaAuth.state.user.id); return error?{ok:false,error:error.message}:{ok:true}; }, async myRequests(){ if(!enabled()||!window.vedutaAuth.state.user)return []; const {data:rows}=await sb().from('booking_requests').select('id,kind,city_slug,start_date,end_date,guests,notes,status,created_at').order('created_at',{ascending:false}); return rows||[]; }, async createRequest(req){ if(!enabled()||!window.vedutaAuth.state.user)return {ok:false,error:'Please sign in first.'}; const {error}=await sb().from('booking_requests').insert({...req,user_id:window.vedutaAuth.state.user.id}); return error?{ok:false,error:error.message}:{ok:true}; }, async cancelRequest(id){ if(!enabled())return {ok:false}; const {error}=await sb().from('booking_requests').update({status:'canceled'}).eq('id',id); return error?{ok:false,error:error.message}:{ok:true}; }, /* Shapes used by the home and destinations grids. */ toCards(cities){ return {cities:cities.filter(c=>!c.seasonal).map(c=>[c.name,c.slug,c.tags,c.hero]),seasonals:cities.filter(c=>c.seasonal).map(c=>[c.emoji,c.name,c.subtitle,c.season,c.slug,c.hero])}; } }; window.vedutaData=data; })();