Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# Dominio
API_DOMAIN=api.comerciobes.com.br
FRONTEND_URL=https://comerciobes.com.br
WEBHOOK_BASE_URL=https://api.comerciobes.com.br
COOKIE_DOMAIN=.comerciobes.com.br

# Banco Docker local/producao
Expand Down Expand Up @@ -44,9 +43,5 @@ CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=

# Pagamentos Mercado Pago - opcional
MERCADO_PAGO_ACCESS_TOKEN=
MERCADO_PAGO_PUBLIC_KEY=

# Nao habilite em publico; serve apenas para homologacao sem banco.
AUTH_FALLBACK_ENABLED=false
1 change: 1 addition & 0 deletions .github/workflows/ci-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
--health-retries 5
env:
TEST_DATABASE_URL: postgresql://comerciobes:test-password@127.0.0.1:5432/comerciobes_test?schema=public
DATABASE_URL: postgresql://comerciobes:test-password@127.0.0.1:5432/comerciobes_test?schema=public
defaults:
run:
working-directory: backend
Expand Down
27 changes: 14 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

## O que é

Guia comercial + marketplace local para **Boa Esperança do Sul — SP**. Conecta moradores ao comércio local via web/PWA. Projeto de um colaborador (dono do repo), sendo desenvolvido com Pedro como co-desenvolvedor.
Vitrine digital e guia comercial para **Boa Esperança do Sul — SP**. Conecta moradores aos estabelecimentos locais via web/PWA. O Comercio BES cuida da descoberta; transacoes pertencem ao Braga Commerce.

## Stack

| Camada | Tecnologia |
|--------|-----------|
| Frontend atual | HTML5 + CSS3 + JS vanilla (PWA) |
| Frontend alvo | React/Next em migracao planejada |
| Backend | Node.js + Express.js |
| Banco | PostgreSQL 16 via Prisma ORM |
| Auth | JWT (HS256) + bcrypt |
Expand All @@ -24,13 +23,13 @@ backend/ → API Express
src/
server.js → entry point
controllers/ → lógica de negócio
routes/ → auth, comercios, categorias, avaliacoes, pedidos, pagamentos, upload, estatisticas
routes/ → auth, comercios, categorias, avaliacoes, upload e estatisticas
middleware/ → auth.js (JWT), upload.js (multer), errorHandler.js
lib/prisma.js → PrismaClient singleton
minha-conta/ → painel unico por role (/minha-conta)
prisma/
schema.prisma → 12 modelos: User, Endereco, Categoria, Comercio, Produto, Promocao, Avaliacao, Estatistica, Pedido, ItemPedido, Pagamento, AuditLog
docs/ → OPERACAO.md
schema.prisma → schema atual em transicao para o modelo minimo da vitrine
docs/ → roadmap, ADRs, operacao e auditoria de seguranca
```

## Comandos úteis
Expand Down Expand Up @@ -64,7 +63,7 @@ docker compose up -d

- `admin` — acesso administrativo, painel em `/minha-conta`
- `comerciante` — gerencia própria loja, painel em `/minha-conta`
- `cliente` — avalia, faz pedidos
- `cliente` — salva favoritos e avalia quando autenticado

## Branch strategy

Expand All @@ -89,17 +88,19 @@ https://api.comerciobes.com.br

## Fase atual

**Fase 4 — Rebase do Marketplace** (em andamento)
- Escopo congelado: pedido real, painel, pagamento testado e checks confiaveis.
- Vitrine vanilla atual deve ser mantida apenas ate a migracao cobrir o fluxo essencial.
- `localStorage` pode manter carrinho/favoritos, mas nao deve ser fonte de verdade para pedidos.
- Cupons, notificacoes push e novas features ficam fora ate checkout/pagamento estarem testados.
- Frontend React/Next deve nascer em paralelo, consumindo a API existente, sem reescrever backend agora.
**v0.1 - Scope Reset** (em andamento)
- Carrinho, checkout, pedidos e pagamentos nao fazem parte do produto publico.
- Interfaces, rotas e controllers do marketplace legado foram removidos; apenas tabelas historicas aguardam migracao controlada.
- O Braga Commerce e dono de catalogo transacional, estoque, pedido, pagamento, entrega e pos-venda.
- A integracao com o Braga Commerce deve passar por um adapter read-only com contrato versionado.
- A vitrine vanilla sera estabilizada antes de qualquer decisao de reescrita.

## Auditoria de segurança

Concluída em março/2026. **43 vulnerabilidades corrigidas** (4 críticas, 8 altas, 10 médias, 5 baixas, 16 frontend). Detalhes em `docs/security-audit.md`.

## O que ainda não existe

- CI/CD completo de deploy; checks backend com PostgreSQL existem e devem ser mantidos obrigatorios
- Cobertura automatizada suficiente para bloquear regressao
- Adapter homologado com o Braga Commerce
- CI/CD completo de deploy
22 changes: 11 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## O que é

Guia comercial + marketplace local para **Boa Esperança do Sul — SP**. Conecta moradores ao comércio local via web/PWA. Projeto de um colaborador (dono do repo), sendo desenvolvido com Pedro como co-desenvolvedor.
Vitrine digital e guia comercial para **Boa Esperança do Sul — SP**. O Comercio BES cuida da descoberta local; transacoes pertencem ao Braga Commerce.

## Stack

Expand All @@ -23,13 +23,13 @@ backend/ → API Express
src/
server.js → entry point
controllers/ → lógica de negócio
routes/ → auth, comercios, categorias, avaliacoes, pedidos, pagamentos, upload, estatisticas
routes/ → auth, comercios, categorias, avaliacoes, upload e estatisticas
middleware/ → auth.js (JWT), upload.js (multer), errorHandler.js
lib/prisma.js → PrismaClient singleton
minha-conta/ → painel unico por role (/minha-conta)
prisma/
schema.prisma → 12 modelos: User, Endereco, Categoria, Comercio, Produto, Promocao, Avaliacao, Estatistica, Pedido, ItemPedido, Pagamento, AuditLog
docs/ → OPERACAO.md
schema.prisma → schema atual com tabelas transacionais historicas aguardando migracao
docs/ → roadmap, ADRs, operacao e auditoria
```

