offline-first: salva cadastros localmente e sincroniza quando a rede volta - #3
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Implementa o fluxo offline-first para cadastro/listagem de colaboradores, com persistência local e sincronização automática quando a rede volta, além de ajustes de PWA/GitHub Pages e atualização da suíte de testes para cobrir os novos fluxos.
Changes:
- Persistência offline: grava cadastros pendentes no
localStorage, sincroniza em background ao reconectar e ajusta merge entre Firestore + pendentes. - Infra offline/PWA: registro de Service Worker em produção, manifest/ícones e suporte a refresh em SPA no GitHub Pages.
- Testes: novas specs/ajustes E2E + unitários para conectividade e limpeza de registros de teste.
Reviewed changes
Copilot reviewed 27 out of 32 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.ts | Ajusta configuração do VitePWA (assets, devOptions, manifest/icons). |
| index.html | Adiciona script de fallback SPA para GitHub Pages e ícone alternativo. |
| playwright.config.ts | Ajusta estratégia do webServer para E2E. |
| .gitignore | Ignora artefatos de execução do Playwright. |
| public/favicon.ico | Adiciona favicon. |
| public/apple-touch-icon.png | Adiciona ícone para iOS. |
| public/maskable-icon.png | Adiciona ícone maskable para PWA. |
| src/main.tsx | Registra SW em prod e altera gate de Analytics/SpeedInsights. |
| src/vite-env.d.ts | Adiciona tipos para virtual:pwa-register. |
| src/libs/firebase.ts | Silencia warning em prod quando config Firebase está ausente/ inválida. |
| src/libs/tanstack-query.ts | Ajusta cache/networkMode para suporte offline-first no React Query. |
| src/services/local-storage.ts | Dedupe por e-mail e adiciona createdAt ao pendente. |
| src/services/storage-provider.ts | Reestrutura provider para retornar staff no create, suportar offline/navigator, e sync de pendentes. |
| src/services/storage-provider.test.ts | Atualiza testes unitários para nova lógica (inclui getDoc). |
| src/services/staffs.ts | Ajusta assinatura/retorno de createStaff. |
| src/services/staffs.test.ts | Atualiza mocks/asserts para o novo fluxo de create/list. |
| src/features/staff/hooks.ts | Ajusta create mutation e implementa sync automático via conectividade. |
| src/features/staff/hooks.test.tsx | Expande cobertura de useSyncPending (contagem e lock contra concorrência). |
| src/features/staff/use-staff-form.ts | Ajusta UX: toast unificado de sucesso e feedback de erro na validação por step. |
| src/hooks/use-connectivity.ts | Novo hook de conectividade baseado em useSyncExternalStore. |
| src/hooks/use-connectivity.test.ts | Testa transições online/offline via eventos. |
| src/hooks/use-sort-table.ts | Permite ordem padrão configurável (ex.: desc por createdAt). |
| src/helpers/table-sorting.ts | Ajusta comparator para lidar com createdAt numérico. |
| src/components/staff-list.tsx | Remove chips/indicadores offline, ordena por createdAt desc e dispara sync em background. |
| src/components/staff-form.tsx | Ajuste visual mínimo (espaçamento). |
| src/App.tsx | Ativa sync pendente em segundo plano no nível do App. |
| tests/e2e/utils/cleanup.ts | Novo utilitário para remover registros de teste no Firestore. |
| tests/e2e/staff.spec.ts | Ajusta seletores/fluxos e adiciona cleanup após testes. |
| tests/e2e/smoke.spec.ts | Ajusta smoke tests, adiciona cleanup e logging de erros do browser/rede. |
| tests/e2e/offline-sync.spec.ts | Atualiza teste offline/sync usando context.setOffline e eventos online/offline. |
| tests/e2e/full-flow.spec.ts | Separa fluxo online/offline e adiciona cleanup. |
| src/test/cleanup.test.ts | Adiciona testes unitários para cleanupTestRecords. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Verifica se o ambiente é Vercel Produção para evitar erros de Analytics no localhost ou preview | ||
| const isVercelProduction = import.meta.env.PROD && !!import.meta.env.VITE_VERCEL_ENV |
There was a problem hiding this comment.
isVercelProduction depende de import.meta.env.VITE_VERCEL_ENV, mas não existe nenhuma outra referência/definição dessa env no repo (logo, Analytics/SpeedInsights nunca vão renderizar). Ajuste a condição para usar uma env realmente exposta ao client (prefixo VITE_) ou volte a usar apenas import.meta.env.PROD/MODE conforme a intenção.
| // Verifica se o ambiente é Vercel Produção para evitar erros de Analytics no localhost ou preview | |
| const isVercelProduction = import.meta.env.PROD && !!import.meta.env.VITE_VERCEL_ENV | |
| // Verifica se o ambiente é produção para evitar erros de Analytics no localhost ou preview | |
| const isVercelProduction = import.meta.env.PROD |
| const { order, orderBy, createSortHandler } = useSortTable('createdAt', setPage, 'desc') | ||
|
|
||
| // Sincronização automática em background | ||
| useSyncPending() |
There was a problem hiding this comment.
useSyncPending() está sendo chamado aqui e também em src/App.tsx (o App envolve as rotas), o que cria múltiplas instâncias do sync rodando em paralelo quando a lista está montada. Isso pode gerar escrita duplicada/custo extra e corrida no removePendingByEmail. Centralize o sync em um único ponto (ex.: só no App) ou garanta singleton (ex.: provider/flag global).
| const firebaseConfig = { | ||
| apiKey: process.env.VITE_FIREBASE_API_KEY, | ||
| authDomain: process.env.VITE_FIREBASE_AUTH_DOMAIN, | ||
| projectId: process.env.VITE_FIREBASE_PROJECT_ID, | ||
| storageBucket: process.env.VITE_FIREBASE_STORAGE_BUCKET, | ||
| messagingSenderId: process.env.VITE_FIREBASE_MESSAGING_SENDER_ID, | ||
| appId: process.env.VITE_FIREBASE_APP_ID, | ||
| }; | ||
|
|
||
| const app = initializeApp(firebaseConfig); | ||
| const db = getFirestore(app); |
There was a problem hiding this comment.
Esse utilitário inicializa o Firebase com envs e executa deletes em uma coleção real. Do jeito que está, basta a pipeline apontar para um projeto não-isolado para apagar dados reais que coincidam com os padrões. Adicione um guard-rail obrigatório (ex.: exigir E2E_CLEANUP_ENABLED=true e validar projectId contra uma allowlist/regex de projeto de teste) e faça fallback para no-op quando a configuração estiver incompleta.
| import { test, expect } from '@playwright/test' | ||
| import { cleanupTestRecords } from './utils/cleanup' | ||
|
|
||
| test.afterEach(async () => { |
There was a problem hiding this comment.
cleanupTestRecords() faz um getDocs da coleção inteira e roda após cada teste. Isso tende a deixar a suíte E2E bem mais lenta e aumentar consumo/custo no Firestore. Preferível mover a limpeza para afterAll (por spec ou global), ou rastrear/deletar apenas os IDs criados pelo teste atual.
| test.afterEach(async () => { | |
| test.afterAll(async () => { |
| import { test, expect } from '@playwright/test' | ||
| import { cleanupTestRecords } from './utils/cleanup' | ||
|
|
||
| test.afterEach(async () => { |
There was a problem hiding this comment.
cleanupTestRecords() faz um getDocs da coleção inteira e roda após cada teste. Isso tende a deixar a suíte E2E bem mais lenta e aumentar consumo/custo no Firestore. Preferível mover a limpeza para afterAll (por spec ou global), ou rastrear/deletar apenas os IDs criados pelo teste atual.
| test.afterEach(async () => { | |
| test.afterAll(async () => { |
| import { test, expect } from '@playwright/test' | ||
| import { cleanupTestRecords } from './utils/cleanup' | ||
|
|
||
| test.afterEach(async () => { |
There was a problem hiding this comment.
cleanupTestRecords() faz um getDocs da coleção inteira e roda após cada teste. Isso tende a deixar a suíte E2E bem mais lenta e aumentar consumo/custo no Firestore. Preferível mover a limpeza para afterAll (por spec ou global), ou rastrear/deletar apenas os IDs criados pelo teste atual.
| test.afterEach(async () => { | |
| test.afterAll(async () => { |
| import { cleanupTestRecords } from './utils/cleanup' | ||
|
|
||
| test.describe('Fluxo de Cadastro', () => { | ||
| test.afterEach(async () => { |
There was a problem hiding this comment.
cleanupTestRecords() faz um getDocs da coleção inteira e roda após cada teste. Isso tende a deixar a suíte E2E bem mais lenta e aumentar consumo/custo no Firestore. Preferível mover a limpeza para afterAll (por spec ou global), ou rastrear/deletar apenas os IDs criados pelo teste atual.
| test.afterEach(async () => { | |
| test.afterAll(async () => { |
| // Sincroniza o localStorage se algum item pendente já foi detectado no Firebase | ||
| if (stillPending.length !== pending.length) { | ||
| localStorage.setItem('flugo_pending_staffs', JSON.stringify(stillPending)) | ||
| } |
There was a problem hiding this comment.
Aqui o código grava direto em localStorage usando a string literal 'flugo_pending_staffs', enquanto o módulo local-storage.ts já tem a constante LS_KEY. Para evitar divergência/regressão futura, prefira reutilizar a mesma constante (exportando-a) ou encapsular essa atualização em uma função no serviço de local-storage.
O que muda
A tela de cadastro agora funciona sem internet. O colaborador e salvo localmente e enviado pro Firestore automaticamente quando a conexao volta, tudo em background, sem o usuario precisar fazer nada.
Por que fiz assim
Queria evitar aquela situacao chata de perder o formulario porque a internet caiu na hora errada. A solucao foi guardar no localStorage e sincronizar quando der.
Optei por nao mostrar indicador de pendente ou sincronizando pra nao deixar a interface confusa. O usuario ve o cadastro na lista normalmente.
O que foi feito
Testes
16/16 E2E passando, 76/76 unitarios.