mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
108 lines
5.2 KiB
TypeScript
108 lines
5.2 KiB
TypeScript
/**
|
|
* Marca da instalação — nome e logo configuráveis pelo `.env`, SEM rebuild.
|
|
*
|
|
* Por que existe: quem instala o DeskcommCRM para clientes (agência, revendedor)
|
|
* precisa da própria marca na interface. Fazer isso editando o código quebraria o
|
|
* caminho de atualização — `update.sh` puxa a imagem nova e o patch local se perde,
|
|
* que é exatamente a dor nº 1 de quem hospeda o próprio sistema. Configuração em
|
|
* `.env` sobrevive a toda atualização.
|
|
*
|
|
* Vale para servidor (`process.env`) e navegador (`window.__PUBLIC_ENV__`, injetado
|
|
* em runtime pelo `<PublicEnvScript/>`) — mesmo modelo de `lib/sentry/dsn.ts`.
|
|
*
|
|
* As variáveis NÃO são `NEXT_PUBLIC_*` de propósito: essas são queimadas no bundle
|
|
* durante o `next build`, e o self-hoster roda uma imagem PRÉ-BUILDADA. A marca dele
|
|
* nunca apareceria. É o mesmo motivo pelo qual a URL do Supabase é injetada em
|
|
* runtime em vez de lida do bundle.
|
|
*/
|
|
|
|
export const DEFAULT_APP_NAME = "DeskcommCRM";
|
|
|
|
export type Branding = {
|
|
/** Nome exibido na interface e nos títulos de página. */
|
|
name: string;
|
|
/** URL do logo, ou `null` quando a marca deve aparecer como texto. */
|
|
logoUrl: string | null;
|
|
/** Arte opcional preparada para o tema escuro; ausente preserva o logo padrão. */
|
|
logoDarkUrl?: string | null;
|
|
/** Primeira letra do nome — usada onde só cabe um caractere (sidebar recolhida). */
|
|
initial: string;
|
|
};
|
|
|
|
/**
|
|
* Resolve a marca a partir dos valores crus. Função pura: recebe a fonte, não a
|
|
* procura — assim o mesmo resolvedor serve servidor, navegador e teste.
|
|
*
|
|
* Valor vazio ou só com espaços cai no padrão. Isso importa porque `.env` gerado
|
|
* por script costuma trazer a chave declarada e vazia (`APP_NAME=`), e tratar isso
|
|
* como "marca sem nome" deixaria a interface em branco.
|
|
*/
|
|
export function resolveBranding(
|
|
name: string | undefined | null,
|
|
logoUrl: string | undefined | null,
|
|
): Branding {
|
|
const resolvedName = (name ?? "").trim() || DEFAULT_APP_NAME;
|
|
const resolvedLogo = (logoUrl ?? "").trim();
|
|
return {
|
|
name: resolvedName,
|
|
logoUrl: resolvedLogo.length > 0 ? resolvedLogo : null,
|
|
// Spread em vez de [0]: nome começando com emoji ou acento composto quebraria
|
|
// no meio do code point e renderizaria caractere inválido.
|
|
initial: ([...resolvedName][0] ?? DEFAULT_APP_NAME[0]!).toUpperCase(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* A marca da instalação — **SOMENTE em componente de servidor.**
|
|
*
|
|
* ⚠️ NÃO CHAME ISTO DE UM `"use client"`. Em client component use
|
|
* `useMarcaDaInstalacao()` (`lib/branding/contexto.tsx`), que recebe a marca por
|
|
* prop do servidor. A regra é vigiada por
|
|
* `tests/unit/marca-sem-divergencia-de-hidratacao.test.tsx`, que varre todo
|
|
* arquivo `"use client"` — comentário não reprova build, teste reprova.
|
|
*
|
|
* ── Por que a regra existe ───────────────────────────────────────────────────
|
|
*
|
|
* Esta função lê fontes DIFERENTES nos dois lados da fronteira, e as duas
|
|
* deixaram de concordar. No navegador ela lê `window.__PUBLIC_ENV__`, que desde
|
|
* a onda do logo carrega a marca RESOLVIDA (banco acima do `.env`). No servidor
|
|
* — inclusive no SSR de um client component, onde `window` não existe — ela lê
|
|
* `process.env`, que é só o `.env`. Numa instalação com logo gravado pela tela e
|
|
* `APP_LOGO_URL` vazio (o caso normal de quem sobe logo pela tela), o SSR
|
|
* renderizava o nome em `<span>` e o cliente hidratava um `<img>`: troca de tipo
|
|
* de elemento, React #418 em toda tela, árvore descartada e regerada.
|
|
*
|
|
* No SERVIDOR o comportamento é correto e é DELIBERADO: o texto sob o "Entrar"
|
|
* sai daqui (o `.env`) enquanto o título da aba sai do banco, e
|
|
* `tests/e2e/icone-da-marca.spec.ts:64-77` cruza as duas resoluções de propósito
|
|
* — é o que faz "trocar o nome pela tela e a aba não acompanhar" reprovar. Por
|
|
* isso o defeito se fecha tirando os client components daqui, e não mudando o
|
|
* que esta função devolve.
|
|
*
|
|
* O ramo do navegador continua de pé porque a alternativa é pior: sem ele, um
|
|
* client component que voltasse a chamar `branding()` cairia no padrão do
|
|
* produto nos DOIS lados e mostraria a marca errada em SILÊNCIO. Divergir é
|
|
* barulhento (o console acusa), e barulhento é o modo de falhar que se conserta.
|
|
*/
|
|
/**
|
|
* A marca em vigor é a do PRODUTO — e é só então que o símbolo e o logotipo
|
|
* de `lib/branding/desenho.ts` podem aparecer.
|
|
*
|
|
* Duas condições, e as duas são necessárias: sem logo configurado E com o nome
|
|
* padrão. Quem só trocou o nome (para "Acme CRM") não pode receber um logotipo
|
|
* que soletra outro nome; quem só subiu um logo já tem o dele na tela. Trocar a
|
|
* cor de destaque não conta — a marca do produto continua sendo a que está
|
|
* escrita, só pintada de outro jeito.
|
|
*/
|
|
export function marcaEhADoProduto(marca: Pick<Branding, "name" | "logoUrl">): boolean {
|
|
return marca.logoUrl === null && marca.name === DEFAULT_APP_NAME;
|
|
}
|
|
|
|
export function branding(): Branding {
|
|
if (typeof window !== "undefined") {
|
|
const runtime = window.__PUBLIC_ENV__;
|
|
return resolveBranding(runtime?.APP_NAME, runtime?.APP_LOGO_URL);
|
|
}
|
|
return resolveBranding(process.env.APP_NAME, process.env.APP_LOGO_URL);
|
|
}
|