@locusgraph/blocks 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +11 -11
  2. package/dist/billing/plan-price.d.ts +4 -0
  3. package/dist/billing/plan-price.js +9 -0
  4. package/dist/billing/{plan-options.d.ts → plans.d.ts} +2 -2
  5. package/dist/billing/plans.js +9 -0
  6. package/dist/billing/top-up.d.ts +11 -0
  7. package/dist/billing/top-up.js +12 -0
  8. package/dist/billing/wallet.d.ts +8 -0
  9. package/dist/billing/wallet.js +19 -0
  10. package/dist/index.d.ts +13 -8
  11. package/dist/index.js +13 -8
  12. package/dist/keys/{no-keys.d.ts → keys-empty.d.ts} +2 -2
  13. package/dist/keys/keys-empty.js +8 -0
  14. package/dist/keys/{keys-table.d.ts → keys.d.ts} +2 -2
  15. package/dist/keys/{keys-table.js → keys.js} +2 -2
  16. package/dist/skeletons/keys-skeleton.d.ts +6 -0
  17. package/dist/skeletons/keys-skeleton.js +18 -0
  18. package/dist/skeletons/plans-skeleton.d.ts +7 -0
  19. package/dist/skeletons/plans-skeleton.js +9 -0
  20. package/dist/skeletons/top-up-skeleton.d.ts +5 -0
  21. package/dist/skeletons/top-up-skeleton.js +8 -0
  22. package/dist/skeletons/usage-skeleton.d.ts +6 -0
  23. package/dist/skeletons/usage-skeleton.js +9 -0
  24. package/dist/skeletons/wallet-skeleton.d.ts +5 -0
  25. package/dist/skeletons/wallet-skeleton.js +6 -0
  26. package/dist/types.d.ts +5 -1
  27. package/dist/usage/quota.d.ts +4 -0
  28. package/dist/usage/quota.js +24 -0
  29. package/dist/usage/usage-alert.d.ts +11 -0
  30. package/dist/usage/usage-alert.js +29 -0
  31. package/dist/usage/usage.d.ts +8 -0
  32. package/dist/usage/{usage-summary.js → usage.js} +3 -3
  33. package/package.json +5 -5
  34. package/dist/billing/credit-packs.d.ts +0 -9
  35. package/dist/billing/credit-packs.js +0 -6
  36. package/dist/billing/plan-options.js +0 -8
  37. package/dist/billing/wallet-panel.d.ts +0 -8
  38. package/dist/billing/wallet-panel.js +0 -16
  39. package/dist/keys/no-keys.js +0 -8
  40. package/dist/usage/quota-meter.d.ts +0 -4
  41. package/dist/usage/quota-meter.js +0 -13
  42. package/dist/usage/usage-attention.d.ts +0 -11
  43. package/dist/usage/usage-attention.js +0 -20
  44. package/dist/usage/usage-summary.d.ts +0 -8
package/README.md CHANGED
@@ -13,22 +13,22 @@ pnpm add @locusgraph/blocks @locusgraph/components
13
13
 
14
14
  | Block | Props |
15
15
  |---|---|
16
- | `UsageSummary` | `quotas`, optional `label`, `emptyText` |
17
- | `QuotaMeter` | `quota` (`key`, `period`, `used`, `included`) |
18
- | `UsageAttention` | `balanceMicroUsd`, `quotas`, `paidPlan`, `buyCreditsHref`, `upgradeHref`, `linkComponent`. Renders nothing when credits are fine and no quota is over |
19
- | `WalletPanel` | `balanceMicroUsd`, `debtMicroUsd`, `grants`. A Rule Grid cell |
20
- | `CreditPacks` | `packs`, `onBuy(packId)`, `pending`. A Rule Grid cell |
21
- | `PlanOptions` | `plans`, `actionFor(plan)` for plans that are not current |
22
- | `KeysTable` | `keys`, `actions(key)`, `highlightedId` to flash a just-created key. Dates render in the viewer's time zone after hydration |
16
+ | `Usage` | `quotas`, optional `label`, `emptyText` |
17
+ | `Quota` | `quota`, a `QuotaData` (`key`, `period`, `used`, `included`) |
18
+ | `UsageAlert` | `balanceMicroUsd`, `quotas`, `paidPlan`, `buyCreditsHref`, `upgradeHref`, `linkComponent`. Renders nothing when credits are fine and no quota is over |
19
+ | `Wallet` | `balanceMicroUsd`, `debtMicroUsd`, `grants`. A Rule Grid cell |
20
+ | `TopUp` | `packs`, `onBuy(packId)`, `pending`. A Rule Grid cell |
21
+ | `Plans` | `plans`, `actionFor(plan)` for plans that are not current |
22
+ | `Keys` | `keys`, `actions(key)`, `highlightedId` to flash a just-created key. Dates render in the viewer's time zone after hydration |
23
23
  | `LocalDate` | `value` (ISO string): UTC on the server, the viewer's time zone after hydration |
24
- | `NoKeys` | `newKeyHref` (omit for read-only roles), `linkComponent` |
24
+ | `KeysEmpty` | `newKeyHref` (omit for read-only roles), `linkComponent` |
25
25
 
26
- `WalletPanel` and `CreditPacks` are cells, so put them in a `RuleGrid`:
26
+ `Wallet` and `TopUp` are cells, so put them in a `RuleGrid`:
27
27
 
28
28
  ```tsx
29
29
  <RuleGrid columns={2}>
30
- <WalletPanel balanceMicroUsd={wallet.balanceMicroUsd} grants={grants} />
31
- <CreditPacks packs={wallet.packs} onBuy={buy} pending={pending} />
30
+ <Wallet balanceMicroUsd={wallet.balanceMicroUsd} grants={grants} />
31
+ <TopUp packs={wallet.packs} onBuy={buy} pending={pending} />
32
32
  </RuleGrid>
33
33
  ```
34
34
 
