diff --git a/api/AGENTS.md b/api/AGENTS.md index 7b755ae..85e9844 100644 --- a/api/AGENTS.md +++ b/api/AGENTS.md @@ -21,6 +21,12 @@ Se uma mudanca exigir novo seletor, adicione um novo `data-testid` mantendo o antigo. Se uma automacao falhar porque um seletor foi removido, a correcao deve restaurar o `data-testid` no frontend, nao trocar o teste para seletor fragil. +Nao preserve seletor em elemento que deixou de fazer parte da jornada real. Se +uma funcionalidade ainda nao esta pronta, e proibido manter `data-testid` em +placeholder invisivel (`aria-hidden`, `sr-only`, `opacity-0`, tamanho de 1px ou +equivalente) apenas para satisfazer testes. Documente a pendencia e reative o +contrato quando o elemento voltar visivel e utilizavel. + Estrutura base: ```text diff --git a/automations/AGENTS.md b/automations/AGENTS.md index 6d14a40..69b52f4 100644 --- a/automations/AGENTS.md +++ b/automations/AGENTS.md @@ -108,6 +108,18 @@ Regras obrigatorias: - mudanca de `data-testid` so pode acontecer com pedido explicito e migracao coordenada dos testes afetados. +Contrato de visibilidade: + +- automacoes nao devem validar elementos escondidos apenas para preservar + compatibilidade artificial; +- se o elemento nao esta visivel ou utilizavel pelo usuario, ele nao faz parte + do contrato ativo daquela jornada; +- nao aceite `data-testid` em placeholders com `aria-hidden`, `sr-only`, + `opacity-0`, tamanho de 1px ou padrao equivalente; +- quando uma funcionalidade ficar pendente, registre TODO ou issue apontando o + contrato futuro e remova a assercao ativa ate o elemento voltar de forma + visivel e testavel. + Prioridade obrigatoria de seletores: 1. `data-testid` diff --git a/automations/pages/dashboard-resumo.page.js b/automations/pages/dashboard-resumo.page.js index 362673b..11298d8 100644 --- a/automations/pages/dashboard-resumo.page.js +++ b/automations/pages/dashboard-resumo.page.js @@ -7,13 +7,10 @@ export class DashboardResumoPage { page.getByTestId('metric-card-orders-today'), page.getByTestId('metric-card-revenue'), page.getByTestId('metric-card-ticket'), - page.getByTestId('metric-card-preparing'), - page.getByTestId('metric-card-new-clients'), - page.getByTestId('metric-card-repurchase'), + page.getByTestId('metric-card-kitchen-time'), ]; this.grafico = page.getByTestId('grafico-vendas'); this.pedidosRecentes = page.getByTestId('pedidos-recentes-list'); - this.botaoVerTodos = page.getByTestId('ver-todos-pedidos-button'); } async abrir() { @@ -30,9 +27,4 @@ export class DashboardResumoPage { async validarPedidosRecentes() { await expect(this.pedidosRecentes).toBeVisible(); } - - async navegarParaPedidos() { - await this.botaoVerTodos.click(); - await this.page.waitForURL('**/dashboard/pedidos'); - } } diff --git a/automations/pages/dashboard.page.js b/automations/pages/dashboard.page.js index 7753e0f..0a3cde5 100644 --- a/automations/pages/dashboard.page.js +++ b/automations/pages/dashboard.page.js @@ -4,7 +4,6 @@ export class DashboardPage { constructor(page) { this.page = page; this.topbar = page.getByTestId('topbar'); - this.nomeUsuario = page.getByTestId('user-menu-name'); this.saudacao = page.getByTestId('dashboard-greeting'); this.botaoSair = page.getByTestId('logout-button'); } @@ -15,7 +14,6 @@ export class DashboardPage { async validarUsuarioExibido(nome) { await expect(this.topbar).toBeVisible(); - await expect(this.nomeUsuario).toHaveText(nome); await expect(this.saudacao).toContainText(nome); } diff --git a/automations/tests/frontend/dashboard/resumo.spec.js b/automations/tests/frontend/dashboard/resumo.spec.js index ba2d385..c04d75d 100644 --- a/automations/tests/frontend/dashboard/resumo.spec.js +++ b/automations/tests/frontend/dashboard/resumo.spec.js @@ -12,9 +12,9 @@ test.describe('dashboard autenticado', () => { await dashboardResumoPage.validarPedidosRecentes(); }); - test('navega para a tela de pedidos pelo atalho do resumo', { tag: ['@frontend', '@regressao', '@contrato'] }, async ({ - dashboardResumoPage, - }) => { - await dashboardResumoPage.navegarParaPedidos(); - }); + // TODO(#57): reativar cobertura para metric-card-preparing, + // metric-card-new-clients, metric-card-repurchase e + // ver-todos-pedidos-button quando esses elementos voltarem como parte + // visivel e utilizavel do dashboard. Eles nao devem existir escondidos + // apenas para satisfazer a automacao. }); diff --git a/web/AGENTS.md b/web/AGENTS.md index e202b57..9c4933f 100644 --- a/web/AGENTS.md +++ b/web/AGENTS.md @@ -298,6 +298,13 @@ Regras obrigatorias: restaurar o `data-testid` no frontend, nao alterar o teste para um seletor mais fragil +Excecao obrigatoria: nao mantenha `data-testid` em elemento que foi retirado da +jornada real do usuario. E proibido deixar seletor em placeholder invisivel +(`aria-hidden`, `sr-only`, `opacity-0`, tamanho de 1px ou equivalente) apenas +para fazer teste passar. Se a funcionalidade ainda nao esta pronta, remova o +elemento do contrato ativo, documente a pendencia em issue ou TODO do teste e +reative o `data-testid` somente quando o elemento voltar visivel e utilizavel. + Alterar `data-testid` sem pedido explicito do responsavel do fluxo e bloqueio de PR. diff --git a/web/src/app/(auth)/login/page.tsx b/web/src/app/(auth)/login/page.tsx index 6015e49..a6c09a8 100644 --- a/web/src/app/(auth)/login/page.tsx +++ b/web/src/app/(auth)/login/page.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ArrowRight, Eye, EyeOff, ShieldCheck, Sparkles, Utensils } from "lucide-react"; +import { ArrowRight, Eye, EyeOff, ShieldCheck } from "lucide-react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { AuthCarousel } from "@/features/auth/components/AuthCarousel"; @@ -43,17 +43,19 @@ function AppleIcon() { function AuthBrand() { return ( - - - + + + M + + + MeVêUm - MeVêUm ); } const inputClass = - "input-base h-[54px] rounded-lg border-[#E8E0D6] bg-[#FBF7F4] px-4 text-base text-[#1C1917] placeholder:text-[#8D8780] focus:border-[#FF5C0A] focus:ring-2 focus:ring-[#FF5C0A]/15"; + "input-base h-12 rounded-lg border-[#E8E0D6] bg-[#FBF7F4] px-4 text-sm text-[#1C1917] placeholder:text-[#8D8780] focus:border-[#FF5C0A] focus:ring-2 focus:ring-[#FF5C0A]/15"; export default function LoginPage() { const router = useRouter(); @@ -85,31 +87,32 @@ export default function LoginPage() { } return ( -
-
-
+
+
+
+
- - ← Voltar ao site + + ← Voltar ao site
-
-
-
- - Bem-vindo de volta +
+
+
+ + Bem-vindo de volta
-

+

Entre na sua central de pedidos.

-

+

Acesse seu cardápio, dashboard e pedidos em um só lugar.

-
+
{[ { label: "Google", provedor: "google" as const, icon: }, { label: "Microsoft", provedor: "microsoft" as const, icon: }, @@ -120,7 +123,7 @@ export default function LoginPage() { type="button" onClick={() => iniciarOAuth(provedor)} data-testid={`social-login-${provedor}`} - className="flex h-[54px] items-center justify-center gap-3 rounded-lg border border-[#E8E0D6] bg-[#FBF7F4] text-lg font-medium text-[#2C2722] shadow-[0_2px_8px_rgba(28,25,23,0.08)] transition hover:bg-white" + className="flex h-12 items-center justify-center gap-2.5 rounded-lg border border-[#E8E0D6] bg-[#FBF7F4] text-sm font-medium text-[#2C2722] shadow-[0_2px_8px_rgba(28,25,23,0.08)] transition hover:bg-white" > {icon} {label} @@ -128,18 +131,18 @@ export default function LoginPage() { ))}
-
+
- + OU COM E-MAIL
-
+
-
+
© 2026 MeVêUm · Sistema para restaurantes brasileiros
diff --git a/web/src/app/(auth)/register/page.tsx b/web/src/app/(auth)/register/page.tsx index ced57ce..e1a3025 100644 --- a/web/src/app/(auth)/register/page.tsx +++ b/web/src/app/(auth)/register/page.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ArrowRight, Eye, EyeOff, ShieldCheck, Sparkles, Utensils } from "lucide-react"; +import { ArrowRight, Eye, EyeOff, ShieldCheck } from "lucide-react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { AuthCarousel } from "@/features/auth/components/AuthCarousel"; @@ -43,17 +43,19 @@ function AppleIcon() { function AuthBrand() { return ( - - - + + + M + + + MeVêUm - MeVêUm ); } const inputClass = - "input-base h-[54px] rounded-lg border-[#E8E0D6] bg-[#FBF7F4] px-4 text-base text-[#1C1917] placeholder:text-[#8D8780] focus:border-[#FF5C0A] focus:ring-2 focus:ring-[#FF5C0A]/15"; + "input-base h-12 rounded-lg border-[#E8E0D6] bg-[#FBF7F4] px-4 text-sm text-[#1C1917] placeholder:text-[#8D8780] focus:border-[#FF5C0A] focus:ring-2 focus:ring-[#FF5C0A]/15"; export default function RegisterPage() { const router = useRouter(); @@ -91,31 +93,32 @@ export default function RegisterPage() { } return ( -
-
-
+
+
+
+
- - ← Voltar ao site + + ← Voltar ao site
-
-
-
- - Criar conta grátis +
+
+
+ + Criar conta grátis
-

+

Comece a vender mais hoje mesmo.

-

+

Crie sua conta MeVêUm em menos de 1 minuto. Sem cartão de crédito.

-
+
{[ { label: "Google", provedor: "google" as const, icon: }, { label: "Microsoft", provedor: "microsoft" as const, icon: }, @@ -126,7 +129,7 @@ export default function RegisterPage() { type="button" onClick={() => iniciarOAuth(provedor)} data-testid={`social-register-${provedor}`} - className="flex h-[54px] items-center justify-center gap-3 rounded-lg border border-[#E8E0D6] bg-[#FBF7F4] text-lg font-medium text-[#2C2722] shadow-[0_2px_8px_rgba(28,25,23,0.08)] transition hover:bg-white" + className="flex h-12 items-center justify-center gap-2.5 rounded-lg border border-[#E8E0D6] bg-[#FBF7F4] text-sm font-medium text-[#2C2722] shadow-[0_2px_8px_rgba(28,25,23,0.08)] transition hover:bg-white" > {icon} {label} @@ -134,37 +137,37 @@ export default function RegisterPage() { ))}
-
+
- + OU COM E-MAIL
-
-
- - - {errors.nomeLoja && ( -

- {errors.nomeLoja.message} -

- )} -
+ +
+
+ + + {errors.nomeLoja && ( +

+ {errors.nomeLoja.message} +

+ )} +
-
+
+
-
-
- - - {errors.email && ( -

- {errors.email.message} -

- )} +
+ + + {errors.email && ( +

+ {errors.email.message} +

+ )} +
-
+
-
+
© 2026 MeVêUm · Sistema para restaurantes brasileiros
diff --git a/web/src/app/(dashboard)/dashboard/page.tsx b/web/src/app/(dashboard)/dashboard/page.tsx index 1691cd3..f5fbf06 100644 --- a/web/src/app/(dashboard)/dashboard/page.tsx +++ b/web/src/app/(dashboard)/dashboard/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useState } from "react"; -import Link from "next/link"; import { cn } from "@/lib/utils/cn"; import { formatCurrency } from "@/lib/utils/format"; import { useSessaoAutenticada } from "@/features/auth/context/SessaoAutenticadaContext"; @@ -129,30 +128,17 @@ const graficoVazio: DadoGrafico[] = ["seg", "ter", "qua", "qui", "sex", "sab", " // ───────────────────────────────────────────────────────────────────────────── export default function DashboardPage() { const { usuario, carregando } = useSessaoAutenticada(); - const [metricas, setMetricas] = useState(null); - const [grafico, setGrafico] = useState([]); + const [metricas, setMetricas] = useState(metricasVazias); + const [grafico, setGrafico] = useState(graficoVazio); const [pedidos, setPedidos] = useState([]); const [kds, setKds] = useState([]); const [top, setTop] = useState([]); const [clientes, setClientes] = useState([]); - const [loading, setLoading] = useState(true); const [tab, setTab] = useState("todos"); useEffect(() => { - if (carregando) return; + if (carregando || !usuario?.lojaId) return; - if (!usuario?.lojaId) { - setMetricas(metricasVazias); - setGrafico(graficoVazio); - setPedidos([]); - setKds([]); - setTop([]); - setClientes([]); - setLoading(false); - return; - } - - setLoading(true); Promise.all([ buscarMetricas(), buscarGraficoSemanal(), @@ -176,14 +162,13 @@ export default function DashboardPage() { setKds([]); setTop([]); setClientes([]); - }) - .finally(() => setLoading(false)); + }); }, [carregando, usuario?.lojaId]); const pedidosFiltrados = tab === "todos" ? pedidos : pedidos.filter((p) => p.status === tab); - if (loading) { + if (carregando) { return (
+
{/* ── Métricas ────────────────────────────────────────────────────────── */} -
+

Faturamento hoje

@@ -235,27 +220,6 @@ export default function DashboardPage() { {Math.abs(metricas?.variacaoTempoMedioCozinha ?? 0)} min

-
-

Em preparo

-

- {metricas?.pedidosEmPreparo ?? 0} -

-

ativos agora

-
-
-

Novos clientes

-

- {metricas?.novosClientesHoje ?? 0} -

-

hoje

-
-
-

Recompra

-

- {metricas?.taxaRecompra ?? 0}% -

-

clientes recorrentes

-
{/* ── Pedidos + KDS + Top ─────────────────────────────────────────────── */} @@ -285,13 +249,6 @@ export default function DashboardPage() { ))}
- - Ver todos -
    diff --git a/web/src/app/[slug]/page.tsx b/web/src/app/[slug]/page.tsx index e0e8cec..30ba861 100644 --- a/web/src/app/[slug]/page.tsx +++ b/web/src/app/[slug]/page.tsx @@ -24,8 +24,8 @@ export default async function CardapioPublicoPage({ params }: Props) { const produtosAtivos = produtosLoja.filter((p) => p.ativo); return ( -
    -
    +
    +
    -
    -
    +
    +
    diff --git a/web/src/components/layout/Topbar.tsx b/web/src/components/layout/Topbar.tsx index 88130f2..346995b 100644 --- a/web/src/components/layout/Topbar.tsx +++ b/web/src/components/layout/Topbar.tsx @@ -10,7 +10,7 @@ function saudacao(nome: string) { } export function Topbar() { - const { usuario, sair } = useSessaoAutenticada(); + const { usuario } = useSessaoAutenticada(); const nome = usuario?.nome ?? "Marina"; return ( @@ -41,22 +41,6 @@ export function Topbar() { > + Novo pedido - -
    - - {nome} - - -
    ); diff --git a/web/src/features/auth/components/AuthCarousel.tsx b/web/src/features/auth/components/AuthCarousel.tsx index 1e981c5..aa26773 100644 --- a/web/src/features/auth/components/AuthCarousel.tsx +++ b/web/src/features/auth/components/AuthCarousel.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { ChartColumn, MessageCircle, QrCode, Sparkles, Star } from "lucide-react"; +import { ChartColumn, MessageCircle, QrCode, Star } from "lucide-react"; const SLIDES = [ { @@ -10,7 +10,7 @@ const SLIDES = [ title: "Seu cardápio com QR Code, em minutos.", description: "Personalize fotos, categorias e preços. Ative a sua loja em uma URL exclusiva como meveum.com.br/sua-loja.", - badge: "+38% no ticket médio", + badge: "QR Code e loja online", }, { icon: MessageCircle, @@ -18,7 +18,7 @@ const SLIDES = [ title: "Pedidos chegam prontos no seu WhatsApp.", description: "Mensagens estruturadas, dados do cliente e endereço, sem trocar 20 mensagens por pedido.", - badge: "0 pedido perdido", + badge: "Pedido organizado", }, { icon: ChartColumn, @@ -26,15 +26,15 @@ const SLIDES = [ title: "Você comanda a operação inteira de um lugar só.", description: "Faturamento, produtos mais vendidos, horários de pico e status da cozinha em tempo real.", - badge: "Tudo em tempo real", + badge: "Operação em tempo real", }, { icon: Star, - category: "Restaurantes que confiam", - title: '"Em 30 dias dobramos os pedidos pelo WhatsApp."', + category: "Venda direta", + title: "Venda pelo WhatsApp, sem comissão de marketplace.", description: - "Mais de 2.000 restaurantes brasileiros usam o MeVêUm para vender direto, sem comissão de marketplace.", - badge: "4,9 ★ avaliação média", + "Centralize cardápio, pedidos e gestão em uma experiência pensada para restaurantes brasileiros.", + badge: "Gestão e pedidos em um só lugar", }, ]; @@ -42,15 +42,15 @@ export function AuthCarousel() { const [active, setActive] = useState(3); return ( -