- feat(025): favoritos locais com FavoritesContext, HeartButton, PublicFavoritesPage
- feat(026): central de contatos admin (leads/contatos unificados)
- feat(027): configuração da página de contato via admin
- feat(028): trabalhe conosco - candidaturas com upload e admin
- feat(029): UX área do cliente - visitas, comparação, perfil
- feat(030): navbar UX - menu mobile, ThemeToggle, useFavorites
- feat(031): hero light/dark - imagens separadas por tema, upload, preview, seed
- feat(032): performance homepage - Promise.all parallel fetches, sessionStorage cache,
preload hero image, loading=lazy nos cards, useInView hook, will-change carrossel,
keyframes em index.css, AgentsCarousel e HomeScrollScene via props
- fix: light mode HomeScrollScene - gradiente, cores de texto, scroll hint
migrations: g1h2i3j4k5l6 (source em leads), h1i2j3k4l5m6 (contact_config),
i1j2k3l4m5n6 (job_applications), j2k3l4m5n6o7 (hero theme images)
8 KiB
8 KiB
Tasks — 032: Performance Homepage
Ordem de execução respeita dependências. Cada task é atômica e pode ser validada individualmente.
FASE 1 — Fundação (sem quebra de interface)
TASK-01: Criar hook useInView
- Arquivo:
frontend/src/hooks/useInView.ts(criar) - Ação:
import { useEffect, useRef, useState } from 'react' export function useInView(options?: IntersectionObserverInit) { const ref = useRef<HTMLDivElement>(null) const [inView, setInView] = useState(false) useEffect(() => { const el = ref.current if (!el) return const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { setInView(true) observer.disconnect() } }, options) observer.observe(el) return () => observer.disconnect() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return { ref, inView } } - Validação:
get_errorssem erros
TASK-02: Mover @keyframes fadeDown para index.css
- Arquivo:
frontend/src/index.css(editar — adicionar ao final) - Conteúdo a adicionar:
@keyframes fadeDown { 0%, 100% { opacity: 0; transform: translateY(-4px); } 50% { opacity: 1; transform: translateY(4px); } } - Arquivo:
frontend/src/components/HomeScrollScene.tsx(editar — remover o bloco<style>inline) - Validação: Build passa, setas do scroll hint continuam animadas
TASK-03: loading="lazy" + decoding="async" em SlideImage
- Arquivo:
frontend/src/components/PropertyRowCard.tsx - Alvo: função
SlideImage, tag<img> - Adicionar atributos:
loading="lazy"edecoding="async" - Cuidado: Não adicionar
loading="lazy"na imagem hero deHomeScrollScene(ela deve ser eager) - Validação:
get_errorssem erros
TASK-04: will-change: transform no track do AgentsCarousel
- Arquivo:
frontend/src/components/AgentsCarousel.tsx - Alvo: elemento
divcomref={trackRef}que recebetransformno estilo inline - Ação: Adicionar
willChange: 'transform'no objeto de style do track - Validação:
get_errorssem erros
FASE 2 — Refatoração de AgentsCarousel para receber props
TASK-05: Adicionar props de dados em AgentsCarousel
- Arquivo:
frontend/src/components/AgentsCarousel.tsx - Ação:
- Definir
interface AgentsCarouselProps { agents: Agent[]; loading: boolean } - Receber
{ agents, loading }como props do componente - Remover
useEffectque chamagetAgents()e os estadosagents/loading - Remover import de
getAgents - Manter toda a lógica de carrossel (autoplay, prev/next, etc.) inalterada
- Definir
- Validação:
get_errorssem erros (vai reportar erro de prop emHomePage.tsx— resolver na TASK-08)
FASE 3 — Refatoração de HomeScrollScene para receber props
TASK-06: Adicionar props de dados em HomeScrollScene
- Arquivo:
frontend/src/components/HomeScrollScene.tsx - Ação:
- Adicionar ao
HomeScrollSceneProps:properties: Property[]eloadingProperties: boolean - Remover
const [properties, setProperties] = useState<Property[]>([]) - Remover
const [loading, setLoading] = useState(true) - Remover
useEffect(() => { getFeaturedProperties()... }) - Remover import de
getFeaturedProperties - No JSX, substituir uso de
loadingporloadingProperties
- Adicionar ao
- Validação:
get_errors(vai ter erro em uso — resolver na TASK-08)
TASK-07: Refatorar RiseCard para usar useInView
- Arquivo:
frontend/src/components/HomeScrollScene.tsx - Ação:
- Importar
useInViewde'../hooks/useInView' - Substituir o corpo de
RiseCard: removeruseRef,useState(false),useEffectcomIntersectionObserver - Usar
const { ref, inView } = useInView({ threshold: 0.05 }) - Manter a div com
ref={ref}e classes condicionais eminView
- Importar
- Validação:
get_errorssem erros
FASE 4 — HomePage como orchestrator
TASK-08: Paralelizar fetches + cache + preload em HomePage
- Arquivo:
frontend/src/pages/HomePage.tsx - Ação:
- Adicionar imports:
getFeaturedPropertiesde'../services/properties',getAgentsde'../services/agents',Agentde'../types/agent',Propertyde'../types/property' - Adicionar estados:
featuredProperties: Property[],agents: Agent[],loadingProperties: boolean,loadingAgents: boolean - Criar helpers de cache:
const CFG_CACHE_KEY = 'homepage_config_v1' const CFG_CACHE_TTL = 5 * 60 * 1000 function getCachedConfig(): HomepageConfig | null { ... } function setCachedConfig(data: HomepageConfig): void { ... } - Substituir
useEffectdegetHomepageConfigporPromise.all:useEffect(() => { const cached = getCachedConfig() const configFetch = cached ? Promise.resolve(cached) : getHomepageConfig().then(d => { setCachedConfig(d); return d }) Promise.all([configFetch, getFeaturedProperties(), getAgents()]) .then(([cfg, props, agts]) => { setConfig(cfg) setFeaturedProperties(props) setAgents(agts) }) .catch(() => {}) .finally(() => { setIsLoading(false) setLoadingProperties(false) setLoadingAgents(false) }) }, []) - Adicionar
useEffectde preload:useEffect(() => { if (!themedBackgroundImage) return const link = document.createElement('link') link.rel = 'preload' link.as = 'image' link.href = themedBackgroundImage document.head.appendChild(link) return () => { document.head.removeChild(link) } }, [themedBackgroundImage]) - Passar
properties={featuredProperties}eloadingProperties={loadingProperties}para<HomeScrollScene> - Passar
agents={agents}eloading={loadingAgents}para<AgentsCarousel>
- Adicionar imports:
- Validação:
get_errorssem erros;npm run buildpassa
TASK-09: fetchPriority="high" + loading="eager" na hero image
- Arquivo:
frontend/src/components/HomeScrollScene.tsx - Alvo: tag
<img>dobackgroundImage(dentro do bloco{backgroundImage ? () - Adicionar:
fetchPriority="high"eloading="eager"edecoding="async" - Nota:
fetchPriorityé atributo HTML5 — TypeScript pode reclamar; usar{...{ fetchpriority: 'high' } as React.ImgHTMLAttributes<HTMLImageElement>}se necessário, ou verificar suporte em@types/react - Validação:
get_errorssem erros
FASE 5 — Validação Final
TASK-10: Build e checklist final
- Ação:
- Executar
npm run buildno diretóriofrontend/ - Verificar zero erros TypeScript
- Testar manualmente:
- Home carrega no tema dark sem erro visual
- Home carrega no tema light sem erro visual
- Cards de destaque aparecem com animação rise
- Carousel de corretores funciona (autoplay, prev/next)
- Troca de tema reage corretamente no gradiente hero
- DevTools Network: 3 requests paralelos no load inicial
- Segunda visita (< 5 min):
/homepage-confignão é chamado
- Executar
Checklist de Qualidade
- TASK-01 concluída —
hooks/useInView.tscriado - TASK-02 concluída —
@keyframesemindex.css, sem<style>inline - TASK-03 concluída —
loading="lazy"emSlideImage - TASK-04 concluída —
will-change: transformno carrossel - TASK-05 concluída —
AgentsCarouselrecebe props - TASK-06 concluída —
HomeScrollScenerecebe props - TASK-07 concluída —
RiseCardusauseInView - TASK-08 concluída —
HomePageparaleliza fetches + cache + preload - TASK-09 concluída — hero image com prioridade correta
- TASK-10 concluída — build verde, smoke test manual OK