Você escolhe o lugar, a gente preenche
Não existe barra flutuante. Nada é injetado no seu layout, e não há opção que faça isso. Você renderiza o componente onde a sua página tem espaço:@userkit/nextjs — é o mesmo componente, e em
modo proxy a leitura e a dispensa são
encaminhadas pelos handlers como o resto da superfície do contato, com a sessão num
cookie httpOnly da sua própria origem.
O banner declara o mesmo slot no painel, e os dois se encontram por string
exata. Letras minúsculas, números, hífen e underscore — um nome com maiúscula ou
espaço é recusado em vez de normalizado, porque essa string é escrita duas
vezes em dois lugares diferentes e maiúscula é como isso dá errado sem ninguém ver.
O motivo não é gosto. Um widget que colocasse uma faixa fixa na viewport cobriria o
cabeçalho de todo produto que já tem um, em produção, no instante em que alguém
salvasse uma linha num painel. Um slot é um lugar que o dono da página declarou ter
espaço, e é o único tipo de lugar em que um terceiro pode desenhar.
Nada verifica se o seu app ainda renderiza um slot — não vemos o seu código, e um
aviso baseado em palpite seria pior que aviso nenhum. O painel oferece os slots que
os seus próprios banners já usam.
Um banner por slot
Um slot mostra um banner de cada vez. Quando há mais de um candidato, ganha o de maiorpriority, e empate vai para o mais novo.
Isso é decidido no banco, na mesma instrução que decide todo o resto, então a
resposta é idêntica para o componente React, para um fetch de um app mobile e para
a prévia do painel. Um cliente que escolhesse o próprio vencedor seria uma segunda
opinião que ninguém pediu.
Dois tipos
text é um título, um texto embaixo, um ícone opcional e um botão opcional. O
widget desenha com a sua paleta — suas cores, seu raio, sua fonte — para parecer
parte do produto e não algo alugado.
image é uma arte sua, opcionalmente com link. Não tem título por construção: uma
chamada por cima de uma imagem desenhada com a própria chamada são duas chamadas.
O arquivo é hospedado por você; a API recebe uma URL http(s) absoluta e não
guarda mais nada.
Para onde qualquer um dos dois leva é o cta_url — o destino do botão no
banner de texto, o link da figura inteira no de imagem. Ele aceita um caminho
começando em / (/planos) ou um endereço http(s) completo, as mesmas duas
formas que uma notificação no produto aceita — e o
caminho é, se tanto, a metade mais natural aqui: um banner é desenhado dentro do
seu próprio produto. Um esquema que executa é recusado, e a forma relativa a
protocolo //host/caminho também: ela se lê como caminho e nomeia outro host. O
image_url é o que continua absoluto — um <img src> relativo resolveria contra
qualquer página em que o widget for desenhado.
O tipo não muda depois de criado. Os dois preenchem colunas diferentes, e o
banco recusa uma linha que carregue as duas — trocar apagaria o que alguém
escreveu, sem volta. Crie um banner do outro tipo, o que também mantém a conta de
dispensas presa à coisa que as pessoas realmente viram.
body é texto puro, não markdown. Um banner que precisa de formatação é um
post de changelog com um botão apontando para ele.
Quando aparece
Três coisas decidem, e elas respondem perguntas diferentes:active nasce false: um banner existe enquanto o texto está sendo escrito, e
criar um nunca coloca nada na frente de ninguém. É também o botão de desligar, e é
por ele que se tira do ar uma faixa da qual você se arrependeu e que já está na
tela de alguém — vale na próxima leitura do widget, não numa expiração de cache.
ends_at é o que tira a faixa da promoção do ar na segunda sem ninguém lembrar. É
o campo mais valioso daqui e o que mais fica vazio.
Os dois limites são RFC 3339 com fuso, comparados com o relógio da própria API. Uma
data seca seria um instante no fuso que o leitor imaginou, então “a promoção acaba
à meia-noite” encerraria na sexta para uns clientes e no sábado para outros.
Quem vê
segment_id é para quem ele é, ou null para todo mundo — o mesmo
segmento que as suas feature flags, pesquisas e posts de
changelog usam, nunca um filtro próprio.
Visitante anônimo não é um caso especial: ele tem um contato como qualquer
outro, então vê os banners mirados em todo mundo. Isso é metade do motivo deste
módulo existir — o aviso de manutenção na tela de entrada do seu produto.
O público nunca viaja até o leitor. “Este banner é mirado num segmento” é a sua
própria análise dos seus usuários, e quem está olhando não é para quem essa
informação é.
Dispensar
kind, nunca por um
campo estar vazio: só os campos do tipo viajam, e é isso que mantém “uma imagem sem
descrição” distinguível de “isto não é uma imagem”.
A dispensa pertence à pessoa e a acompanha em todos os aparelhos. É idempotente, e
dispensar um banner que não existe, que é de outra organização ou que já estava
dispensado responde o mesmo 204 — um 404 aqui diria a quem tem a chave
publicável se um uuid existe.
dismissible: false é para os avisos que são estado do produto e não notícia:
“esta conta fica somente leitura até você confirmar o e-mail”. Esconder isso
esconderia a explicação do que a pessoa está prestes a encontrar. Dispensar
continua sendo registrado e depois ignorado, então nenhum cliente precisa conhecer
a regra para evitar um erro — e um banner que você tornar dispensável depois já
está fechado para quem tentou.
Religar um banner não o mostra de novo para quem já fechou. Dizer a mesma coisa de
novo é um banner novo, o que também é o que faz “quantas pessoas fecharam este
aqui” ser um número sobre um aviso só.
Alcance
A lista mostra quatro números ao lado de cada banner, dos últimos 30 dias:
Um painel que alguém abre onze vezes por dia é onze exibições e um leitor, e o par é
justamente o ponto: citar o primeiro como alcance é como um banner parece um sucesso
para ninguém.
viewers conta todo contato distinto, visitante anônimo incluído,
porque o banner é desenhado para ele também — deixar de fora quem ainda não entrou
seria deixar de fora exatamente o público a que um aviso costuma se dirigir.
Uma exibição é a faixa CRUZAR a dobra, não o componente renderizar. Um banner
abaixo da dobra é desenhado a cada carga de página e visto por ninguém, então o
<Banner /> reporta a partir de um IntersectionObserver a 50% de visibilidade, uma
vez por banner por carga de página — por mais vezes que uma SPA remonte o componente.
Os números são null, nunca 0, quando o analytics não pôde ser lido: zero diria
que o banner não alcançou ninguém. O engagement.available da resposta diz qual dos
dois é.
Isso são os seus próprios eventos de analytics: banner.shown e banner.clicked,
escritos pelo widget pela mesma porta que os seus track() usam, carregando
banner_id, slot e kind — e nenhum texto. Eles aparecem
no explorador como qualquer outro evento, que é onde mora
o histórico sem limite: os 30 dias do painel são uma janela, e um banner que rodou uma
semana em março ficaria com cara de fracasso conforme os zeros de hoje se acumulassem.
Não existe tabela de impressões do nosso lado, e isso é deliberado: um contador
gravado a cada carga de página pertence ao lugar onde todo contador de carga de
página já vai, com o lote e a política de abuso que aquele endpoint já tem. O clique é
contado ao lado do seu link e não por um redirecionamento nosso — passar todos os seus
botões por esta API faria eles pararem de funcionar quando a gente parasse. A troca é
dita e não escondida: um clique que navega antes da fila esvaziar é um clique que
ninguém contou, o que é um número um pouco baixo em vez do seu produto quebrado
durante um incidente nosso.
O que não existe aqui
E-mail. Um banner é desenhado dentro do seu produto e em nenhum outro lugar. O que se envia é campanha.Permissões
engagement:manage — owner e admin por padrão, a mesma do onboarding, do changelog
e das pesquisas. Leitura incluída: o que você diz para os seus próprios usuários é
cópia sua, e nenhuma permissão mais larga mostra isso.