Pular para o conteúdo principal

Visão Geral da Arquitetura

Plataforma multi-tenant de apostas esportivas (Cactus Gaming). ~30 sites clientes, cada um com seu fork do template base.

Repositórios

RepoDescrição
front-cactus-coreSDK TypeScript — pacotes @cactus-agents/* (monorepo)
front-web-baseTemplate React Router v7 (SSR / Cloudflare Workers)
front-cactus-docsEsta documentação (Docusaurus)
front-web-{marca}Forks de marca (ex: front-web-vera-bet-br)
front-web-panelPainel admin (vault + deploys)
front-opsReusable workflow de deploy + configuração por brand/repo/ambiente

Stack

CamadaTecnologia
FrameworkReact Router v7 (SSR nativo no Cloudflare Workers)
Build/bundlerVite (via React Router v7)
StylingTailwind CSS v3 + CSS custom properties para tokens de tema por cliente
State (client)Zustand (13 stores: auth, layout, brand, gamification, kyc, validationSteps, validationRuntime, passwordValidation, games, payments, wallet, user, accountMenu)
State (server)React Router loaders
i18ni18next + react-i18next (traduções) + @cactus-agents/i18n
Íconeslucide-react
Fonte@fontsource/montserrat
Flagscountry-flag-icons
Linter/formatterBiome (todos os repos)
TestesVitest + React Testing Library
Pre-commitHusky + lint-staged → biome check --write
CommitsConventional Commits (commitlint)
Package managerpnpm (enforced)
Node>=24.0.0 (recomendado: 24.14.1)

SDK — Pacotes @cactus-agents/*

PacoteDescrição
api-clientApiClient base (fetch, headers, auth)
authAuthService (login, register, logout, profile)
brandBrandConfig (appearance, features, settings)
country-configConfiguração por país (moeda, locale, validações)
i18nInternacionalização (setup i18next, namespaces)
gamesGamesService (home, categorias, providers, busca)
gamificationSmartico SDK integration
kycKYC (operadores, iframe, polling)
paymentsPaymentsService (deposit, withdraw, providers)
sportsSportsService (Altenar, Betby)
typesTipos compartilhados
userUserService (perfil, segurança, limites)
utilsUtilitários compartilhados
validationsValidações (modules, contexts, runtime)
walletWalletService (saldo, transações, rollover)

Deploy

  • SSR via Cloudflare Workers (React Router v7)
  • Assets estáticos → Cloudflare R2
  • 1 Worker por ambiente (SSR + static, sem api-proxy separado)
  • Deploy centralizado via reusable workflow no front-ops
  • Forks chamam cactus-agents/front-ops/.github/workflows/deploy.yml@main
  • Configuração em config/brands/<brand>/{brand.yml,repos.yml} mapeia repo → environments → worker_name (com defaults por brand)
  • Secrets de deploy (Cloudflare) são org secrets com acesso restrito

Pacotes privados

  • GitHub Packages como registry privado
  • Token de leitura por cliente (acesso apenas a @cactus-agents/*, sem código-fonte)

Modelo de fork

O fork é uma cópia completa do front-web-base. O cliente tem liberdade total para personalizar: criar componentes, páginas, regras, redesenhar o tema inteiro.

Pontos rápidos de customização (já preparados no template):

  • app/config/theme/colors.ts — paleta de cores
  • app/config/layout/composition.ts — composição do layout (slots e variantes por brand)
  • app/config/routes/paths.ts — customização de URL paths por brand

O registro de rotas fica em app/router/routes.ts (não overrideable). A árvore de variantes de layout vive em app/layouts/variants/ (não overrideable).

Mas o cliente não está limitado a esses arquivos. Pode modificar qualquer coisa no fork.

Única restrição: não alterar pacotes @cactus-agents/* diretamente — eles são dependências npm atualizáveis via pnpm update.

Regra arquitetural: CORE vs BASE

O front-web-base é burro por design. Ele chama métodos do SDK, recebe dados normalizados e renderiza. Toda inteligência de domínio vive nos pacotes @cactus-agents/*.

O que vai onde

Se é...Onde vive
Shape real da API (snake_case)@cactus-agents/<pkg>/src/ como RawXxx
Tipo público normalizado (camelCase)@cactus-agents/<pkg>/src/types.ts
Transform raw → público@cactus-agents/<pkg>/src/transform.ts
Constante de domínio (opções de seleção, etc.)@cactus-agents/<pkg>/src/ exportada
Helper de cálculo (parseLimitPeriod, etc.)@cactus-agents/<pkg>/src/ exportado
Validador de formato (CPF, CLABE, RUT...)@cactus-agents/country-config/src/validators/
Lógica condicional por país@cactus-agents/country-config/src/countries/
Feature flag da API@cactus-agents/brand/src/transform/features.ts
Proxy HTTP server-sidefront-web-base/app/routes/api.*.ts (simples, sem lógica)
Renderização de UIfront-web-base/app/components/**/*.tsx

Padrão Raw → Transform → Público

API (snake_case) → RawXxx { campo_api } → transformXxx() → Xxx { campoNormalizado }

Exemplo: Login History

  • A API retorna date: "2026-03-17 18:14:42" — o SDK converte para createdAt: "2026-03-17T18:14:42"
  • A API retorna city + state separados — o SDK compõe location: "São Paulo, SP"
  • O template só usa item.createdAt e item.location — zero normalização local

Documentação detalhada: CORE vs BASE — Arquitetura

Regra arquitetural: app/config

A pasta app/config no template deve conter somente configuração estática de brand (dados que variam por fork/marca):

  • ✅ Objetos de config exportados (ex: sportsConfig, gamificationConfig, depositConfig)
  • ❌ Tipos e interfaces de config — ficam em app/types/ (importados pelos configs)
  • ❌ Hooks (devem ficar em app/hooks/)
  • ❌ Parsers, helpers, lógica de negócio
  • ❌ Registros estruturais (registro de rotas → app/router/, registry de variantes → app/layouts/)
  • ❌ Dados globais (catálogo de países, DDI, currency-country) — vêm do SDK @cactus-agents/*

Overrides de brand são resolvidos pelo brandOverridesPlugin do Vite: qualquer import ~/xxx é checado primeiro em overrides/<brand-key>/app/xxx e, se o arquivo existir lá, substitui o base. Não há whitelist — qualquer arquivo sob ~/ é potencialmente overridável. Os alvos mais comuns estão em forking/override-files.

Tipos de config ficam em app/types/ (ex: navigation.ts, layout.ts, licenses.ts, deposit.ts, topbar.ts). Configs e overrides importam tipos diretamente de ~/types/<file>.

Decisões técnicas

DecisãoMotivo
Cookie HttpOnly para JWTToken não fica acessível no browser JS; leitura e validação acontecem no server.
AuthService singleton client-sideUm único ApiClient pro browser, separado do SSR.
Zustand sem persist para authStore mantém apenas user/userInfo/authModal/hydrated; estado inicial vem do loader SSR.
13 Zustand stores separadasCada domínio tem sua store isolada. Evita store monolítica e facilita code-splitting.
Modais controlados por ZustandauthModal: 'login' | 'register' | null. Qualquer componente abre/fecha.
EnvProvider separado do BrandProviderclientEnv (API_BASE_URL etc.) precisa existir antes de qualquer chamada API client-side.
Fork = cópia completa + liberdade totalConfig files são atalhos rápidos, não limites. Cliente pode personalizar o que quiser.
Biome em vez de ESLintMais rápido, config única pra lint + format, sem plugins.
i18next para i18nBiblioteca consolidada com suporte a namespaces, interpolação e pluralização.