npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ocahub/kit

v0.3.0

Published

Módulo Nuxt da plataforma: páginas e componentes da loja (prateleira e detalhe do produto) ligados aos endpoints públicos do sales-api

Readme

rhfactor-kit

Módulo Nuxt 4.5 da plataforma. Com uma linha de configuração (o id da loja), o site do cliente ganha as páginas da loja já funcionando, e cada pedaço delas pode ser trocado.

Stack: Nuxt 4.5 · @nuxt/module-builder · TypeScript · CSS puro (sem framework)


O que o módulo entrega

| | | |---|---| | Páginas injetadas | / prateleira (todos os produtos ativos na loja, 24 por página, ?page=) · /produto/{slug} detalhe · /carrinho · conta do cliente: /login, /cadastro, /cadastro/dados, /termos, /minha-conta, /confirmar-email · checkout e pedidos (00017): /carrinho/checkout (identificação), /carrinho/checkout/frete (entrega), /compras/{codigo}/pagamento, /compras/{codigo}, /minha-conta/pedidos, /minha-conta/enderecos · compra coletiva (00018): /rodadas/{rodada} (hotsite) e /rodadas/{rodada}/{produto} | | Componentes | RhProductGrid → RhProductCard · RhProductGallery (troca e zoom) · RhVariantSelector · RhProductProperties · RhProductDescription · RhAddToCartButton · compra coletiva: RhCompraColetiva (destaque da home) → RhRoundCard, RhOfferCard, RhReserveButton, RhLotProgress (caixa compartilhada) · RhPrice · RhImage · RhPagination · RhStoreHeader · RhStoreFooter · RhCartButton · RhAccountButton | | Composables | useRhStore() · useRhProducts(page) · useRhProduct(slug) · useRhFormat() · useRhLinks() · useRhCart() (store Pinia do carrinho) · useRhHomeRounds(), useRhRound(slug), useRhRoundProduct(slug, produto) · useRhAccount() (sessão e cadastro do cliente) | | Rotas de servidor | /api/rhfactor/stores/{id}, …/products, …/products/{slug}, …/cart, …/account/*: o navegador nunca fala com o sales-api, o IAM ou o contract-management | | Dados no build | nome, descrição, logotipo e formato das imagens da loja entram no app.config |

RhShelfPage                       RhProductPage
 └─ RhProductGrid                  ├─ RhProductGallery   (miniaturas, zoom no mouse, tela cheia, arrastar no celular)
     └─ RhProductCard              ├─ RhPrice            (preço do SKU escolhido)
         ├─ RhImage                ├─ RhVariantSelector  (cor, tamanho, peso...; indisponível riscado)
         └─ RhPrice                ├─ RhAddToCartButton  (quantidade + hook do carrinho)
 └─ RhPagination                   ├─ RhProductDescription
                                   └─ RhProductProperties

Uso

// nuxt.config.ts do site
export default defineNuxtConfig({
  modules: ['@ocahub/kit'],
  rhfactor: {
    storeId: process.env.RHFACTOR_STORE_ID,    // painel > Minha loja
    apiBase: process.env.RHFACTOR_API_BASE     // sales-api visto pelo servidor
  }
})

| Opção | Padrão | | |---|---|---| | storeId | RHFACTOR_STORE_ID | id da loja; sem ele, as páginas respondem 503 "loja não configurada" | | apiBase | RHFACTOR_API_BASE ou http://localhost:8085 | só servidor; em execução, NUXT_RHFACTOR_API_BASE troca sem novo build | | iamBase | RHFACTOR_IAM_BASE ou http://localhost:8080 | IAM (cadastro e login do cliente); em execução, NUXT_RHFACTOR_IAM_BASE | | contractsBase | RHFACTOR_CONTRACTS_BASE ou http://localhost:8082 | contract-management (termos da loja); em execução, NUXT_RHFACTOR_CONTRACTS_BASE | | paymentsBase | RHFACTOR_PAYMENTS_BASE ou http://localhost:8089 | payments-service (meios de pagamento da loja no checkout); em execução, NUXT_RHFACTOR_PAYMENTS_BASE | | cacheSeconds | 60 | cache stale-while-revalidate das rotas /api/rhfactor/**; 0 desliga | | pages | true | false desliga todas; { shelf: false }, { product: '/p' }, { login: '/entrar' }... | | css | true | false tira o tema padrão (estilize as classes .rh-* do zero) | | prefetchStore | true | busca os dados da loja no build |

Personalizar, do mais leve ao mais profundo

  1. Tokens — sobrescreva no CSS do site: --rh-color-accent, --rh-font, --rh-radius, --rh-grid-min... (lista em src/runtime/assets/rhfactor.css).
  2. Dados da loja só no site — app.config.ts: rhfactor: { store: { logoUrl: '…' } }.
  3. Um componente — crie no site um componente com o mesmo nome (ex.: app/components/RhProductCard.vue); o do site tem prioridade.
  4. Um pedaço da grade — <RhProductGrid :products><template #item="{ product }">….
  5. A página inteira — crie app/pages/index.vue ou app/pages/produto/[slug].vue: a página do site sempre vence a do kit. Monte com os composables e componentes.

Carrinho

  • Página /carrinho (RhCartPage): itens com o preço da loja agora, a quantidade (no box, de caixa em caixa), remover e o resumo. "Finalizar compra" leva ao /carrinho/checkout, que pede conta.
  • RhCartButton: a sacola com o contador, para o cabeçalho.
  • RhAddToCartButton: grava no carrinho e soma ao que já está lá. Quando a loja recusa (estoque), mostra a mensagem dela.
  • Estado na tela: um store Pinia (useRhCart(), instalado pelo kit). O id e o segredo do carrinho não ficam nele: ficam num cookie httpOnly por loja (rh_cart_*, 30 dias).
  • Quem fala com o sales-api: as rotas /api/rhfactor/stores/{id}/cart (GET, e PUT/DELETE de cart/items/{sku}), sem cache. O carrinho só é criado no primeiro item.
  • Hook rhfactor:cart:add: continua disparando a cada adição, para o site avisar.
export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.hook('rhfactor:cart:add', item => { /* ... */ })
})

