Pular para o conteúdo principal

User Account

Area do usuario no template: dados pessoais, configuracoes, historico, protecao (responsible gaming), recompensas, indicacoes e informe de rendimentos.

:::danger @cactus-agents/user nao existe mais Os pacotes @cactus-agents/auth, @cactus-agents/user e @cactus-agents/wallet foram deletados (core 452450e, 2026-04-17) e unificados em @cactus-agents/accounts. Nenhum import … from "@cactus-agents/user" resolve.

Regra pratica:

  • Regras de negocio e constantes (filterTimeoutOptions, TIMEOUT_DAYS_OPTIONS, isoDurationToHours, isAccountRestricted, …) → @cactus-agents/accounts
  • Estado e hooks React (useAccountsStore, useProfile, useFormatMoney, …) → @cactus-agents/accounts/react

Ver SDK — accounts. :::

Estado de auth e perfil

Nao ha mais app/store/auth.ts nem app/services/user.client.ts. user, userInfo, isAuthenticated e authHydrated vivem em useAccountsStore:

import { useAccountsStore } from "@cactus-agents/accounts/react";

const user = useAccountsStore((s) => s.user);
const userInfo = useAccountsStore((s) => s.userInfo);

:::danger Nao leia auth do loader do _layout O doc SSR e auth-agnostico (spec user-data-out-of-ssr, 2026-07): o loader do _layout nao retorna auth — o campo nao existe mais. useRouteLoaderData("routes/_layout") nunca deve ser usado pra decidir visibilidade ou comportamento de UI.

Pra "o usuario esta logado?" em UI, use o gate canonico useAuthGate() (app/hooks/useAuthGate.ts): SSR sempre pinta guest, o cookie nao-HttpOnly is_authenticated e o sinal otimista pos-mount, e a store reativa assume depois de authHydrated. Ver State Management. :::

Rotas

A area do usuario foi refatorada para um shell de tabs (routes/user/_layout.tsx, auth-protegido). Paths sao defaults — configuraveis via Route Registry (~/config/routes/paths); use routeHref() de ~/utils/routes.

ChavePath padraoArquivoDescricao
user.wallet/user/walletroutes/user/wallet.tsxCarteira — ver Wallet
user.rewards/user/rewardsroutes/user/rewards.tsxRecompensas resgataveis
user.data/user/dataroutes/user/general.tsxDados pessoais + secao bancaria por pais
user.history/user/historyroutes/user/history.tsxHistorico unificado (transacoes + login)
user.config/user/configroutes/user/config.tsxPreferencias, contratos, marketing, seguranca
user.refers/user/refersroutes/user/refers.tsxIndicacoes (app/config/referral/, useReferral())
user.incomeReport/user/income-reportroutes/user/income-report.tsxInforme de rendimentos
user.protection/user/protectionroutes/user/protection.tsxResponsible gaming

Fora do shell de tabs (full width):

ChavePath padraoArquivo
user.notifications/user/notificationsroutes/user/notifications.tsx
user.validate/user/validate/:type/:tokenroutes/user/validate.$type.$token.tsx

:::warning Chaves que nao existem user.account, user.security, user.loginHistory, user.accountProtection e user.irpf nao sao chaves do registry. routeHref("user.account") e erro de typecheck. A pagina de seguranca virou secao dentro de user.config; historico de login virou um filtro de user.history; IRPF virou user.incomeReport. :::

Tab bar — app/config/layout/user-tabs.ts

As tabs sao declarativas e brand-overridable:

export interface UserTab {
key: string;
labelKey: string;
icon: string;
routeKey: RouteKey;
/** Quando false (ou a funcao retorna false), a tab e escondida. */
condition?: boolean | ((brand: BrandConfig) => boolean);
}

UserTabNavigation (app/components/user/UserTabNavigation.tsx) filtra em tempo de render:

CondicaoEfeito
conta restritaesconde a tab referral
featuresConfig.incomeReport falsyesconde a tab incomeReport
accountFeaturesConfig.protectionPageEnabled falsyesconde a tab protection
tab.condition resolve falseesconde a tab

A tab referral tem condicao propria: quando featuresConfig.referralVariant e "default", ela so aparece se brand.settings.bonus.referral.active e o amount > 0.

Forks podem sobrescrever overrides/<brand>/app/config/layout/user-tabs.ts pra adicionar, remover ou reordenar tabs.

Painel flutuante do usuario

Alem das paginas, existe um painel (app/components/user/panel/) com abas Perfil / Dados / Notificacoes, montado por UserPanel e suas variantes (UserPanelFloatingDesktop, UserPanelFloatingTabs). Ele e config-driven por dois arquivos em app/config/widgets/:

