Marketplace local de lojas — HTML, CSS e JavaScript vanilla com backend Supabase. Clientes exploram lojas, montam o carrinho e finalizam o pedido pelo WhatsApp. Não há pagamento in-app.
Site em produção: https://maredevendas.com.br/
Espelho (GitHub Pages): https://kelfys.github.io/mdv-web/
| Papel | O que pode fazer |
|---|---|
| Visitante | Ver feed de lojas e produtos (Para você — anúncios Premium no mix), buscar, adicionar ao carrinho e pedir pelo WhatsApp |
| Cliente | Dashboard em Minha conta (/favoritos): favoritos, produtos curtidos, histórico de pedidos e perfil editável; curtir/comentar produtos; checkout com dados pré-preenchidos |
| Lojista | Painel com produtos, pedidos, anúncios e configurações (após aprovação do admin); logo e banner só em planos pagos (Plus/Premium); catálogo Gratuito = 1 item + 1 foto; anúncios no feed só no plano Premium (2 inclusos/mês; extras R$ 5 com aprovação staff) |
| Moderador | Aprovações (lojas, planos, anúncios e denúncias) e pedidos do bairro atribuído; lojas/produtos somente leitura na região |
| Admin | Métricas globais, gestão de bairros, moderadores por região, lojistas, aprovação de anúncios, cor de alerta do logo (#/admin/conta) e configuração |
- Frontend: ES Modules nativos, lazy-load de páginas, sem bundler
- Roteamento: Hash (
#/rota) em produção e local — único modo confiável no GitHub Pages - Backend: Supabase (Auth, PostgreSQL, Storage, Row Level Security)
- Deploy: GitHub Pages via
peaceiris/actions-gh-pages(branchgh-pages) - Testes: Vitest (
npm test) - Fonte: Inter (Google Fonts)
mdv-web/
├── index.html # Shell HTML — cache bust em app.js/styles.css no deploy
├── strings-editor.html # Editor visual dos textos (js/strings.js)
├── 404.html # Fallback SPA para rotas diretas no GitHub Pages
├── css/styles.css # Estilos globais e componentes
├── js/
│ ├── app.js # Boot, registro de rotas e lazy-load de páginas
│ ├── strings.js # Catálogo de textos da UI + t(), helpers i18n
│ ├── router.js # Roteador SPA (History ou hash)
│ ├── state.js # Estado global (tema, auth, carrinho)
│ ├── config.js # Credenciais Supabase e constantes
│ ├── db.js # Cliente Supabase (CDN ESM)
│ ├── api.js # Camada de acesso a dados (erros em errors.*)
│ ├── feed.js # Algoritmo do feed da home (lojas, produtos e anúncios no mix)
│ ├── plan-renewal.js # Vencimento de plano (30 dias), aviso 72 h e downgrade ao Gratuito
│ ├── logo-accent.js # Cor de alerta do “Maré” no logo (presets; admin em Conta)
│ ├── home-filters-scroll.js # Esconde bairros/categorias da home ao rolar
│ ├── header-scroll.js # Esconde o cabeçalho no mobile ao rolar
│ ├── scroll-to-top.js # Botão flutuante ↑ (topo + atualizar página)
│ ├── payment.js # Formas de pagamento no checkout
│ ├── ui.js # Header (logo Maré de Vendas), carrinho, cards e checkout
│ ├── utils.js # Formatação, escape HTML, validação de idade
│ ├── neighborhood.js # Seleção de bairro no feed e escopo do moderador
│ ├── merchant-nav.js # Menu do painel do lojista
│ ├── staff-nav.js # Menu dos painéis admin e moderador
│ └── pages/ # Uma página por rota
├── supabase/migrations/ # Migrations SQL (001 → 057)
├── tools/ # Scripts de DB (apply-sql, register-migration, db-push)
├── tests/ # Testes unitários (Vitest)
└── .github/workflows/
└── deploy.yml # Pipeline de deploy para GitHub Pages
Todos os rótulos, mensagens, placeholders e templates (WhatsApp, planos, regras) ficam em js/strings.js. O app não usa biblioteca de i18n — apenas o helper t() e o objeto STRINGS.
import { t, deliveryPeriodLabel, orderStatusLabel } from './strings.js'
t('nav.home') // "Início"
t('cart.itemsCount', { count: 3 }) // "Itens (3)"
deliveryPeriodLabel('manha') // "Manhã"- UI (
js/ui.js,js/pages/*,js/plans.js, …):t('secao.chave')no HTML/innerHTML - Erros (
js/api.js,js/utils.js,js/uploads.js): chaves emerrors.* - WhatsApp (
js/whatsapp.js): chaves emwhatsapp.* - Mensagens cruas do Supabase sem mapeamento em
formatAuthError()continuam comoerror.message
strings-editor.html — publicado junto com o site (ex.: https://kelfys.github.io/mdv-web/strings-editor.html).
- Buscar por chave ou valor
- Editar textos (rascunho salvo em
localStorage) - Baixar ou copiar o
strings.jsgerado - Substituir
js/strings.jsno repositório e fazer commit
Testes: tests/strings.test.js (resolve de chaves, placeholders, round-trip do editor).
- Adicione a chave em
js/strings.jsdentro da seção adequada (nav,errors,merchant, …) - Use
t('secao.novaChave')no código — não deixe texto PT solto no JS/HTML - Rode
npm teste atualize o editor se necessário (ele importa o módulo atual)
- Navegador moderno com suporte a ES Modules
- Servidor HTTP local (módulos não funcionam com
file://) - Projeto Supabase configurado
python -m http.server 8080
# ou: npx serve -l 8080Acesse: http://localhost:8080
Projeto: ulpjsxmilumqedkkfuqw — https://ulpjsxmilumqedkkfuqw.supabase.co
npx supabase login
npm run db:link
npm run db:pushSem login CLI: copie .env.example → .env.local, preencha DATABASE_URL (senha do Postgres em Settings → Database) e rode:
npm run db:push:urlEste projeto é SPA vanilla (
js/db.jsvia CDN). Guias do dashboard para Next.js (@supabase/ssr), Prisma e@supabase/servernão se aplicam aqui.
Ou execute cada arquivo em supabase/migrations/ no SQL Editor (ordem numérica).
Authentication → URL Configuration:
| Campo | Valor |
|---|---|
| Site URL (prod) | https://maredevendas.com.br |
| Redirect URLs | https://maredevendas.com.br/#/auth/callback |
https://maredevendas.com.br/ |
|
https://www.maredevendas.com.br/#/auth/callback |
|
https://kelfys.github.io/mdv-web/#/auth/callback |
|
https://kelfys.github.io/mdv-web/ |
|
| Local | http://localhost:8080 |
Aplicar no projeto remoto: npx supabase config push (valores em supabase/config.toml).
Credenciais em js/config.js (chave publishable / anon — pública por design):
export const SUPABASE_URL = 'https://ulpjsxmilumqedkkfuqw.supabase.co'
export const SUPABASE_ANON_KEY = 'sb_publishable_...'Nunca commite service_role nem secret keys.
npm testSenhas e secrets não ficam neste README (repo público).
No seu PC: copie o modelo e preencha localmente:
cp SECRETS.local.example.md SECRETS.local.md
# edite SECRETS.local.md → está no .gitignore| Papel | Email (público) | Senha | Notas |
|---|---|---|---|
| Cliente | cliente@maredevendas.com |
ver SECRETS.local.md |
demo |
| Admin | (conta real no Auth) | só em SECRETS.local.md |
nunca no Git |
| Moderador | moderador@maredevendas.com |
ver SECRETS.local.md |
bairro ativo |
| Lojas fake (seed) | lojasfake@gmail.com |
ver SECRETS.local.md |
N lojas ads/seed |
| Produtos fake (seed) | produtosfake@gmail.com |
ver SECRETS.local.md |
vitrine oculta; produtos no feed |
Login moderador: #/moderador/entrar.
| Conta | Serve para | O visitante vê | Limpeza |
|---|---|---|---|
lojasfake@gmail.com |
Várias lojas fake (ads/demo) | Lojas + produtos no marketplace | Apagar perfil → cascade lojas |
produtosfake@gmail.com |
Produtos só para volume no feed | Só produtos (loja-balde oculta) | Apagar perfil → loja + produtos |
Regra: todo produto tem store_id. A vitrine seed-produtos-fake é só o “balde” técnico — não é loja pública.
Para o marketplace parecer cheio sem misturar com usuários reais:
| Item | Detalhe |
|---|---|
lojasfake@gmail.com (senha em SECRETS.local.md) |
|
| Papel | merchant |
| Constante no código | SEED_MULTI_STORE_OWNER_EMAIL em js/config.js (isSeedMultiStoreOwnerEmail) |
| Regra especial | Este e-mail pode ter várias lojas (exceção admin). Demais lojistas: 1 loja = 1 dono |
| Criar mais demos | #/admin/lojas → Nova loja → dono lojasfake@gmail.com |
| Limpeza | Apagar o perfil no admin (ou SQL) remove as lojas em cascade (owner_id ON DELETE CASCADE) |
- Todas as lojas seed/ads devem ficar com esse único dono (não criar um merchant por loja fake)
- Scripts locais (pasta
scripts/, não versionada — ver.gitignore):node scripts/seed-ads-free-stores.mjs— cria lojas a partir de imagens (já usalojasfake@)node scripts/consolidate-fake-owner.mjs— reatribui lojas fake ao dono úniconode scripts/cleanup-orphans.mjs— audita/limpa lojistas sem loja, lojas/produtos órfãos
Contas demo-gratuito@… / demo-plus@… antigas sem loja foram removidas na limpeza de órfãos; use admin + e-mail real (1 loja) ou lojasfake@ (N lojas demo).
Objetivo: mais cards de produto no feed, sem criar loja fake visível.
No schema todo produto tem store_id. Solução: 1 loja-balde oculta ao público; os produtos dela aparecem no feed.
| Item | Detalhe |
|---|---|
produtosfake@gmail.com (senha em SECRETS.local.md) |
|
| Loja-balde | Vitrine demo (produtos seed) · slug seed-produtos-fake |
| Público | Produtos sim no feed · loja não (sem card de loja, sem #/loja/seed-produtos-fake) |
| Card no feed | Nome + preço + + Carrinho (sem link “ver loja”) |
| WhatsApp por item | Campo products.whatsapp (migration 058). Checkout usa o número do produto, não o da loja-balde |
| Checkout telefone | Visitante digita (validado BR). Cliente logado: sem campo — usa perfil; no chat o lojista vê o WA real |
| Admin | #/admin/produtos → loja seed → WhatsApp do item obrigatório em cada produto |
| Constantes | SEED_PRODUCTS_*, isSeedProductsStore, isPublicMarketplaceStore em js/config.js |
| Limpeza | Excluir produtosfake@gmail.com → loja + produtos em cascade |
# Pasta scripts/ é local (gitignored). Requer DATABASE_URL em .env.local
node scripts/ensure-produtosfake.mjs # dry-run
node scripts/ensure-produtosfake.mjs --apply # cria conta + loja se faltarem| Conta seed | O que o visitante vê |
|---|---|
lojasfake@ |
Lojas (e produtos delas) no marketplace |
produtosfake@ |
Só produtos no feed; vitrine escondida |
- Login unificado em
/conta/entrar(e alias/lojista/entrar): mesma tela para cliente, lojista, admin e moderador - Após login, redirecionamento automático por papel:
/favoritos,/dashboard,/adminou/moderador - Parâmetro
?redirect=funciona para clientes (ex.: voltar à loja após login) - Esqueci minha senha na tela de login (email do formulário → link por email)
- Cadastro de cliente em
/conta/criarexige data de nascimento (18+), validada no front, API e banco - Cadastro de loja em
/lojista/cadastro(link na tela de login) - Admin e moderador têm telas dedicadas em
/admin/entrare/moderador/entrar(com recuperação de senha)
Botões na UI em /conta/entrar, /conta/criar e /lojista/cadastro. Requer configuração manual no Supabase — o código já está pronto; sem isso aparece provider is not enabled.
- APIs & Services → Credentials → OAuth client ID (Web application)
- Authorized JavaScript origins:
https://maredevendas.com.brhttps://www.maredevendas.com.brhttps://kelfys.github.iohttps://ulpjsxmilumqedkkfuqw.supabase.co
- Authorized redirect URIs (apenas Supabase, não o GitHub Pages):
https://ulpjsxmilumqedkkfuqw.supabase.co/auth/v1/callback
- Authentication → Providers → Google: Enable + Client ID + Client Secret → Save
- Authentication → URL Configuration: Site URL e Redirect URLs (tabela acima)
- Usuário clica no botão Google →
signInWithGoogle()emjs/api.js - Google redireciona com
?code=→handleAuthCallback()emjs/app.js - Rota
#/auth/callbackenvia paraoauth-next(ex.:/favoritosou/lojista/cadastro) - Cadastro de loja com Google:
completeOAuthSignup()promovecustomer→merchant
- Desktop:
Criar lojaeEntrar(pills; Entrar só para visitantes) - Mobile:
Criar lojaeEntrarno topo do menu hambúrguer (☰); carrinho 🛒 para clientes logados - Logado: 👤 Minha conta (cliente), painel do lojista/admin/moderador ou sair conforme o papel
- Painéis admin/moderador/lojista: link ← Voltar ao site (feed)
Comportamento ao rolar a página (listener passive em window):
| Recurso | Arquivo | Onde vale | Comportamento |
|---|---|---|---|
| Header mobile | js/header-scroll.js |
Telas ≤767px | Cabeçalho some ao rolar para baixo e volta ao rolar para cima; não esconde com menu ☰ aberto |
| Filtros da home | js/home-filters-scroll.js |
#/ (bairros + categorias) |
Chips somem ao rolar para baixo; a busca permanece fixa |
| Chips bairro/categoria | js/pages/home.js (bindChipRowScroll) |
#/ desktop e mobile |
Arrastar / roda do mouse no desktop; toque no mobile; fade dinâmico nas pontas |
| Botão ↑ | js/scroll-to-top.js |
Global | Aparece após ~280px; clique sobe ao topo e chama render() (atualiza a rota) |
Classes CSS: .header--scroll-hidden, .home-toolbar__filters--hidden, .scroll-to-top--visible, .category-scroll--dragging.
Inicialização no boot (app.js); estados resetados a cada troca de rota (router.js).
O cabeçalho mostra Maré · de · Vendas:
| Parte | Cor |
|---|---|
| Maré | Muda com o status de alerta (admin) |
| de | Cor do texto (preto/cinza) |
| Vendas | Dourado fixo |
Admin → Conta (#/admin/conta) → Cor de alerta do logo → Aplicar no site.
Valor público em platform_settings.logo_accent (migration 057); presets: normal, promo, alerta (verde), urgente, info, rosa, preto.
Arquivos: js/logo-accent.js, css/styles.css (html[data-logo-accent]), js/app.js (loadLogoAccent).
Deploy automático ao fazer push na main (após testes passarem no CI).
O workflow (.github/workflows/deploy.yml):
npm ci+npm test(Vitest)- Copia
index.html,strings-editor.html,css/,js/,favicon.svge404.htmlparadist/ - Injeta
?v=<commit>em todos os.jse emstyles.css(cache bust) - Gera shells SPA por rota (
copy-spa-shells.sh) para deep links no GitHub Pages - Publica na branch
gh-pagesvia Peaceiris
Deploy manual:
gh workflow run deploy.yml| Rota | Página |
|---|---|
/ |
Feed de lojas e produtos (Para você — sem aba separada de anúncios) |
/loja/:slug |
Página pública da loja |
/conta/entrar |
Login unificado |
/conta/criar |
Cadastro do cliente (com data de nascimento) |
/lojista/cadastro |
Cadastro de loja |
/dashboard |
Painel do lojista |
/admin/entrar |
Login admin |
/admin |
Painel admin |
/admin/bairros |
Gestão de bairros/regiões (só admin) |
/admin/moderadores |
Promover moderadores e atribuir bairro |
/moderador/entrar |
Login moderador |
/moderador |
Painel moderador (escopo regional) |
/moderador/aprovacoes |
Aprovar lojas e planos do bairro |
/moderador/lojas |
Lojas do bairro (somente leitura) |
/moderador/produtos |
Produtos do bairro (somente leitura) |
/moderador/pedidos |
Pedidos das lojas do bairro |
/moderador/conta |
Perfil e região atribuída |
/favoritos |
Dashboard do cliente (favoritos, curtidos, pedidos, perfil) |
/regras |
Regras e planos |
/auth/callback |
Retorno OAuth Google / recovery de senha |
Rotas sempre em hash:
https://maredevendas.com.br/#/conta/entrar(ou…github.io/mdv-web/#/conta/entrar). O404.htmlredireciona rotas diretas para/#/rota.
A plataforma é multi-bairro: um único site, várias regiões. O admin controla tudo; cada moderador opera só no bairro atribuído.
Admin (visão global)
└── Bairros (regiões ativas no painel)
└── Moderador regional (1 bairro ou todos)
└── Lojas e pedidos daquele escopo
| Tabela / coluna | Função |
|---|---|
neighborhoods |
Bairros/regiões (nome, slug, cidade, UF, ativo) |
stores.neighborhood_id |
Loja pertence a um bairro (obrigatório no cadastro) |
users.neighborhood_id |
Moderador vinculado a um bairro (ou null = todos, conforme fluxo atual) |
platform_settings |
Config pública da UI (ex.: logo_accent) — migration 057 |
Bairros são geridos em #/admin/bairros (criar, editar, ativar/desativar, excluir se vazio). O seed antigo da migration 033 (Copacabana, Ipanema, etc.) pode não refletir a produção atual.
- Na home, chips de bairro filtram lojas, produtos e anúncios (chip Todos lista todas as regiões)
- A escolha fica salva no navegador (
js/neighborhood.js) - Sem bairro selecionado, o primeiro bairro ativo é usado automaticamente
- Ao rolar o feed, bairros e categorias se recolhem (
home-filters-scroll.js); no mobile o header também some (header-scroll.js) - Produtos podem ser marcados como Usado no catálogo (
is_used, migration043)
| Aba | Rota | O que faz |
|---|---|---|
| Bairros | #/admin/bairros |
Criar região (nome, cidade, UF); ativar/desativar |
| Moderadores | #/admin/moderadores |
Promover usuário existente com bairro obrigatório; alterar região depois; permissão de aprovar mudança de plano |
| Lojas | #/admin/lojas |
Ver/editar bairro; criar loja por e-mail do dono (cliente → lojista; 1 loja/lojista, exceto lojasfake@gmail.com) |
| Produtos | #/admin/produtos |
Sidebar de lojas + catálogo; loja seed seed-produtos-fake (produtosfake@) no topo, sem teto de itens; admin sem cooldown de preço |
| Conta | #/admin/conta |
Senha, e-mail e cor de alerta do logo |
- Login:
#/moderador/entrar - Painel mostra a região atribuída no topo e em Minha conta
- Aprovações: só cadastros de loja e pedidos de plano de lojas do seu bairro
- Lojas / Produtos: somente leitura, já filtrados pela região
- Pedidos: só pedidos de lojas do bairro
- Segurança reforçada por RLS no Supabase (
moderator_neighborhood_id())
Moderadores com permissão “Pode aprovar planos” (checkbox no admin) analisam mudanças de plano apenas das lojas do bairro deles.
Em #/lojista/cadastro, o lojista escolhe Bairro / região antes de enviar. A aprovação cai na fila do moderador daquele bairro.
- O usuário precisa existir (cadastro em
/conta/criarou login Google) - Admin → Moderadores (
#/admin/moderadores) - Informar email, escolher bairro e marcar “Pode aprovar planos” se necessário
- O moderador passa a ver só lojas, produtos, pedidos e aprovações daquele bairro
supabase/migrations/033_neighborhoods.sql— schema, seed e políticas RLSjs/neighborhood.js— seleção no feed egetStaffNeighborhoodScope()js/api.js—fetchNeighborhoods,createNeighborhood,promoteUserToModerator(email, neighborhoodId)js/pages/admin.js— abas Bairros e Moderadoresjs/pages/home.js— seletor de bairro no feedjs/staff-nav.js— menu dos painéis admin e moderadortests/neighborhood.test.js— testes de escopo e persistência do bairro
Limites e regras ficam em js/plans.js (PLAN_LIMITS, canCreateProduct, canAddProductImage, planAllowsStoreLogo, planAllowsStoreBanner). A API (js/api.js) e o painel do lojista bloqueiam cadastro/upload além do plano. Textos em js/strings.js (seção plans.*).
O plano Gratuito é ativado após aprovação do cadastro da loja. Planos pagos (Plus, Premium) são solicitados em Dashboard → Planos e confirmados pelo admin após comprovante.
| Recurso | Limite |
|---|---|
| Itens no catálogo (produtos ou serviços) | 1 no total |
| Imagens nos produtos | 1 foto no catálogo |
| Logo da loja (foto de perfil) | Não — só planos pagos (Plus/Premium) |
| Banner personalizado da vitrine | Não — apenas cor/tema padrão |
| Alteração de preço (lojista) | A cada 24 h |
| Alteração de preço (admin) | Sem espera (bypass do cooldown) |
| Anúncios no feed | Não — exclusivo Premium |
| Pedidos | Via WhatsApp |
| Ativar/ocultar itens no catálogo | Sim |
Lojistas no Gratuito publicam 1 produto/serviço com 1 foto. Logo e banner exigem upgrade.
| Plano | Itens | Fotos no catálogo | Logo | Banner | Anúncios no feed | Cooldown de preço (lojista) |
|---|---|---|---|---|---|---|
| Gratuito | 1 | 1 | Não | Não | Não | 24 h |
| Plus | 6 | 6 | Sim | Sim | Não | 12 h |
| Premium | 30 | 30 | Sim | Sim | 2 inclusos/mês (+ extras) | 6 h |
Detalhes de preços, destaques no feed e lista completa de benefícios: #/regras (seção planos) ou painel Planos do lojista.
Fluxo completo (migration 044_store_ads_approval_billing.sql):
- Lojista Premium envia anúncio em Painel → Anúncios → registro
pendingcom UUID (toast exibe o ID). - Slots inclusos: até 2/mês calendário (
is_extra = false). Contagem só de inclusos, não extras. - Acima do limite: anúncio extra — taxa R$ 5 (
STORE_AD_EXTRA_FEE), checkbox de confirmação e link WhatsApp para comprovante;fee_acknowledgedobrigatório na API. - Admin e moderador analisam na aba Aprovações (
#/admin/aprovacoesou#/moderador/aprovacoes): cards com ID, loja, mensagem, badge de taxa extra quando aplicável. - Aprovação define
approved_ateexpires_at= +24 h (STORE_AD_DURATION_HOURS). Só então o anúncio entra no mix do feed da home (js/feed.js). - Rejeição marca
rejected— não aparece no feed.
| Papel | Onde age |
|---|---|
| Lojista | js/pages/merchant.js — merchantAdsCreatePanel, bindAdForm |
| Admin / moderador | js/pages/admin.js — renderStoreAdApprovalCards, bindStoreAdApprovalActions |
| API | js/api.js — createStoreAd, fetchPendingStoreAds, approveStoreAd, rejectStoreAd |
| Limites / taxa | js/plans.js — STORE_AD_EXTRA_FEE, canCreateIncludedStoreAd, isExtraStoreAdSlot |
| Banco | store_ads + RLS; função store_ads_included_this_month() |
Testes: tests/api-premium-ads.test.js, tests/api-store-ad-approval.test.js, tests/plans.test.js.
| Função | O que valida |
|---|---|
planAllowsStoreLogo(planId) |
Logo só em planos pagos (não no Gratuito) |
planAllowsStoreBanner(planId) |
Banner só em planos pagos |
planAllowsProductImages(planId) |
Fotos no catálogo só se productImages > 0 |
canCreateProduct(planId, count) |
Teto de itens no catálogo |
canAddProductImage(planId, …) |
Teto de imagens (Gratuito: 1 no catálogo) |
planAllowsStoreAds(planId) |
Anúncios no feed só no Premium |
canCreateIncludedStoreAd(planId, includedThisMonth) |
Slot incluso (< 2/mês) |
canCreateExtraStoreAd(planId) |
Permite extra pago no Premium |
isExtraStoreAdSlot(planId, includedThisMonth) |
true quando inclusos do mês esgotados |
Testes: tests/plans.test.js, tests/api-premium-ads.test.js, tests/api-store-ad-approval.test.js.
| Recurso | Comportamento |
|---|---|
| Ciclo | 30 dias por assinatura (stores.subscription_expires_at) |
| Aviso ao lojista | Banner com 72 h de antecedência no painel |
| Sem pagamento | Downgrade automático ao Gratuito; só o produto mais recente permanece ativo |
| WhatsApp (comprovante) | Mensagem inclui nome e ID da loja (sem linha de email) |
| Admin/moderador | Seção Planos a renovar + pedidos com merchant_note identificando a loja |
Arquivos: js/plan-renewal.js, js/api.js (downgradeExpiredStoreToFree), js/pages/merchant.js, js/pages/admin.js.
Testes: tests/plan-renewal.test.js.
- Cliente navega até uma loja aprovada
- Adiciona produtos ao carrinho (uma loja por vez)
- Escolhe forma de pagamento e preenche nome, telefone e endereço
- O pedido é salvo no Supabase (
orders+order_items); se o cliente estiver logado,orders.user_idvincula o pedido à conta (migration032) - WhatsApp abre com a mensagem formatada para o lojista
- Cliente logado vê o histórico em Minha conta → Pedidos (pedidos anteriores à migration
032não têmuser_id)
- Crie
js/pages/minha-pagina.jscomexport async function renderMinhaPagina(main) { ... } - Registre em
js/app.jscomregisterRoute - Adicione link em
js/ui.jsse for rota pública - Textos da página em
js/strings.js— uset(), não strings hardcoded
- Edite
js/strings.jsdiretamente ou usestrings-editor.htmle baixe o arquivo npm test(incluitests/strings.test.js)- Commit — o deploy publica o módulo atualizado sem build extra
- Crie
supabase/migrations/034_descricao.sql(próximo número sequencial) npx supabase db pushou SQL Editor- Atualize
api.jse a UI conforme necessário
Últimas migrations:
044_store_ads_approval_billing.sql— anúncios:is_extra,fee_amount,fee_acknowledged, RLS staff, slots inclusos vs. extras043_product_is_used.sql— tag Usado em produtos042_content_reports.sql— denúncias de loja/produto033_neighborhoods.sql— bairros, escopo regional de moderadores e RLS
Em #/admin/lojas → + Nova loja:
- Informe o e-mail de uma conta já cadastrada (
owner_email) - API:
resolveOwnerForAdminStore+createStoreAsAdminemjs/api.js - Se for cliente, o admin promove a lojista na hora
- Regra 1 lojista = 1 loja — se o e-mail já tem loja, erro amigável (“Este lojista já tem a loja …”)
- Exceção seed:
lojasfake@gmail.com(SEED_MULTI_STORE_OWNER_EMAIL) pode ter N lojas — use este e-mail para novas lojas demo/ads - Admin e moderador não podem ser donos de loja
| Objetivo | E-mail do dono / loja |
|---|---|
| Loja de pessoa real | Conta nova ou lojista sem loja |
| Mais lojas fake no feed | lojasfake@gmail.com |
| Muitos produtos demo | Loja seed-produtos-fake (conta produtosfake@gmail.com) em #/admin/produtos |
Testes: tests/api-resolve-owner-email.test.js, tests/api-fetch-merchants.test.js.
Scripts locais de DB (.env.local com DATABASE_URL):
npm run db:push:url # supabase db push
npm run db:push:url -- --include-all # inclui migrations fora de ordem no remoto
node tools/apply-sql.mjs supabase/migrations/044_store_ads_approval_billing.sql
node tools/register-migration.mjs 044 store_ads_approval_billing- Ativar Google OAuth em produção (Supabase Providers — ver seção acima)
- Notificações em tempo real (Supabase Realtime) para novos pedidos
- Integração de pagamento/assinatura (Stripe)
- Service Worker para cache offline
- Testes E2E automatizados no CI (Playwright)
- Paginação no feed e listagens longas
Projeto privado — uso conforme acordado com os mantenedores.