Checkout e pedidos (00017)

/carrinho ─▶ /carrinho/checkout ─▶ /carrinho/checkout/frete ─▶ /compras/{codigo}/pagamento ─▶ payments.ocahub.com.br/{transação} ─▶ /compras/{codigo}
              identificação        endereço + entrega           escolhe o meio                 paga no gateway                       acompanha
  • Entrega (RhCheckoutDeliveryPage): endereço do caderno (IAM) ou novo — o CEP preenche o resto (ViaCEP, do navegador). Com itens de prazos de preparo diferentes, o cliente escolhe tudo junto ou separado (um pacote por prazo); cada pacote tem as opções cotadas (transportadoras + retirada na loja). O valor do frete vem da cotação guardada no sales-api, nunca do navegador.
  • Fechar pedido congela itens, preços e entrega; o carrinho vira pedido.
  • Pagamento (RhPurchasePaymentPage): pede e-mail confirmado (409 EMAIL_NOT_VERIFIED). A volta do pagamento é montada pelo servidor do site.
  • Compra (RhPurchasePage): andamento, entregas com rastreio, pagamentos, histórico; "pagar de novo" e "cancelar" enquanto não pago. Voltando do pagamento, confere a situação por alguns instantes.
  • Rotas de servidor: /api/rhfactor/stores/{id}/checkout, …/checkout/freight-quotes, …/checkout/orders, …/purchases[/{codigo}[/payments|/cancel]], …/payment-methods, …/account/addresses[/{id}]. Todas pedem a sessão do cliente.
  • Componentes: RhCheckoutSteps, RhAddressForm, RhOrderStatusBadge, RhOrderProgress.

Conta do cliente

O cliente é um usuário da plataforma com o papel CLIENT na tenant da loja. A sessão do IAM fica em cookies httpOnly por loja (rh_at_*, rh_rt_*); o script da página nunca vê os tokens. Depois de todo login, a mesma pergunta:

/minha-conta ou /carrinho/checkout  ──(sem sessão)──▶ /login?volta=…  ──▶  falta nome e CPF? /cadastro/dados
                                                                  falta aceitar os termos da loja? /termos
                                                                  nada? volta para onde ia
  • Cadastro (/cadastro → /cadastro/dados → /termos): e-mail e senha (mínimo de 12 caracteres), depois nome e CPF, depois os termos de uso da loja. O e-mail de boas-vindas leva o link /confirmar-email; confirmar não bloqueia nada agora (o pagamento vai pedir).
  • CPF que já tem conta: o cadastro recém-começado é descartado e a tela mostra o e-mail daquela conta, mascarado, para a pessoa entrar com ela.
  • E-mail que já tem conta (cliente de outra loja, ou dona da loja): entra com ela; o login pelo site a associa à tenant como cliente.
  • Termos de uso: o contrato que a loja escolheu no painel. Versão nova publicada, o cliente aceita de novo no próximo login.
  • Carrinho: sem pendências, o carrinho do cookie passa a ser do cliente (ou é juntado ao que ele já tinha nesta loja). Daí em diante quem abre o carrinho é a sessão; ao sair da conta, o carrinho sai do navegador.
  • Páginas que pedem conta: o middleware rh-account (ligado em /minha-conta, o checkout e as compras). Numa página própria do site: definePageMeta({ middleware: 'rh-account' }).