## Comandos úteis
Expand Down Expand Up @@ -63,7 +63,7 @@ docker compose up -d

- `admin` — acesso administrativo, painel em `/minha-conta`
- `comerciante` — gerencia própria loja, painel em `/minha-conta`
- `cliente` — avalia, faz pedidos
- `cliente` — gerencia a conta e avalia quando autenticado

## Branch strategy

Expand All @@ -82,12 +82,12 @@ claude/* → worktrees do Claude Code (merge na dev ao final da sessão)

## Fase atual

**Fase 4 — Marketplace** (em andamento)
- Carrinho + histórico de pedidos: implementado (localStorage)
- Pedidos backend (`/api/pedidos`): implementado
- Pagamento PIX Mercado Pago (`/api/pagamentos`): backend implementado, pendente testes + UI
- Notificações push: pendente
- Sistema de cupons: pendente
**v0.1 - Scope Reset** (em andamento)
- Carrinho, checkout, pedidos, pagamentos e estoque nao fazem parte do produto.
- Interfaces, APIs e dependencias transacionais foram removidas.
- Tabelas historicas permanecem ate inventario, backup e migracao versionada.
- O Braga Commerce e dono da experiencia de compra.
- A vitrine vanilla sera simplificada e estabilizada antes de qualquer reescrita.

## Auditoria de segurança

Expand Down
47 changes: 24 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
# Comercio BES

Guia comercial e marketplace local de Boa Esperanca do Sul - SP.
Vitrine digital e guia comercial de Boa Esperanca do Sul - SP.

## Decisao atual
O produto ajuda moradores a encontrar estabelecimentos, consultar informacoes essenciais e iniciar contato pelos canais oficiais da loja. Carrinho, pedido, pagamento, estoque e entrega nao fazem parte do nucleo do Comercio BES.

Nao recomecar do zero. A base atual continua como backend/API/infra, mas a Fase 4 fica congelada em estabilizacao do marketplace real.
## Direcao do produto

Prioridade atual:
- O Comercio BES e a fonte da descoberta local: busca, categorias, perfil, fotos, horario, mapa, contato e moderacao.
- O Braga Commerce e a fonte da transacao: catalogo comercial, estoque, carrinho, pedido, pagamento, entrega e pos-venda.
- A integracao sera feita por um adapter read-only e links assinados/deep links quando o contrato estiver definido.
- As interfaces e APIs do marketplace legado foram removidas; as tabelas historicas permanecem somente ate a migracao controlada dos dados.
- Dados transacionais existentes somente poderao ser removidos apos inventario, backup restauravel e aprovacao da politica de retencao.

1. checkout criando pedido real em `/api/pedidos`;
2. painel lendo e atualizando esses pedidos;
3. checks com PostgreSQL real no CI;
4. pagamento Mercado Pago testado e endurecido;
5. migracao planejada da vitrine para React/Next sem apagar a vitrine atual antes de cobrir o fluxo essencial.

Nada de cupons, push ou novas features antes disso.

Rodar a API em servidor local com Docker agora e migrar para VPS depois. O dominio da API ja deve ser definitivo:

```txt
https://api.comerciobes.com.br
```

Assim a migracao futura troca DNS e restaura backup, sem mudar o frontend.
A decisao completa esta em [`docs/adr/0001-product-scope-and-braga-commerce.md`](docs/adr/0001-product-scope-and-braga-commerce.md).

## Stack

- Frontend: HTML, CSS e JavaScript vanilla.
- Frontend: HTML, CSS e JavaScript vanilla, distribuido como PWA.
- Backend: Node.js + Express.
- Banco: PostgreSQL 16 via Prisma.
- Proxy/SSL: Caddy.
Expand Down Expand Up @@ -72,7 +62,18 @@ URLs locais:
- Lojista: `lojista@comerciobes.com` / `lojista123`
- Alias lojista: `comerciante@demo.com` / `demo123`

## Docs importantes
## Qualidade

```bash
cd backend
npm run build
npm test
npm audit --omit=dev --audit-level=high
```

## Documentacao

- Operacao, backup e migracao: `docs/OPERACAO.md`
- Roadmap, milestones e issues: `docs/ROADMAP.md`
- [Roadmap, milestones e issues](docs/ROADMAP.md)
- [ADR de escopo e Braga Commerce](docs/adr/0001-product-scope-and-braga-commerce.md)
- [Operacao, backup e migracao](docs/OPERACAO.md)
- [Auditoria de seguranca](docs/security-audit.md)
47 changes: 0 additions & 47 deletions backend/minha-conta/js/sections.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ const ICONS = {
users: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`,
shield: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"/></svg>`,
box: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>`,
archive: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>`,
receipt: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1z"/><line x1="16" y1="14" x2="8" y2="14"/><line x1="16" y1="10" x2="8" y2="10"/></svg>`,
bag: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 0 1-8 0"/></svg>`,
creditCard: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>`,
mail: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>`,
};

/** @type {Section[]} */
Expand Down Expand Up @@ -74,48 +69,6 @@ export const sections = [
icon: ICONS.box,
load: '/minha-conta/js/sections/merchant/ProdutosSection.js',
},
{
key: 'estoque',
path: '/minha-conta/estoque',
roles: ['comerciante'],
label: 'Estoque',
icon: ICONS.archive,
load: '/minha-conta/js/sections/merchant/EstoqueSection.js',
},
{
key: 'pedidos-loja',
path: '/minha-conta/pedidos-loja',
roles: ['comerciante'],
label: 'Pedidos',
icon: ICONS.receipt,
load: '/minha-conta/js/sections/merchant/PedidosSection.js',
},