@@ -0,0 +1,4 @@
1
+ import type { PlanOffer } from "../types.js";
2
+ export declare function PlanPrice({ plan }: {
3
+ plan: PlanOffer;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { formatCents } from "@locusgraph/utils";
3
+ const intervalLabel = { month: "/month", year: "/year" };
4
+ export function PlanPrice({ plan }) {
5
+ if (plan.amountCents === undefined) {
6
+ return (_jsx("span", { className: "font-heading text-xl font-semibold tracking-[-0.02em] tabular-nums", children: plan.price }));
7
+ }
8
+ return (_jsxs("span", { className: "flex items-baseline gap-1", children: [_jsx("span", { className: "font-heading text-2xl font-semibold tracking-[-0.02em] tabular-nums", children: formatCents(plan.amountCents) }), plan.interval && (_jsx("span", { className: "text-sm text-muted-foreground", children: intervalLabel[plan.interval] }))] }));
9
+ }
@@ -1,8 +1,8 @@
1
1
  import type * as React from "react";
2
2
  import type { PlanOffer } from "../types.js";
3
- type PlanOptionsProps = {
3
+ type PlansProps = {
4
4
  plans: PlanOffer[];
5
5
  actionFor?: (plan: PlanOffer) => React.ReactNode;
6
6
  };
7
- export declare function PlanOptions({ plans, actionFor }: PlanOptionsProps): React.JSX.Element;
7
+ export declare function Plans({ plans, actionFor }: PlansProps): React.JSX.Element;
8
8
  export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge, RuleGrid, RuleGridCell, RuleGridFooter, RuleGridTitle, } from "@locusgraph/components";
3
+ import { PlanPrice } from "./plan-price.js";
4
+ export function Plans({ plans, actionFor }) {
5
+ return (_jsx(RuleGrid, { columns: plans.length >= 3 ? 3 : 2, children: plans.map((plan) => {
6
+ const action = plan.current ? null : actionFor?.(plan);
7
+ return (_jsxs(RuleGridCell, { "data-current": plan.current || undefined, className: "gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs(RuleGridTitle, { className: "flex min-h-6 items-center gap-2", children: [plan.name, plan.current && _jsx(Badge, { variant: "secondary", children: "Current" })] }), _jsx(PlanPrice, { plan: plan })] }), _jsx("ul", { className: "flex flex-col gap-1.5 text-sm text-muted-foreground", children: plan.points.map((point) => (_jsx("li", { children: point }, point))) }), _jsx(RuleGridFooter, { className: "mt-auto", children: plan.current ? (_jsx("p", { className: "flex min-h-8 items-center text-sm text-muted-foreground", children: "Your plan" })) : (action) })] }, plan.name));
8
+ }) }));
9
+ }
@@ -0,0 +1,11 @@
1
+ import type { CreditPack } from "../types.js";
2
+ type TopUpProps = {
3
+ packs: CreditPack[];
4
+ onBuy: (packId: string) => void;
5
+ pending?: boolean;
6
+ pendingPackId?: string;
7
+ highlightedPackId?: string;
8
+ description?: string;
9
+ };
10
+ export declare function TopUp({ packs, onBuy, pending, pendingPackId, highlightedPackId, description, }: TopUpProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, RuleGridCell, RuleGridDescription, RuleGridLabel, Spinner, } from "@locusgraph/components";
3
+ import { centsToMicroUsd, formatCents, formatMicroUsd } from "@locusgraph/utils";
4
+ function hasBonus(pack) {
5
+ return centsToMicroUsd(pack.priceCents) !== pack.amountMicroUsd;
6
+ }
7
+ export function TopUp({ packs, onBuy, pending = false, pendingPackId, highlightedPackId, description = "Pay once with Stripe. Credits never expire and never renew on their own.", }) {
8
+ const busy = pending || pendingPackId !== undefined;
9
+ return (_jsxs(RuleGridCell, { className: "gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "flex min-h-6 items-center", children: _jsx(RuleGridLabel, { children: "Buy credits" }) }), _jsx(RuleGridDescription, { children: description })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: packs.map((pack) => (_jsxs(Button, { variant: pack.id === highlightedPackId ? "default" : "outline", disabled: busy, "aria-busy": pack.id === pendingPackId || undefined, onClick: () => onBuy(pack.id), children: [pack.id === pendingPackId && _jsx(Spinner, { "aria-hidden": "true", role: undefined }), _jsx("span", { className: "tabular-nums", children: formatMicroUsd(pack.amountMicroUsd) }), hasBonus(pack) && (_jsxs("span", { className: pack.id === highlightedPackId
10
+ ? "tabular-nums opacity-75"
11
+ : "font-medium text-good tabular-nums", children: ["for ", formatCents(pack.priceCents)] }))] }, pack.id))) })] }));
12
+ }
@@ -0,0 +1,8 @@
1
+ import type { WalletGrant } from "../types.js";
2
+ type WalletProps = {
3
+ balanceMicroUsd: number;
4
+ debtMicroUsd?: number;
5
+ grants: WalletGrant[];
6
+ };
7
+ export declare function Wallet({ balanceMicroUsd, debtMicroUsd, grants }: WalletProps): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge, RuleGridCell, RuleGridLabel } from "@locusgraph/components";
3
+ import { balanceLevel, formatDate, formatMicroUsd } from "@locusgraph/utils";
4
+ const levelHint = {
5
+ low: { badge: "Low", text: "Add credits before usage stops.", tone: "text-warn" },
6
+ empty: {
7
+ badge: "Empty",
8
+ text: "Paid usage is paused until you add credits.",
9
+ tone: "text-destructive",
10
+ },
11
+ };
12
+ function GrantList({ grants }) {
13
+ return (_jsx("ul", { className: "flex flex-col divide-y divide-line", children: grants.map((grant) => (_jsxs("li", { className: "flex justify-between gap-4 py-2", children: [_jsx("span", { children: grant.label }), _jsxs("span", { className: "flex items-baseline gap-2 tabular-nums", children: [_jsx("span", { className: "font-medium text-foreground", children: formatMicroUsd(grant.remainingMicroUsd) }), _jsx("span", { className: "text-muted-foreground", children: grant.expiresAt ? `expires ${formatDate(grant.expiresAt)}` : "never expires" })] })] }, `${grant.label}:${grant.expiresAt}`))) }));
14
+ }
15
+ export function Wallet({ balanceMicroUsd, debtMicroUsd = 0, grants }) {
16
+ const level = balanceLevel(balanceMicroUsd);
17
+ const hint = level === "fine" ? null : levelHint[level];
18
+ return (_jsxs(RuleGridCell, { "data-level": level, className: "gap-4", children: [_jsxs("span", { className: "flex min-h-6 items-center justify-between", children: [_jsx(RuleGridLabel, { children: "Prepaid credits" }), hint && (_jsx(Badge, { variant: level === "empty" ? "destructive" : "secondary", className: level === "low" ? "text-warn" : undefined, children: hint.badge }))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: `font-heading text-3xl font-semibold tracking-[-0.02em] tabular-nums ${hint?.tone ?? ""}`, children: formatMicroUsd(balanceMicroUsd) }), hint && _jsx("p", { className: "text-sm text-muted-foreground", children: hint.text })] }), _jsxs("div", { className: "flex flex-col gap-3 text-sm", children: [grants.length > 0 && _jsx(GrantList, { grants: grants }), grants.length === 0 && level !== "empty" && (_jsx("p", { className: "text-muted-foreground", children: "No credit grants to show." })), debtMicroUsd > 0 && (_jsxs("p", { className: "text-destructive", children: [formatMicroUsd(debtMicroUsd), " owed. It comes out of the next credits you add."] }))] })] }));
19
+ }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,15 @@
1
- export { CreditPacks } from "./billing/credit-packs.js";
2
- export { PlanOptions } from "./billing/plan-options.js";
3
- export { WalletPanel } from "./billing/wallet-panel.js";
1
+ export { Plans } from "./billing/plans.js";
2
+ export { TopUp } from "./billing/top-up.js";
3
+ export { Wallet } from "./billing/wallet.js";
4
4
  export { LocalDate } from "./dates/local-date.js";