Compra coletiva (00018)

A compra coletiva não é uma forma de vender o produto na loja: é uma feature da loja (o plano libera, a loja liga em Loja › Features no painel) que acontece em rodadas — páginas temáticas com início e fim ("Tommy Hilfiger esta semana"). Cada produto da rodada (sempre sem variação) tem o preço da rodada, a reserva por unidade e o tamanho da caixa compartilhada.

  • <RhCompraColetiva /> lista as rodadas abertas com a capa (8:3). A prateleira padrão já o põe no topo da primeira página. Ele aparece só com a feature ligada e "exibir rodadas na home" marcada; always ignora essa opção.
  • /rodadas/{rodada}: hotsite com a capa, o tema, até quando vai e os produtos, cada um com RhLotProgress (caixas fechadas, "7 de 10", "faltam 3").
  • /rodadas/{rodada}/{produto}: galeria, preço da rodada, reserva e o RhReserveButton. A reserva entra no carrinho como outra linha (com offerId) e vale o preço da reserva; no checkout o cliente paga as reservas e o frete (calculado sobre o produto final), num pedido de reserva próprio. O restante é cobrado no fechamento da rodada.

Dados

Vêm dos endpoints públicos do sales-api (/public/v1/stores/{id}, ver README do sales-api). Aparece o produto ativo na loja, com status ativo e ao menos um SKU ativo; o preço é o da loja. Sem saldo de estoque: o site só sabe se o SKU está disponível, se é sob encomenda e o prazo. Loja de tenant sem plano de vendas responde 404, e o site sai do ar com a assinatura.

As imagens vêm no formato da loja (quadrado 1:1 ou retrato 3:4): thumb (300) na grade, full (1200) no detalhe, original no zoom de tela cheia.

Desenvolvendo o kit: a loja teste

O playground é uma loja teste na stack de desenvolvimento (dev/, com ./scripts/stack.sh dev no ar):

npm install
npm run dev:prepare
npm run loja-teste      # cria/atualiza a "Loja Teste" e grava RHFACTOR_STORE_ID em playground/.env
npm run dev             # http://localhost:3012

O loja-teste usa uma tenant sintética (loja-teste, só no sales-api e no files-service, sem cadastro no core-api) e cria pela API de verdade:

  • 15 produtos: 6 cafés em grade peso × moagem, 3 microlotes (sem variação, só na rodada) e 6 utensílios, alguns com cor;
  • estoque com pronta entrega, esgotado e sob encomenda, e um produto sem foto;
  • compra coletiva ligada (com destaque na home) e a "Rodada Chapada Diamantina", aberta por 7 dias a cada execução: caixa de 10 com 57 reservados (5 fechadas, faltam 3), caixa de 6 com 2 e caixa de 12 com 17. As reservas de exemplo vão direto no contador das ofertas (round_offers.reserved) do banco de dev;
  • 1 a 3 fotos PNG geradas na hora, processadas pelo files-service.

O plano da tenant entra pelo evento de assinatura no Kafka do container, e o token é assinado com a chave de desenvolvimento do IAM: só funciona na stack local. O script pode rodar de novo; ele reaproveita o que já existe.

Loja de uma tenant de verdade (a que o store-whitelabel mostra na porta 3003): npm run loja-real monta o mesmo catálogo entrando com o login da dona (LOJA_EMAIL / LOJA_SENHA; padrão [email protected], a tenant "Café Boa Vista" criada antes com npm run tenant:loja no release-check). O id da loja vai para store-whitelabel/.env.

Os dois scripts gravam também o id no dev/.env (KIT_PLAYGROUND_STORE_ID e WHITELABEL_STORE_ID), de onde o docker compose sobe os sites:

docker compose --profile loja up -d --build kit-playground store-whitelabel
#   http://localhost:3002  playground do kit (Dockerfile.playground, compilado do src/)
#   http://localhost:3003  store-whitelabel

Sem a stack, use o sales-api simulado (qualquer id de loja responde):

npm run mock                                               # porta 8185
RHFACTOR_API_BASE=http://localhost:8185 RHFACTOR_STORE_ID=qualquer npm run dev

Ele tem 26 produtos, com os mesmos casos. FORMAT=PORTRAIT simula uma loja com fotos 3:4.

npm run build && npm run typecheck

O site consome o dist/ (não o src/): depois de mudar o kit, rode npm run kit:sync no store-whitelabel.