import { useState } from "react"; import { useMutation } from "@tanstack/react-query"; import { SUBSCRIPTION_CURRENCIES, centsToUsd, usdToCents, type SubscriptionAccountReport, type SubscriptionCostReport, type SubscriptionUsage } from "@paperclipai/shared"; import { subscriptionsApi } from "../api/subscriptions"; import { formatCents, formatDateTime, formatTokens, providerDisplayName } from "../lib/utils"; import { Button } from "./ui/button"; import { Badge } from "./ui/badge"; import { Card } from "./ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; export function subscriptionMoney(cents: string, currency: string) { return currency === "USD" ? formatCents(Number(cents)) : new Intl.NumberFormat(undefined, { style: "currency", currency, currencyDisplay: "code" }).format(Number(cents) / 100); } export function SubscriptionTokenUsage({ usage }: { usage: SubscriptionUsage }) { return {formatTokens(usage.inputTokens + usage.cachedInputTokens)} in · {formatTokens(usage.outputTokens)} out {usage.cachedInputTokens > 0 && {formatTokens(usage.cachedInputTokens)} input cached} ; } function SubscriptionEditor({ account, accounts, companyId, onDone }: { account: SubscriptionAccountReport; accounts: SubscriptionAccountReport[]; companyId: string; onDone: () => void; }) { const [plan, setPlan] = useState(account.price.plan); const [amount, setAmount] = useState(account.price.amountCents == null ? "" : centsToUsd(account.price.amountCents).replace(/\.?0+$/, "")); const [currency, setCurrency] = useState(account.price.currency); const [cadence, setCadence] = useState(account.price.cadence); const [status, setStatus] = useState(account.price.status); const [targetId, setTargetId] = useState(""); const save = useMutation({ mutationFn: () => subscriptionsApi.update(companyId, account.id, { expectedRevision: account.price.revision, plan, amountCents: amount.trim() ? usdToCents(amount.trim()) : null, currency: currency as typeof SUBSCRIPTION_CURRENCIES[number], cadence, status, }), onSuccess: onDone }); const link = useMutation({ mutationFn: () => subscriptionsApi.link(companyId, account.id, targetId, account.price.revision, accounts.find(row => row.id === targetId)!.price.revision), onSuccess: onDone }); const targets = accounts.filter(row => row.id !== account.id && row.provider === account.provider && row.canEdit); const busy = save.isPending || link.isPending; const selectClass = "h-9 rounded-md border border-input bg-background px-3 text-sm"; return
{ event.preventDefault(); save.mutate(); }}>
setPlan(event.target.value)} required maxLength={100} />
setAmount(event.target.value)} />

Enter the full amount for one account or seat. Annual prices are divided by 12. Changes apply from now. Ending tracking here does not cancel your provider subscription.

{save.error &&

Could not save this price. Check the amount, or reopen the account if another person updated it.

}
{targets.length > 0 &&

Linking counts these accounts once, combines their Paperclip usage, and keeps the selected account’s price. Only link the same subscription or paid seat.

{link.error &&

Could not link these accounts. Reopen their details and try again.

}
}
; } export function SubscriptionCostCard({ companyId, report, error, onChanged }: { companyId: string; report?: SubscriptionCostReport; error?: unknown; onChanged: () => void; }) { const [open, setOpen] = useState(false); const [editing, setEditing] = useState(null); const closeEditor = () => { setEditing(null); onChanged(); }; const known = report?.monthlyTotals.length; const estimatedCount = report?.monthlyTotals.reduce((sum, total) => sum + total.estimatedCount, 0) ?? 0; return <>
Subscriptions
{known ? report!.monthlyTotals.map(total =>
{subscriptionMoney(total.amountCents, total.currency)}/month
) : report && report.activeCount === 0 && report.accounts.length > 0 ? "$0.00/month" : "—"}
{report ? <> {estimatedCount > 0 &&
{estimatedCount < report.activeCount - report.unknownPriceCount ? "Partially estimated" : "Estimated"}
} {report.unknownPriceCount > 0 && !!known &&
Known monthly subtotal
}
{report.accounts.length === 0 ? "No visible subscriptions" : <>{report.activeCount} active {report.activeCount === 1 ? "subscription" : "subscriptions"}{report.unknownPriceCount > 0 && ` · ${report.unknownPriceCount} ${report.unknownPriceCount === 1 ? "price" : "prices"} unknown`}}
{report.accounts.length > 0 &&
Monthly fees for accounts visible to you
} {report.unidentifiedAccountCount > 0 &&
{report.unidentifiedAccountCount} unconfirmed {report.unidentifiedAccountCount === 1 ? "account identity" : "account identities"}
} : error ?
Subscription data is unavailable.
:
Loading subscriptions…
} {report && error ?
Showing last loaded subscription data.
: null}
{ setOpen(value); if (!value) setEditing(null); }}> {editing ? "Subscription price" : "Subscriptions"}Current monthly costs for accounts visible to you. Token totals cover company work in the selected period. {editing ? :
{report?.accounts.length === 0 &&

No subscriptions are visible to you yet. Connect an AI account or run an agent with a connected subscription.

} {report?.accounts.map(account =>
{account.name}
{account.shared ? "Shared" : account.ownerName ?? (account.ownerUserId ? "Account owner" : "Personal accounts")} · {providerDisplayName(account.provider)} · {account.price.plan}
{account.price.status !== "active" ? account.price.status === "ended" ? "Ended" : "Excluded" : account.price.monthlyCents == null ? "Price unknown" : `${subscriptionMoney(account.price.monthlyCents, account.price.currency)}/month`}
{account.price.source === "catalog" ? "Estimated · before tax" : account.price.source === "user" ? "User supplied" : ""}{account.price.cadence === "year" ? " · annual payment ÷ 12" : ""}
{Number(account.usage.costCents) > 0 &&
{formatCents(Number(account.usage.costCents))} recorded usage charges in selected period, separate from the subscription fee.
} {account.agents.length > 0 &&
Used by {account.agents.map(agent => agent.name).join(", ")}
} {!account.identityVerified &&

Account identity is unconfirmed. Link duplicates if these connections use the same subscription.

} {account.observedAt &&
Plan checked {formatDateTime(account.observedAt)}{account.refreshStatus === "unavailable" ? " · latest check unavailable" : ""}
} {account.refreshStatus === "unavailable" && !account.observedAt &&

The provider did not make plan details available. You can enter the price yourself.

} {account.canEdit &&
}
)} {!!report?.unattributedSubscription.eventCount &&

Some subscription usage has no recorded account. It is included in the subscription token total.

}

Monthly fees cover the whole account, including use outside Paperclip. They are separate from recorded charges and agent budgets. Currencies are totaled separately.

}
; }