ConfigConteudo
user-panel-data-menu.tsItens do menu "Dados" (userPanelDataMenuConfig)
user-panel-shortcuts.tsAtalhos da aba Perfil (userPanelShortcutsConfig)

Ambos sao brand-overridable e substituidos por arquivo inteiro (sem deep-merge).

Feature flags da area do usuario

app/config/features/account.ts (accountFeaturesConfig, brand-overridable):

export const accountFeaturesConfig: AccountFeatureFlags = {
accountSetLimits: false,
accountTimeoutLimits: false,
pixRandomEnabled: true,
redeemableRewards: true,
userPreferences: false,
protectionPageEnabled: true,
};

:::warning Propague campos novos Override e substituicao de arquivo inteiro. Ao adicionar um campo aqui, replique em todos os overrides/<brand>/app/config/features/account.ts — senao a brand recebe undefined. :::

Componentes

Lista viva — consulte app/components/user/.

Dados pessoais (components/user/account/) — rota user.data

  • InfosSection — nome, data de nascimento, genero, username
  • DocumentSection — documento (CPF, RUT, etc.)
  • EmailSection — email com verificacao
  • PhoneSection — telefone com verificacao SMS
  • AddressSection — endereco completo com estados (por pais)
  • ChileIdentityValidationBlock — validacao RUT + DIDIT (so CHL com RUT_VALIDATION); ver KYC
  • SectionWrapper — casca visual compartilhada

Secao bancaria (por pais)

Renderizada em routes/user/general.tsx condicionada por useCountry().payments.bankAccountSection:

ValorComponentePais
"pix"PixSectionBRA — chave Pix via /api/payments/bank-account
"clabe"ClabeSectionMEX — conta CLABE
"chl-banks"ChlBanksSectionCHL — conta bancaria generica
"nga-banks"NgaBanksSectionNGA — conta bancaria nigeriana
null(nenhum)Outros paises

Configuracoes (user.config)

  • PreferencesSection — preferencias do usuario (gated por accountFeaturesConfig.userPreferences); ver User Preferences
  • ContractsSection — termos aceitos, dinamico via legal.requiredTerms do country-config
  • MarketingSection — preferencias de comunicacao
  • TwoFactorSection — ativar/desativar 2FA (SMS ou email)
  • SocialAccountsSection — contas vinculadas; filtrada por features.socialAuth.* (so exibe providers habilitados pra brand)
  • PasswordSection — alterar senha

Protecao / Responsible Gaming (components/user/protection/)

A pagina (routes/user/protection.tsx) e controlada por feature flags vindas da API (/bff/features), lidas de brand.features:

Feature FlagSecoes visiveis
features.accountSetLimits === trueDepositLimitSection, BetLimitSection, LossLimitSection, TimeLimitSection
features.accountTimeoutLimits === trueTimeoutSection
SelfExclusionSection sempre presente no bloco "mais opcoes"

Outros componentes do dominio: LimitAccordion (UI compartilhada, com indicador "Ativo"), ExclusionAlternatives (sugere pausa/limites antes da autoexclusao quando o motivo tem hasAlternative), RestrictedModeAlert, SelfExclusionSupportPrompt / SelfExclusionSupportChat (modo featuresConfig.selfExclusionViaSupport).

RestrictedModeHome cobre a home quando a conta esta restrita.

:::info Constantes e helpers de responsible gaming vem do core TIMEOUT_DAYS_OPTIONS, SELF_EXCLUSION_MONTHS_OPTIONS, LIMIT_PERIOD, SPA_EXCLUSION_CODES e os helpers parseLimitPeriod, isoDurationToHours, hoursToIsoDuration, isPermanentExclusion, isLimitChangeBlocked, getLimitChangeUnblocksAt, getMinSelfExclusionMonths, getMinTimeoutDays, filterSelfExclusionOptions, filterTimeoutOptions, isAccountRestricted, getAccountRestriction, getRestrictionType — todos de @cactus-agents/accounts. O template nunca redefine essas regras. :::

Extensao de restricoes ativas

Com uma autoexclusao ou pausa ativa, o usuario pode estender o periodo (duracao maior). Nao e possivel reduzir nem remover a restricao ativa.

EstadoSelfExclusionSectionTimeoutSection
InativoForm completo — todas as opcoesForm completo — todas as opcoes
Self-exclusion ativa (nao permanente)Alerta + form com opcoes filtradas (>= minimo)N/A
Pausa ativaN/AAlerta + form com opcoes filtradas (>= minimo)
PermanenteAlerta — form ocultoN/A
Operator exclusionAlerta — form ocultoN/A
SPA exclusionAlerta — form ocultoN/A
Pausa ativa sem opcoes disponiveisN/AAlerta + "periodo maximo atingido"

