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

@meterbox/customer-portal

v0.1.16

Published

Drop-in multi-page React customer portal for Meterbox: Account, Plans, Credits. Built on @meterbox/react.

Readme

@meterbox/customer-portal

Drop-in multi-page React customer portal for Meterbox. One component, three pages (Account, Plans, Credits), composed from the widgets shipped in @meterbox/react. All data is fetched via the /v1/customer-portal/* endpoints using a short-lived customer-scoped JWT that your backend mints with POST /v1/tokens/:id, so no API key ever reaches the browser. The portal works from any origin (the endpoints answer CORS for any site), and a token only works in the environment it was minted in: mint with a sandbox API key to show sandbox data.

npm install @meterbox/customer-portal react react-dom
import CustomerPortal from "@meterbox/customer-portal";

function handlePlanSelect(plan, billingInterval) {
  // Fires alongside the built-in preview + apply/schedule flow below.
  // Optional, e.g. for your own analytics. You don't need to build a
  // checkout redirect for a monthly pick; that's what the Plans page
  // already does. An annual pick still has no in-place apply path (see
  // below), so route those through your own <CheckoutButton> instead.
}

<CustomerPortal
  customerId="cus_42"
  customerToken={customerJwt}
  initialPage="account"
  locale="en"
  onPlanSelect={handlePlanSelect}
  onTopUp={(balance) => /* mint top-up session */}
/>

It calls https://meterbox.ai. To point it elsewhere, wrap it in <MeterboxProvider baseUrl="…"> from @meterbox/react, or pass it baseUrl.

Deep links

initialAction opens the portal on an action rather than a page, for links like "cancel" or "upgrade to Pro" in your emails or app:

<CustomerPortal {...props} initialAction={{ type: "cancel" }} />
<CustomerPortal {...props} initialAction={{ type: "change_plan", planId: "pro" }} />

cancel opens the cancel flow on the Account page, at its "Why are you cancelling?" step. change_plan opens the Plans page with that plan picked: its proration preview and the "Change now" / "Schedule for end of period" buttons. A plan that's the customer's current one, or that isn't published, picks nothing. The action applies once; moving to another tab drops it.

Language

locale shows the portal in your customer's language, with dates and amounts formatted for it: English (the default), Spanish (es), French (fr) and German (de) are built in. messages overrides any string by its key, or translates them all for another language (anything missing stays in English):

<CustomerPortal {...props} locale={navigator.language} />
<CustomerPortal {...props} locale="it" messages={{ "portal.account": "Conto" }} />

The keys are listed in @meterbox/react's src/i18n.js. Plan names, features and retention offers are shown as you entered them.

Branding

The portal takes your branding from Settings → Branding (GET /v1/customer-portal/:id/branding): your logo and company name in its header, and your colors, font and buttons on every page. Pass branding to override it.

Pages

  • Account: name, email, current plan, status (GET /v1/customer-portal/:id/me), a button to update the payment method and billing details on Stripe's own portal (<BillingDetailsButton>, when available), and Cancel subscription: the customer gives a reason, sees your plan's retention offers (a discount, a longer trial, or a downgrade) and either takes one or cancels at the end of the period, keeping access until then (/v1/customer-portal/:id/cancellation). Also exported on its own as <CancelSubscription>. Each step reaches you as a webhook (customer.cancel.initiated, customer.win-back.accepted, customer.cancel.confirmed). Below that, the customer's invoices, each with its PDF and payment page (<InvoiceHistory>, GET /v1/customer-portal/:id/invoices).
  • Plans: the published plans (GET /v1/customer-portal/:id/plans) in a <PricingTable> (monthly/annual toggle and each plan's model rates included) + inline <PlanChangeCard> for a monthly pick: proration preview plus "Change now" / "Schedule for end of period" actions, so picking a plan actually applies it, not just previews it. onPlanSelect fires with (plan, billingInterval) either way. The inline card only covers monthly: subscription/preview and subscription/change don't prorate an annual commitment yet, so an annual pick shows a short note instead of previewing/applying a misleading number; annual stays on the checkout path (a new Stripe Checkout session with its own annual price).
  • Credits: <CreditBalance> + <CreditGrantsTable> + <CreditUtilization>

No router dependency. Page state is internal; wire deep-linking yourself via initialPage if you want browser-history navigation.