// ---- Cliente ----
{
key: 'historico',
path: '/minha-conta/historico',
roles: ['cliente'],
label: 'Meus Pedidos',
icon: ICONS.bag,
load: '/minha-conta/js/sections/customer/HistoricoSection.js',
},
{
key: 'pagamentos',
path: '/minha-conta/pagamentos',
roles: ['cliente'],
label: 'Pagamentos',
icon: ICONS.creditCard,
load: '/minha-conta/js/sections/customer/PagamentosSection.js',
},
{
key: 'enderecos',
path: '/minha-conta/enderecos',
roles: ['cliente'],
label: 'Endereços',
icon: ICONS.mail,
load: '/minha-conta/js/sections/customer/EnderecosSection.js',
},
];

/**
Expand Down
23 changes: 5 additions & 18 deletions backend/minha-conta/js/sections/common/InicioSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,14 @@ export function mount(container, ctx) {
users: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`,
shield: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"/></svg>`,
list: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>`,
settings: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`,
box: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>`,
receipt: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1z"/><line x1="16" y1="14" x2="8" y2="14"/><line x1="16" y1="10" x2="8" y2="10"/></svg>`,
truck: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>`,
dollar: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>`,
mapPin: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>`,
creditCard: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>`,
mail: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>`,
cornerUpLeft: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 14 4 9 9 4"/><path d="M20 20v-7a4 4 0 0 0-4-4H4"/></svg>`
};

const roleConfig = {
admin: {
emoji: SVG.crown,
titulo: 'Painel de Administração',
descricao: 'Gerencie usuários, lojas, pedidos e configurações globais do marketplace.',
descricao: 'Gerencie usuários, lojas e a moderação da vitrine local.',
cor: '#7C3AED',
items: [
{ icon: SVG.users, label: 'Usuários', path: '/minha-conta/usuarios', cap: 'users.manage' },
Expand All @@ -48,23 +40,18 @@ export function mount(container, ctx) {
titulo: 'Painel do Lojista',
descricao: stores.length > 1
? `Você gerencia ${stores.length} lojas. Use o seletor no topo para alternar.`
: 'Gerencie seus produtos, pedidos e recebíveis.',
: 'Gerencie as informações públicas e os produtos em destaque da sua loja.',
cor: '#047857',
items: [
{ icon: SVG.box, label: 'Produtos', path: '/minha-conta/produtos', cap: 'products.manage.own' },
{ icon: SVG.receipt, label: 'Pedidos', path: '/minha-conta/pedidos-loja', cap: 'orders.view.ownStore' },
{ icon: SVG.box, label: 'Produtos em destaque', path: '/minha-conta/produtos', cap: 'products.manage.own' },
],
},
cliente: {
emoji: SVG.bag,
titulo: 'Minha Conta',
descricao: 'Acompanhe seus pedidos, endereços e dados de pagamento.',
descricao: 'Gerencie seus dados de acesso ao guia comercial.',
cor: '#2563EB',
items: [
{ icon: SVG.bag, label: 'Meus Pedidos', path: '/minha-conta/historico', cap: 'orders.view.own' },
{ icon: SVG.mail, label: 'Endereços', path: '/minha-conta/enderecos', cap: 'addresses.manage.own' },
{ icon: SVG.creditCard, label: 'Pagamentos', path: '/minha-conta/pagamentos', cap: 'payments.manage.own' },
],
items: [],
},
};

Expand Down
5 changes: 0 additions & 5 deletions backend/minha-conta/js/sections/common/PerfilSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ async function loadPerfil(container, ctx) {
try {
const res = await fetch('/api/auth/perfil', {
credentials: 'include',
headers: { 'Authorization': `Bearer ${getToken()}` },
});
if (!res.ok) return;
const data = await res.json();
Expand Down Expand Up @@ -145,7 +144,6 @@ async function submitPerfil(e, container, ctx) {
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': ctx.csrfToken,
'Authorization': `Bearer ${getToken()}`,
},
body: JSON.stringify(body),
});
Expand Down Expand Up @@ -173,9 +171,6 @@ function showFeedback(el, msg, type) {
el.className = `feedback-box ${type}`;
}
function hideFeedback(el) { el.className = 'feedback-box hidden'; }
function getToken() {
return document.cookie.split(';').find(c => c.trim().startsWith('access_token='))?.split('=')[1] || '';
}

export function unmount() {
_cleanup.forEach(fn => fn());
Expand Down
Loading
Loading