Nasaq

Rates and Subscriptions

Effective-dated bill and cost rates, per-project recurring subscriptions with month-end clamping, and an organisation billing overview.

PreviewOpen ↗

Code

<RateSchedule rates={rates} currency="USD" onAdd={async ({ amount, from }) => api.addRate(amount, from)} /><RecurringSubscriptions subscriptions={subs} currency="USD" onSave={save} onStatusChange={setStatus} /><BillingOverview subscriptions={subs} currency="USD" />

Billing · beta

Live examples and controls: Rates and Subscriptions in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/rates-subscriptions.json

Rates that change over time without repricing old work, subscriptions that renew on a cycle or a cron schedule (per project or for the whole organisation), and an overview of what is billed each month and what is due soon. Money is integer minor units and every division rounds half up.

When to use

  • Agencies and studios pricing time by the hour, and tracking recurring costs or revenue.

When not to use

  • Choosing a plan on a pricing page: use PlanCard.
  • Metered usage: use UsageMeter.

Import

import { BillingOverview, RateSchedule, RecurringSubscriptions } from "@fadymondy/nasaq/web";

Quick start

<RateSchedule rates={rates} currency="USD" onAdd={async ({ amount, from }) => api.addRate(amount, from)} />
<RecurringSubscriptions subscriptions={subs} currency="USD" onSave={save} onStatusChange={setStatus} />
<BillingOverview subscriptions={subs} currency="USD" />

Anatomy

RateSchedule             data-slot="rate-schedule"             current rate, margin, history with changes, Add dialog
RecurringSubscriptions   data-slot="recurring-subscriptions"   list, editor dialog (cycle or cron), cancel dialog
BillingOverview          data-slot="billing-overview"          stat cards, split by project, upcoming charges

API

Read the exported prop types in rates-subscriptions.tsx for the exact fields. In short:

  • RateSchedule: rates (id, amount, from), currency, optional today, marginAgainst, onAdd, onRemove, loading, labels. A rate holds until the next one starts. Omit onAdd for read only. Remove is in the row's context menu.
  • RecurringSubscriptions: subscriptions, currency, optional projects, today, onSave(input, id?), onStatusChange, loading, labels. The schedule is a cycle (every and unit) or a cron expression. Pause, resume, edit and cancel are in each row's context menu.
  • BillingOverview: subscriptions, currency, optional today, loading, labels. Only active subscriptions count.

Pure helpers: rateAt, sortRates, rateSegments, checkRate, amountForWork, marginBps, nextOccurrences, cycleAround, prorate, cycleMonthlyEquivalent, monthlyRecurring. nextOccurrences counts months from the anchor, so 31 January gives 28 February then 31 March.

Examples

Price tracked time

amountForWork([{ day: "2026-03-30", minutes: 90 }], rates); // each entry uses the rate in force that day

Prorate a mid-cycle change

prorate(3000, "2026-09-01", "2026-10-01", "2026-09-16"); // 1500

Accessibility

KeyAction
TabMove through buttons, rows and dialog fields.
EnterSubmit the open dialog.
EscapeClose the dialog (not while busy).
Shift+F10 or MenuOpen a row's actions.
  • Rate changes are a sign, an arrow and text, not colour alone.
  • Dialog problems use role="alert".

RTL & i18n

Built-in English and Arabic, and a labels prop to override. Cron descriptions follow the locale. Amounts and dates use Intl.

Styling & tokens

Card, surface, border, primary and status tokens only. Target the data-slot values above and extend with className. No raw hex.

Do / Don't

  • Do store dates as day keys, not timestamps.
  • Do keep old rates: a change adds a rate, it does not edit history.
  • Don't compute money in floats.
  • cron-builder, usage-meter, stat-card

Lab

Storybook: Components / Commerce / Rates and Subscriptions.

On this page