5
- export { KeysTable } from "./keys/keys-table.js";
6
- export { NoKeys } from "./keys/no-keys.js";
5
+ export { Keys } from "./keys/keys.js";
6
+ export { KeysEmpty } from "./keys/keys-empty.js";
7
+ export { KeysSkeleton } from "./skeletons/keys-skeleton.js";
8
+ export { PlansSkeleton } from "./skeletons/plans-skeleton.js";
9
+ export { TopUpSkeleton } from "./skeletons/top-up-skeleton.js";
10
+ export { UsageSkeleton } from "./skeletons/usage-skeleton.js";
11
+ export { WalletSkeleton } from "./skeletons/wallet-skeleton.js";
7
12
  export * from "./types.js";
8
- export { QuotaMeter } from "./usage/quota-meter.js";
9
- export { UsageAttention } from "./usage/usage-attention.js";
10
- export { UsageSummary } from "./usage/usage-summary.js";
13
+ export { Quota } from "./usage/quota.js";
14
+ export { Usage } from "./usage/usage.js";
15
+ export { UsageAlert } from "./usage/usage-alert.js";
package/dist/index.js CHANGED
@@ -1,10 +1,15 @@
1
- export { CreditPacks } from "./billing/credit-packs.js";
2
- export { PlanOptions } from "./billing/plan-options.js";
3
- export { WalletPanel } from "./billing/wallet-panel.js";
1
+ export { Plans } from "./billing/plans.js";
2
+ export { TopUp } from "./billing/top-up.js";
3
+ export { Wallet } from "./billing/wallet.js";
4
4
  export { LocalDate } from "./dates/local-date.js";
5
- export { KeysTable } from "./keys/keys-table.js";
6
- export { NoKeys } from "./keys/no-keys.js";
5
+ export { Keys } from "./keys/keys.js";
6
+ export { KeysEmpty } from "./keys/keys-empty.js";
7
+ export { KeysSkeleton } from "./skeletons/keys-skeleton.js";
8
+ export { PlansSkeleton } from "./skeletons/plans-skeleton.js";
9
+ export { TopUpSkeleton } from "./skeletons/top-up-skeleton.js";
10
+ export { UsageSkeleton } from "./skeletons/usage-skeleton.js";
11
+ export { WalletSkeleton } from "./skeletons/wallet-skeleton.js";
7
12
  export * from "./types.js";
8
- export { QuotaMeter } from "./usage/quota-meter.js";
9
- export { UsageAttention } from "./usage/usage-attention.js";
10
- export { UsageSummary } from "./usage/usage-summary.js";
13
+ export { Quota } from "./usage/quota.js";
14
+ export { Usage } from "./usage/usage.js";
15
+ export { UsageAlert } from "./usage/usage-alert.js";
@@ -1,8 +1,8 @@
1
1
  import type { LinkComponent } from "../types.js";
2
- type NoKeysProps = {
2
+ type KeysEmptyProps = {
3
3
  newKeyHref?: string;
4
4
  description?: string;
5
5
  linkComponent?: LinkComponent | "a";
6
6
  };
7
- export declare function NoKeys({ newKeyHref, description, linkComponent: Link, }: NoKeysProps): import("react").JSX.Element;
7
+ export declare function KeysEmpty({ newKeyHref, description, linkComponent: Link, }: KeysEmptyProps): import("react").JSX.Element;
8
8
  export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Password as KeyRoundIcon, Add as PlusIcon } from "@carbon/icons-react";
3
+ import { Button, RuleGrid, RuleGridCell, RuleGridDescription, RuleGridTitle, } from "@locusgraph/components";
4
+ export function KeysEmpty({ newKeyHref, description = newKeyHref
5
+ ? "Create a key to let a service call LocusGraph products for this organization."
6
+ : "Ask an owner to create a key for this organization.", linkComponent: Link = "a", }) {
7
+ return (_jsx(RuleGrid, { columns: 1, children: _jsxs(RuleGridCell, { className: "items-center gap-2 py-12 text-center", children: [_jsx(KeyRoundIcon, { className: "mb-1 size-5 text-muted-foreground" }), _jsx(RuleGridTitle, { children: "No API keys yet" }), _jsx(RuleGridDescription, { className: "max-w-sm", children: description }), newKeyHref && (_jsx(Button, { asChild: true, size: "sm", className: "mt-2", children: _jsxs(Link, { href: newKeyHref, children: [_jsx(PlusIcon, {}), "New key"] }) }))] }) }));
8
+ }
@@ -1,9 +1,9 @@
1
1
  import type * as React from "react";
2
2
  import type { ApiKeyRow } from "../types.js";