A filtragem usa as funcoes puras do SDK (aceitam null/undefined com seguranca):

import { filterTimeoutOptions, TIMEOUT_DAYS_OPTIONS } from "@cactus-agents/accounts";
import { useAccountsStore } from "@cactus-agents/accounts/react";

const userInfo = useAccountsStore((s) => s.userInfo);
const availableTimeoutOptions = filterTimeoutOptions(userInfo);
import {
filterSelfExclusionOptions,
isPermanentExclusion,
SELF_EXCLUSION_MONTHS_OPTIONS,
} from "@cactus-agents/accounts";

const availableExclusionOptions = filterSelfExclusionOptions(userInfo);

O label do submit muda automaticamente: "Ativar" sem restricao ativa, "Estender" com restricao ativa.

:::caution Restricoes impostas externamente Quando a restricao veio de operador ou da SPA (autoridade reguladora), o usuario nao pode modificar — apenas o alerta e exibido. A deteccao usa isAccountRestricted() / getAccountRestriction() / getRestrictionType() do SDK, e o hook useAccountRestriction() (app/hooks/useAccountRestriction.ts) empacota isso pra UI. :::

Header / menu

  • HeaderUserArea — area do usuario logado (avatar, nome, saldo)
  • UserBox — box compacto do usuario
  • UserSummaryHeader — cabecalho da area de usuario
  • UserTabNavigation — tab bar do shell
  • UserPanel + components/user/panel/* — painel flutuante

Recompensas (components/user/rewards/)

RewardCard, RewardCardSkeleton, RewardsTabs, RewardsFilters, RewardsEmpty, RedeemModal — alimentados por useRewards() / useRewardsCountSync().

API Routes

Todas registradas em app/router/routes.ts. Ver a arvore la pra lista completa; as principais do dominio:

RotaDescricao
api/auth/profilePerfil completo do usuario (+ smarticoHash)
api/user/update-profileAtualiza dados do perfil
api/user/update-addressAtualiza endereco
api/user/update-pending-dataDados pendentes (fluxo regulatorio)
api/user/update-marketingPreferencias de comunicacao
api/user/update-preferencesPreferencias do usuario (whitelist no core)
api/user/check-passwordVerifica senha atual
api/user/change-passwordAltera senha
api/user/add-phoneAdiciona telefone
api/user/two-factor2FA
api/user/social-accounts / api/user/social-accounts/:idContas sociais vinculadas
api/user/login-historyHistorico de logins
api/user/update-limitsLimites de responsible gaming
api/user/timeout-limitsPausa temporaria
api/user/self-exclusionAutoexclusao
api/user/store-documentUpload/registro de documento
api/user/documents/verify-identityPasso 3 da identidade CHL
api/user/last-casino-gamesUltimos jogos jogados
api/user/referrals / api/user/indication-stats / api/user/referral-indicatorIndicacoes
api/user/zendesk-tokenToken do Zendesk
api/payments/bank-accountConta bancaria (PIX / CLABE / CL / NG)

:::note Padrao de proxy Rotas api/* sao proxies server-side: leem o JWT do cookie HttpOnly, delegam ao service do SDK e retornam dados ja transformados. Erros usam proxyErrorResponse() + unauthorizedNoToken(). Nenhuma logica de negocio no route handler. Ver Services. :::

Informe de rendimentos (user.incomeReport)

A tab e escondida quando featuresConfig.incomeReport e falsy — nao ha <Navigate> com routeHref("user.account") (essa chave nao existe).

O fluxo em routes/user/income-report.tsx:

  1. O ano disponivel vem de useUserConfigCacheStore (incomeReportYears); incomeReportAccessDenied cobre o caso de acesso negado
  2. O usuario escolhe ano e formato (download ou email)
  3. A request gera o informe no backend
  4. Polling local (pollingStatus: idleprocessingsuccess | error, com timeout) acompanha a geracao
  5. Download do PDF ou confirmacao de envio por email

Contratos por pais

ContractsSection usa legal.requiredTerms do country-config:

const { legal } = useCountry();
// legal.requiredTerms: ["tc", "privacy", "lgpd", "law"] para BRA
// ["tc", "privacy"] para CHL

Nao existe if (country === "BRA") no componente — ele usa o que o SDK define pro pais configurado. Quando features.userMigration === true, o termo "migrate" e adicionado automaticamente.