// Instagram integration — profile header + live-style feed grid, all editable from the admin panel. const { useState: igUseState, useEffect: igUseEffect } = React; /* ——— Atualização automática das publicações ——— Dois caminhos, na ordem de preferência: 1) proxy: um endpoint do seu servidor que devolve {data:[…]} — o token fica seguro lá. 2) token direto na Instagram Graph API (bom para testar; o token vence a cada 60 dias). Sem nenhum dos dois, o site usa as publicações escolhidas à mão no painel. */ async function fetchInstagramFeed(sync) { const limit = +sync.limit || 9; let url; if (sync.proxy) { url = sync.proxy + (sync.proxy.includes('?') ? '&' : '?') + 'limit=' + limit; } else { if (!sync.token) throw new Error('Sem token nem endpoint configurado'); const fields = 'id,caption,media_type,media_url,thumbnail_url,permalink,timestamp'; url = `https://graph.instagram.com/${sync.userId || 'me'}/media?fields=${fields}&limit=${limit}&access_token=${encodeURIComponent(sync.token)}`; } const r = await fetch(url, {cache:'no-store'}); const j = await r.json().catch(() => ({})); if (!r.ok) throw new Error(j?.error?.message || ('HTTP ' + r.status)); const raw = j.data || j.feed || []; if (!raw.length) throw new Error('Nenhuma publicação retornada'); return raw.slice(0, limit).map(m => ({ id: 'ig_' + m.id, src: m.media_type === 'VIDEO' ? (m.thumbnail_url || m.media_url) : m.media_url, caption: (m.caption || '').split('\n')[0].slice(0, 220), permalink: m.permalink, date: m.timestamp, type: m.media_type === 'VIDEO' ? 'reel' : m.media_type === 'CAROUSEL_ALBUM' ? 'carousel' : 'photo', likes: m.like_count != null ? String(m.like_count) : '', comments: m.comments_count != null ? String(m.comments_count) : '', tags: ((m.caption || '').match(/#[\wÀ-ÿ]+/g) || []).slice(0, 3), })); } async function syncInstagram(cfg = {}, {silent = true} = {}) { try { const feed = await fetchInstagramFeed(cfg); setStore(s => ({instagram: {...s.instagram, feed, sync: {...s.instagram.sync, lastSync: new Date().toISOString(), lastError: ''}}})); return {ok: true, count: feed.length}; } catch (e) { setStore(s => ({instagram: {...s.instagram, sync: {...s.instagram.sync, lastError: String(e.message || e), lastSync: s.instagram.sync.lastSync}}})); if (!silent) console.warn('Instagram sync', e); return {ok: false, error: String(e.message || e)}; } } function InstagramSection({showToast}) { const store = useStore(); const ig = store.instagram; const [lb, setLb] = igUseState(null); const sync = ig?.sync || {}; // atualiza sozinho: ao abrir o site e a cada "everyMin" minutos igUseEffect(() => { if (!sync.enabled || (!sync.token && !sync.proxy)) return; const every = Math.max(5, +sync.everyMin || 60) * 60000; const age = sync.lastSync ? Date.now() - new Date(sync.lastSync).getTime() : Infinity; if (age > every) syncInstagram(sync); const t = setInterval(() => syncInstagram(sync), every); return () => clearInterval(t); }, [sync.enabled, sync.token, sync.proxy, sync.everyMin]); if (!ig) return null; const url = ig.url || `https://instagram.com/${(ig.handle||'').replace('@','')}`; return (
{ig.handle}/
{ig.handle} Seguir
{ig.posts} publicações
{ig.followers} seguidores
{ig.following} seguindo

{ig.bio}

{(ig.feed||[]).map((post, i) => ( ))}
Ver perfil completo no Instagram
{lb && (
setLb(null)}>
e.stopPropagation()}>
{lb.caption}/
{ig.handle}
Parintins · Amazonas

{ig.handle} {lb.caption}

{(lb.tags||['#angellabarrosstudio','#parintins']).map(t => {t})}
{lb.likes ? `${lb.likes} curtidas` : (lb.date ? new Date(lb.date).toLocaleDateString('pt-BR', {day:'2-digit', month:'long'}) : '')}
Abrir no Instagram
)}
); } /* Admin tab for the Instagram integration */ function InstagramAdmin({store, showToast}) { const ig = store.instagram; const fileRef = React.useRef(null); const avaRef = React.useRef(null); const upd = (patch) => setStore(s => ({instagram: {...s.instagram, ...patch}})); const addPosts = async (files) => { const list = Array.from(files).filter(f => f.type.startsWith('image/')); if (!list.length) return; const items = []; for (const f of list) { const src = await fileToDataURL(f, 1100); items.push({id: uid('ig'), src, caption: f.name.replace(/\.[^.]+$/,''), likes: 0, comments: 0, type:'photo'}); } upd({feed: [...items, ...(ig.feed||[])]}); showToast(`${items.length} publicação${items.length>1?'ões':''} adicionada${items.length>1?'s':''}`); }; return ( <> addPosts(e.target.files)}/>
Perfil conectado

As informações abaixo alimentam a seção Instagram do site.

ativo
upd({handle:e.target.value})} placeholder="@seuperfil"/> upd({url:e.target.value})} placeholder="https://instagram.com/…"/> upd({posts:e.target.value})}/> upd({followers:e.target.value})}/> upd({following:e.target.value})}/>