3
- type KeysTableProps = {
3
+ type KeysProps = {
4
4
  keys: ApiKeyRow[];
5
5
  actions?: (key: ApiKeyRow) => React.ReactNode;
6
6
  highlightedId?: string;
7
7
  };
8
- export declare function KeysTable({ keys, actions, highlightedId }: KeysTableProps): React.JSX.Element;
8
+ export declare function Keys({ keys, actions, highlightedId }: KeysProps): React.JSX.Element;
9
9
  export {};
@@ -4,6 +4,6 @@ import { LocalDate } from "../dates/local-date.js";
4
4
  function dateOr(value, empty) {
5
5
  return value ? _jsx(LocalDate, { value: value }) : empty;
6
6
  }
7
- export function KeysTable({ keys, actions, highlightedId }) {
8
- return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Name" }), _jsx(TableHead, { className: "hidden md:table-cell", children: "Key" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { className: "hidden md:table-cell", children: "Created by" }), _jsx(TableHead, { className: "hidden sm:table-cell", children: "Created" }), _jsx(TableHead, { children: "Expires" }), actions && _jsx(TableHead, { className: "w-0" })] }) }), _jsx(TableBody, { children: keys.map((key) => (_jsxs(TableRow, { "data-new": key.id === highlightedId ? "" : undefined, className: "transition-colors duration-1000 data-new:bg-accent", children: [_jsx(TableCell, { className: "font-medium", children: key.name ?? "Untitled" }), _jsx(TableCell, { className: "hidden font-mono text-muted-foreground md:table-cell", children: key.maskedKey }), _jsx(TableCell, { children: key.productLabel }), _jsx(TableCell, { className: "hidden md:table-cell", children: key.createdBy }), _jsx(TableCell, { className: "hidden sm:table-cell", children: dateOr(key.createdAt, "") }), _jsx(TableCell, { children: dateOr(key.expiresAt, "Never") }), actions && _jsx(TableCell, { className: "text-right", children: actions(key) })] }, key.id))) })] }));
7
+ export function Keys({ keys, actions, highlightedId }) {
8
+ return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Name" }), _jsx(TableHead, { className: "hidden md:table-cell", children: "Key" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { className: "hidden md:table-cell", children: "Created by" }), _jsx(TableHead, { className: "hidden md:table-cell", children: "Created" }), _jsx(TableHead, { children: "Expires" }), actions && _jsx(TableHead, { className: "w-0" })] }) }), _jsx(TableBody, { children: keys.map((key) => (_jsxs(TableRow, { "data-new": key.id === highlightedId ? "" : undefined, className: "transition-colors duration-1000 data-new:bg-accent", children: [_jsxs(TableCell, { className: "max-w-40", children: [_jsx("span", { className: "block truncate font-medium", children: key.name ?? "Untitled" }), _jsx("span", { className: "block truncate font-mono text-xs text-muted-foreground md:hidden", children: key.maskedKey })] }), _jsx(TableCell, { className: "hidden font-mono text-muted-foreground md:table-cell", children: key.maskedKey }), _jsx(TableCell, { children: key.productLabel }), _jsx(TableCell, { className: "hidden md:table-cell", children: key.createdBy }), _jsx(TableCell, { className: "hidden md:table-cell", children: dateOr(key.createdAt, "—") }), _jsx(TableCell, { children: dateOr(key.expiresAt, "Never") }), actions && (_jsx(TableCell, { className: "w-0 pr-2 text-right whitespace-nowrap", children: actions(key) }))] }, key.id))) })] }));
9
9
  }
@@ -0,0 +1,6 @@
1
+ type KeysSkeletonProps = {
2
+ rows?: number;
3
+ actions?: boolean;
4
+ };
5
+ export declare function KeysSkeleton({ rows, actions }: KeysSkeletonProps): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SkeletonBar, SkeletonLoadingLabel, skeletonRootProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@locusgraph/components";
3
+ const columns = [
4
+ { name: "Key", visibility: "hidden md:table-cell", bar: "w-36" },
5
+ { name: "Product", visibility: "", bar: "w-20" },
6
+ { name: "Created by", visibility: "hidden md:table-cell", bar: "w-32" },
7
+ { name: "Created", visibility: "hidden md:table-cell", bar: "w-20" },
8
+ { name: "Expires", visibility: "", bar: "w-20" },
9
+ ];
10
+ function NameCellSkeleton() {
11
+ return (_jsx(TableCell, { className: "max-w-40", children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(SkeletonBar, { className: "h-4 w-28" }), _jsx(SkeletonBar, { className: "h-3 w-20 md:hidden" })] }) }));
12
+ }
13
+ export function KeysSkeleton({ rows = 3, actions = false }) {
14
+ const count = Math.max(1, Math.floor(rows));
15
+ return (_jsxs("div", { "data-slot": "keys-skeleton", ...skeletonRootProps, className: "w-full", children: [_jsxs(Table, { "aria-hidden": "true", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableHead, { children: "Name" }), columns.map((column) => (_jsx(TableHead, { className: column.visibility, children: column.name }, column.name))), actions && _jsx(TableHead, { className: "w-0" })] }) }), _jsx(TableBody, { children: Array.from({ length: count }, (_, row) => (_jsxs(TableRow
16
+ // biome-ignore lint/suspicious/noArrayIndexKey: placeholder rows have no identity
17
+ , { "data-slot": "keys-skeleton-row", className: "hover:bg-transparent", children: [_jsx(NameCellSkeleton, {}), columns.map((column) => (_jsx(TableCell, { className: column.visibility, children: _jsx(SkeletonBar, { className: `h-4 ${column.bar}` }) }, column.name))), actions && (_jsx(TableCell, { className: "w-0 pr-2 text-right", children: _jsx(SkeletonBar, { className: "ml-auto h-8 w-16 rounded-full" }) }))] }, row))) })] }), _jsx(SkeletonLoadingLabel, { children: "Loading API keys\u2026" })] }));
18
+ }
@@ -0,0 +1,7 @@
1
+ type PlansSkeletonProps = {
2
+ plans?: number;
3
+ points?: number;
4
+ currentIndex?: number;
5
+ };
6
+ export declare function PlansSkeleton({ plans, points, currentIndex }: PlansSkeletonProps): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { RuleGrid, RuleGridCell, SkeletonBar, SkeletonLoadingLabel, skeletonRootProps, } from "@locusgraph/components";
3
+ export function PlansSkeleton({ plans = 2, points = 2, currentIndex = 0 }) {
4
+ const planCount = Math.max(1, Math.floor(plans));
5
+ const pointCount = Math.max(0, Math.floor(points));
6
+ return (_jsxs(RuleGrid, { columns: planCount >= 3 ? 3 : 2, "data-slot": "plans-skeleton", ...skeletonRootProps, children: [Array.from({ length: planCount }, (_, plan) => (_jsxs(RuleGridCell
7
+ // biome-ignore lint/suspicious/noArrayIndexKey: placeholder plans have no identity
8
+ , { "data-slot": "plan-skeleton", "aria-hidden": "true", className: "gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "flex min-h-6 items-center", children: _jsx(SkeletonBar, { className: "h-5 w-20" }) }), _jsx(SkeletonBar, { className: "h-8 w-28" })] }), _jsx("div", { className: "flex flex-col gap-1.5", children: Array.from({ length: pointCount }, (_, point) => (_jsx(SkeletonBar, { className: "h-4 w-40" }, point))) }), plan === currentIndex ? (_jsx("div", { className: "mt-auto flex min-h-8 items-center", children: _jsx(SkeletonBar, { className: "h-4 w-16" }) })) : (_jsx(SkeletonBar, { className: "mt-auto h-8 w-28 rounded-full" }))] }, plan))), _jsx(SkeletonLoadingLabel, { children: "Loading plans\u2026" })] }));
9
+ }
@@ -0,0 +1,5 @@
1
+ type TopUpSkeletonProps = {
2
+ packs?: number;
3
+ };
4
+ export declare function TopUpSkeleton({ packs }: TopUpSkeletonProps): import("react").JSX.Element;
5
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { RuleGridCell, RuleGridLabel, SkeletonBar, SkeletonLoadingLabel, skeletonRootProps, } from "@locusgraph/components";
3
+ export function TopUpSkeleton({ packs = 3 }) {
4
+ const count = Math.max(1, Math.floor(packs));
5
+ return (_jsxs(RuleGridCell, { "data-slot": "top-up-skeleton", ...skeletonRootProps, className: "gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "flex min-h-6 items-center", children: _jsx(RuleGridLabel, { children: "Buy credits" }) }), _jsx(SkeletonBar, { className: "h-4 w-full max-w-72" }), _jsx(SkeletonBar, { className: "h-4 w-3/4 max-w-56" })] }), _jsx("div", { className: "flex flex-wrap gap-2", "aria-hidden": "true", children: Array.from({ length: count }, (_, index) => (_jsx(SkeletonBar
6
+ // biome-ignore lint/suspicious/noArrayIndexKey: placeholder packs have no identity
7
+ , { "data-slot": "credit-pack-skeleton", className: "h-9 w-24 rounded-full" }, index))) }), _jsx(SkeletonLoadingLabel, { children: "Loading credit packs\u2026" })] }));
8
+ }
@@ -0,0 +1,6 @@
1
+ type UsageSkeletonProps = {
2
+ quotas?: number;
3
+ label?: string;
4
+ };
5
+ export declare function UsageSkeleton({ quotas, label }: UsageSkeletonProps): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { RuleGrid, RuleGridCell, RuleGridLabel, SkeletonBar, SkeletonLoadingLabel, skeletonRootProps, } from "@locusgraph/components";
3
+ function QuotaSkeleton() {
4
+ return (_jsxs("div", { "data-slot": "quota-skeleton", "aria-hidden": "true", className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsx(SkeletonBar, { className: "h-4 w-28" }), _jsx(SkeletonBar, { className: "h-4 w-36" })] }), _jsx(SkeletonBar, { className: "h-2 w-full rounded-full" })] }));
5
+ }
6
+ export function UsageSkeleton({ quotas = 2, label = "Usage" }) {
7
+ const count = Math.max(1, Math.floor(quotas));
8
+ return (_jsx(RuleGrid, { columns: 1, "data-slot": "usage-skeleton", ...skeletonRootProps, children: _jsxs(RuleGridCell, { className: "gap-5", children: [_jsx(RuleGridLabel, { children: label }), Array.from({ length: count }, (_, index) => (_jsx(QuotaSkeleton, {}, index))), _jsx(SkeletonLoadingLabel, { children: "Loading usage\u2026" })] }) }));
9
+ }
@@ -0,0 +1,5 @@
1
+ type WalletSkeletonProps = {
2
+ grants?: number;
3
+ };
4
+ export declare function WalletSkeleton({ grants }: WalletSkeletonProps): import("react").JSX.Element;
5
+ export {};
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { RuleGridCell, RuleGridLabel, SkeletonBar, SkeletonLoadingLabel, skeletonRootProps, } from "@locusgraph/components";
3
+ export function WalletSkeleton({ grants = 1 }) {
4
+ const count = Math.max(0, Math.floor(grants));
5
+ return (_jsxs(RuleGridCell, { "data-slot": "wallet-skeleton", ...skeletonRootProps, className: "gap-4", children: [_jsx("span", { className: "flex min-h-6 items-center justify-between", children: _jsx(RuleGridLabel, { children: "Prepaid credits" }) }), _jsx("div", { className: "flex flex-col gap-1", "aria-hidden": "true", children: _jsx(SkeletonBar, { className: "h-9 w-32" }) }), _jsx("ul", { className: "flex flex-col divide-y divide-line", "aria-hidden": "true", children: Array.from({ length: count }, (_, index) => (_jsxs("li", { "data-slot": "wallet-grant-skeleton", className: "flex justify-between gap-4 py-2", children: [_jsx(SkeletonBar, { className: "h-4 w-24" }), _jsx(SkeletonBar, { className: "h-4 w-40" })] }, index))) }), _jsx(SkeletonLoadingLabel, { children: "Loading credits\u2026" })] }));
6
+ }
package/dist/types.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  export type { LinkComponent } from "@locusgraph/components";
2
2
  export type QuotaPeriod = "month" | "lifetime";
3
- export type Quota = {
3
+ export type QuotaData = {
4
4
  key: string;
5
5
  period: QuotaPeriod;
6
6
  used: number;
7
7
  included: number;
8
+ label?: string;
8
9
  };
9
10
  export type CreditPack = {
10
11
  id: string;
@@ -16,9 +17,12 @@ export type WalletGrant = {
16
17
  remainingMicroUsd: number;
17
18
  expiresAt: string | null;
18
19
  };
20
+ export type PlanInterval = "month" | "year";
19
21
  export type PlanOffer = {
20
22
  name: string;
21
23
  price: string;
24
+ amountCents?: number;
25
+ interval?: PlanInterval;
22
26
  points: string[];
23
27
  current: boolean;
24
28
  };
@@ -0,0 +1,4 @@
1
+ import { type QuotaData } from "../types.js";
2
+ export declare function Quota({ quota }: {
3
+ quota: QuotaData;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Progress } from "@locusgraph/components";
3
+ import { formatCount, quotaPercent, quotaStatus } from "@locusgraph/utils";
4
+ import { periodLabel } from "../types.js";
5
+ const statusIndicator = {
6
+ ok: "",
7
+ near: "[&_[data-slot=progress-indicator]]:bg-warn",
8
+ over: "[&_[data-slot=progress-indicator]]:bg-destructive",
9
+ };
10
+ function QuotaStatusNote({ quota }) {
11
+ const status = quotaStatus(quota);
12
+ if (status === "over") {
13
+ return (_jsxs("span", { className: "font-medium text-destructive", children: [formatCount(quota.used - quota.included), " over"] }));
14
+ }
15
+ if (status === "near") {
16
+ return _jsxs("span", { className: "font-medium text-warn", children: [quotaPercent(quota), "%"] });
17
+ }
18
+ return null;
19
+ }
20
+ export function Quota({ quota }) {
21
+ const percent = quotaPercent(quota);
22
+ const name = quota.label ?? quota.key;
23
+ return (_jsxs("div", { "data-slot": "quota", "data-status": quotaStatus(quota), className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 text-sm", children: [_jsx("span", { className: quota.label ? "text-muted-foreground" : "font-mono text-muted-foreground", children: name }), _jsxs("span", { className: "flex items-baseline gap-2 tabular-nums", children: [_jsxs("span", { className: "font-medium text-foreground", children: [formatCount(quota.used), " of ", formatCount(quota.included), " ", periodLabel(quota.period)] }), _jsx(QuotaStatusNote, { quota: quota })] })] }), _jsx(Progress, { value: percent, "aria-label": `${name} ${percent}% of quota`, className: statusIndicator[quotaStatus(quota)] })] }));
24
+ }
@@ -0,0 +1,11 @@
1
+ import { type LinkComponent, type QuotaData } from "../types.js";
2
+ type UsageAlertProps = {
3
+ balanceMicroUsd: number | null;
4
+ quotas: QuotaData[];
5
+ paidPlan: boolean;
6
+ buyCreditsHref: string;
7
+ upgradeHref: string;
8
+ linkComponent?: LinkComponent | "a";
9
+ };
10
+ export declare function UsageAlert({ balanceMicroUsd, quotas, paidPlan, buyCreditsHref, upgradeHref, linkComponent: Link, }: UsageAlertProps): import("react").JSX.Element | null;
11
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Warning as CircleAlertIcon } from "@carbon/icons-react";
3
+ import { Alert, AlertDescription, AlertTitle, Button } from "@locusgraph/components";
4
+ import { balanceLevel, formatCount, formatMicroUsd, quotaStatus, } from "@locusgraph/utils";
5
+ import { periodLabel } from "../types.js";
6
+ function attentionTitle(level, overCount) {
7
+ if (level === "empty")
8
+ return "Out of credits";
9
+ if (overCount === 0)
10
+ return "Credits are running low";
11
+ return level === "low"
12
+ ? "Usage is over your plan and credits are low"
13
+ : "Usage is over your plan";
14
+ }
15
+ const toneClasses = {
16
+ warn: "bg-warn/5 [&>svg]:text-warn",
17
+ destructive: "bg-destructive/5 text-card-foreground [&>svg]:text-destructive",
18
+ };
19
+ export function UsageAlert({ balanceMicroUsd, quotas, paidPlan, buyCreditsHref, upgradeHref, linkComponent: Link = "a", }) {
20
+ const level = balanceMicroUsd === null ? "fine" : balanceLevel(balanceMicroUsd);
21
+ const over = quotas.filter((quota) => quotaStatus(quota) === "over");
22
+ if (level === "fine" && over.length === 0)
23
+ return null;
24
+ const empty = level === "empty";
25
+ const upgradeFirst = !paidPlan && over.length > 0;
26
+ return (_jsxs(Alert, { className: toneClasses[empty ? "destructive" : "warn"], children: [_jsx(CircleAlertIcon, {}), _jsx(AlertTitle, { children: attentionTitle(level, over.length) }), _jsxs(AlertDescription, { className: "flex flex-col items-start gap-3", children: [_jsxs("ul", { className: "flex flex-col gap-1", children: [over.map((quota) => (_jsxs("li", { children: [_jsx("span", { className: quota.label ? undefined : "font-mono", children: quota.label ?? quota.key }), " ", "is at ", formatCount(quota.used), " of ", formatCount(quota.included), " ", periodLabel(quota.period), "."] }, quota.key))), balanceMicroUsd !== null && level !== "fine" && (_jsx("li", { children: empty
27
+ ? "Paid usage is paused until you add credits."
28
+ : `${formatMicroUsd(balanceMicroUsd)} of credits left.` }))] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [!paidPlan && (_jsx(Button, { asChild: true, size: "sm", variant: upgradeFirst ? "default" : "ghost", children: _jsx(Link, { href: upgradeHref, children: "Upgrade to Pro" }) })), _jsx(Button, { asChild: true, size: "sm", variant: upgradeFirst ? "ghost" : "default", children: _jsx(Link, { href: buyCreditsHref, children: "Buy credits" }) })] })] })] }));
29
+ }
@@ -0,0 +1,8 @@
1
+ import type { QuotaData } from "../types.js";
2
+ type UsageProps = {
3
+ quotas: QuotaData[];
4
+ label?: string;
5
+ emptyText?: string;
6
+ };
7
+ export declare function Usage({ quotas, label, emptyText, }: UsageProps): import("react").JSX.Element;
8
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { RuleGrid, RuleGridCell, RuleGridLabel } from "@locusgraph/components";
3
- import { QuotaMeter } from "./quota-meter.js";
4
- export function UsageSummary({ quotas, label = "Usage", emptyText = "No plan includes metered usage yet.", }) {
5
- return (_jsx(RuleGrid, { columns: 1, children: _jsxs(RuleGridCell, { className: "gap-5", children: [_jsx(RuleGridLabel, { children: label }), quotas.length > 0 ? (quotas.map((quota) => _jsx(QuotaMeter, { quota: quota }, quota.key))) : (_jsx("p", { className: "text-sm text-muted-foreground", children: emptyText }))] }) }));
3
+ import { Quota } from "./quota.js";
4
+ export function Usage({ quotas, label = "Usage", emptyText = "No plan includes metered usage yet.", }) {
5
+ return (_jsx(RuleGrid, { columns: 1, children: _jsxs(RuleGridCell, { className: "gap-5", children: [_jsx(RuleGridLabel, { children: label }), quotas.length > 0 ? (quotas.map((quota) => _jsx(Quota, { quota: quota }, quota.key))) : (_jsx("p", { className: "text-sm text-muted-foreground", children: emptyText }))] }) }));
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusgraph/blocks",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Product sections built from @locusgraph/components: usage meters and alerts, wallet, credit packs, plan options and API keys.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,16 +29,16 @@
29
29
  "provenance": true
30
30
  },
31
31
  "peerDependencies": {
32
- "@locusgraph/components": "^0.1.1",
32
+ "@locusgraph/components": "^0.2.0",
33
33
  "react": "^19.2.0",
34
34
  "react-dom": "^19.2.0"
35
35
  },
36
36
  "dependencies": {
37
- "@locusgraph/utils": "0.1.1",
38
- "lucide-react": "^1.28.0"
37
+ "@carbon/icons-react": "11.89.0",
38
+ "@locusgraph/utils": "0.1.1"
39
39
  },
40
40
  "devDependencies": {
41
- "@locusgraph/components": "0.1.1",
41
+ "@locusgraph/components": "0.2.0",
42
42
  "@locusgraph/config": "0.1.1",
43
43
  "@types/node": "^22.0.0",
44
44
  "@types/react": "^19.2.0",
@@ -1,9 +0,0 @@
1
- import type { CreditPack } from "../types.js";
2
- type CreditPacksProps = {
3
- packs: CreditPack[];
4
- onBuy: (packId: string) => void;
5
- pending?: boolean;
6
- description?: string;
7
- };
8
- export declare function CreditPacks({ packs, onBuy, pending, description, }: CreditPacksProps): import("react").JSX.Element;
9
- export {};
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, RuleGridCell, RuleGridDescription, RuleGridLabel } from "@locusgraph/components";
3
- import { centsToMicroUsd, formatCents, formatMicroUsd } from "@locusgraph/utils";
4
- export function CreditPacks({ packs, onBuy, pending = false, description = "Pay once with Stripe. Credits never expire and never renew on their own.", }) {
5
- return (_jsxs(RuleGridCell, { className: "gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(RuleGridLabel, { children: "Buy credits" }), _jsx(RuleGridDescription, { children: description })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: packs.map((pack) => (_jsxs(Button, { variant: "outline", disabled: pending, onClick: () => onBuy(pack.id), children: [_jsx("span", { className: "tabular-nums", children: formatMicroUsd(pack.amountMicroUsd) }), centsToMicroUsd(pack.priceCents) !== pack.amountMicroUsd && (_jsxs("span", { className: "text-muted-foreground tabular-nums", children: ["for ", formatCents(pack.priceCents)] }))] }, pack.id))) })] }));
6
- }
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Badge, RuleGrid, RuleGridCell, RuleGridDescription, RuleGridTitle, } from "@locusgraph/components";
3
- export function PlanOptions({ plans, actionFor }) {
4
- return (_jsx(RuleGrid, { columns: plans.length >= 3 ? 3 : 2, children: plans.map((plan) => {
5
- const action = plan.current ? null : actionFor?.(plan);
6
- return (_jsxs(RuleGridCell, { "data-current": plan.current || undefined, className: "gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(RuleGridTitle, { className: "flex items-center gap-2", children: [plan.name, plan.current && _jsx(Badge, { variant: "secondary", children: "Current" })] }), _jsx(RuleGridDescription, { children: plan.price })] }), _jsx("ul", { className: "flex flex-col gap-1.5 text-sm text-muted-foreground", children: plan.points.map((point) => (_jsx("li", { children: point }, point))) }), action && _jsx("div", { className: "mt-auto", children: action })] }, plan.name));
7
- }) }));
8
- }
@@ -1,8 +0,0 @@
1
- import type { WalletGrant } from "../types.js";
2
- type WalletPanelProps = {
3
- balanceMicroUsd: number;
4
- debtMicroUsd?: number;
5
- grants: WalletGrant[];
6
- };
7
- export declare function WalletPanel({ balanceMicroUsd, debtMicroUsd, grants }: WalletPanelProps): import("react").JSX.Element;
8
- export {};
@@ -1,16 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Badge, RuleGridCell, RuleGridLabel } from "@locusgraph/components";
3
- import { balanceLevel, formatDate, formatMicroUsd } from "@locusgraph/utils";
4
- const levelHint = {
5
- low: { badge: "Low", text: "Add credits before usage stops.", tone: "text-warn" },
6
- empty: {
7
- badge: "Empty",
8
- text: "Paid usage is paused until you add credits.",
9
- tone: "text-destructive",
10
- },
11
- };
12
- export function WalletPanel({ balanceMicroUsd, debtMicroUsd = 0, grants }) {
13
- const level = balanceLevel(balanceMicroUsd);
14
- const hint = level === "fine" ? null : levelHint[level];
15
- return (_jsxs(RuleGridCell, { "data-level": level, className: "gap-4", children: [_jsxs("span", { className: "flex items-center justify-between", children: [_jsx(RuleGridLabel, { children: "Prepaid credits" }), hint && (_jsx(Badge, { variant: level === "empty" ? "destructive" : "secondary", className: level === "low" ? "text-warn" : undefined, children: hint.badge }))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "font-heading text-3xl font-semibold tracking-[-0.02em] tabular-nums", children: formatMicroUsd(balanceMicroUsd) }), hint && _jsx("p", { className: `text-sm ${hint.tone}`, children: hint.text })] }), _jsxs("div", { className: "flex flex-col gap-3 text-sm", children: [grants.length > 0 ? (_jsx("ul", { className: "flex flex-col divide-y divide-line", children: grants.map((grant) => (_jsxs("li", { className: "flex justify-between gap-4 py-2", children: [_jsx("span", { children: grant.label }), _jsxs("span", { className: "text-muted-foreground tabular-nums", children: [formatMicroUsd(grant.remainingMicroUsd), " \u00B7", " ", grant.expiresAt ? `expires ${formatDate(grant.expiresAt)}` : "never expires"] })] }, `${grant.label}:${grant.expiresAt}`))) })) : (_jsx("p", { className: "text-muted-foreground", children: "No credits yet. Buy a pack to keep using paid features." })), debtMicroUsd > 0 && (_jsxs("p", { className: "text-destructive", children: [formatMicroUsd(debtMicroUsd), " owed. It comes out of the next credits you add."] }))] })] }));
16
- }
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, RuleGrid, RuleGridCell, RuleGridDescription, RuleGridLabel, } from "@locusgraph/components";
3
- import { KeyRoundIcon, PlusIcon } from "lucide-react";
4
- export function NoKeys({ newKeyHref, description = newKeyHref
5
- ? "Create a key to let a service call LocusGraph products for this organization."
6
- : "Ask an owner to create a key for this organization.", linkComponent: Link = "a", }) {
7
- return (_jsx(RuleGrid, { columns: 1, children: _jsxs(RuleGridCell, { className: "items-center gap-3 py-12 text-center", children: [_jsx(KeyRoundIcon, { className: "size-5 text-muted-foreground" }), _jsx(RuleGridLabel, { children: "No API keys yet" }), _jsx(RuleGridDescription, { className: "max-w-sm", children: description }), newKeyHref && (_jsx(Button, { asChild: true, size: "sm", children: _jsxs(Link, { href: newKeyHref, children: [_jsx(PlusIcon, {}), "New key"] }) }))] }) }));
8
- }
@@ -1,4 +0,0 @@
1
- import { type Quota } from "../types.js";
2
- export declare function QuotaMeter({ quota }: {
3
- quota: Quota;
4
- }): import("react").JSX.Element;
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Progress } from "@locusgraph/components";
3
- import { formatCount, quotaPercent, quotaStatus } from "@locusgraph/utils";
4
- import { periodLabel } from "../types.js";
5
- const statusIndicator = {
6
- ok: "",
7
- near: "[&_[data-slot=progress-indicator]]:bg-warn",
8
- over: "[&_[data-slot=progress-indicator]]:bg-destructive",
9
- };
10
- export function QuotaMeter({ quota }) {
11
- const percent = quotaPercent(quota);
12
- return (_jsxs("div", { "data-slot": "quota-meter", "data-status": quotaStatus(quota), className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-4 text-sm", children: [_jsx("span", { className: "font-mono", children: quota.key }), _jsxs("span", { className: "text-muted-foreground tabular-nums", children: [formatCount(quota.used), " of ", formatCount(quota.included), " ", periodLabel(quota.period)] })] }), _jsx(Progress, { value: percent, "aria-label": `${quota.key} ${percent}% of quota`, className: statusIndicator[quotaStatus(quota)] })] }));
13
- }
@@ -1,11 +0,0 @@
1
- import { type LinkComponent, type Quota } from "../types.js";
2
- type UsageAttentionProps = {
3
- balanceMicroUsd: number | null;
4
- quotas: Quota[];
5
- paidPlan: boolean;
6
- buyCreditsHref: string;
7
- upgradeHref: string;
8
- linkComponent?: LinkComponent | "a";
9
- };
10
- export declare function UsageAttention({ balanceMicroUsd, quotas, paidPlan, buyCreditsHref, upgradeHref, linkComponent: Link, }: UsageAttentionProps): import("react").JSX.Element | null;
11
- export {};
@@ -1,20 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Alert, AlertDescription, AlertTitle, Button } from "@locusgraph/components";
3
- import { balanceLevel, formatCount, formatMicroUsd, quotaStatus } from "@locusgraph/utils";
4
- import { CircleAlertIcon } from "lucide-react";
5
- import { periodLabel } from "../types.js";
6
- function attentionTitle(empty, overCount) {
7
- if (empty)
8
- return "Out of credits";
9
- return overCount > 0 ? "Usage is over your plan" : "Credits are running low";
10
- }
11
- export function UsageAttention({ balanceMicroUsd, quotas, paidPlan, buyCreditsHref, upgradeHref, linkComponent: Link = "a", }) {
12
- const level = balanceMicroUsd === null ? "fine" : balanceLevel(balanceMicroUsd);
13
- const over = quotas.filter((quota) => quotaStatus(quota) === "over");
14
- if (level === "fine" && over.length === 0)
15
- return null;
16
- const empty = level === "empty";
17
- return (_jsxs(Alert, { variant: empty ? "destructive" : "default", className: empty ? undefined : "text-warn *:data-[slot=alert-description]:text-warn/85", children: [_jsx(CircleAlertIcon, {}), _jsx(AlertTitle, { children: attentionTitle(empty, over.length) }), _jsxs(AlertDescription, { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("ul", { className: "flex flex-col gap-1", children: [over.map((quota) => (_jsxs("li", { children: [_jsx("span", { className: "font-mono", children: quota.key }), " is at ", formatCount(quota.used), " of", " ", formatCount(quota.included), " ", periodLabel(quota.period), "."] }, quota.key))), balanceMicroUsd !== null && level !== "fine" && (_jsx("li", { children: empty
18
- ? "Paid usage is paused until you add credits."
19
- : `${formatMicroUsd(balanceMicroUsd)} of credits left.` }))] }), _jsxs("div", { className: "flex items-center gap-4", children: [!paidPlan && (_jsx(Link, { href: upgradeHref, className: "text-sm text-foreground underline-offset-4 hover:underline", children: "Upgrade to Pro" })), _jsx(Button, { asChild: true, size: "sm", children: _jsx(Link, { href: buyCreditsHref, children: "Buy credits" }) })] })] })] }));
20
- }
@@ -1,8 +0,0 @@
1
- import type { Quota } from "../types.js";
2
- type UsageSummaryProps = {
3
- quotas: Quota[];
4
- label?: string;
5
- emptyText?: string;
6
- };
7
- export declare function UsageSummary({ quotas, label, emptyText, }: UsageSummaryProps): import("react").JSX.Element;
8
- export {};