@odla-ai/chapter 0.52.1 → 0.53.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.
- package/README.md +17 -0
- package/dist/{chunk-WWF2TO3H.js → chunk-PTC43J7F.js} +39 -5
- package/dist/{chunk-WWF2TO3H.js.map → chunk-PTC43J7F.js.map} +1 -1
- package/dist/index.cjs +8 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +4 -3
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/worker/index.cjs +299 -84
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +33 -1
- package/dist/worker/index.d.ts +33 -1
- package/dist/worker/index.js +292 -77
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1782,6 +1782,23 @@ These bite silently — a smoke test won't catch them:
|
|
|
1782
1782
|
and a payable price without reading the database; nothing on it edits a
|
|
1783
1783
|
version.
|
|
1784
1784
|
|
|
1785
|
+
A promise is frozen at checkout, so publishing a later discount policy version
|
|
1786
|
+
changes nothing for members who already paid. When the later version widens
|
|
1787
|
+
the benefit to `while_active`, the network owner moves the cohort's existing
|
|
1788
|
+
members onto it with `POST /api/admin/membership-discount/reterm`
|
|
1789
|
+
({ chapterId, policyVersionId, runtime?, appId?, membershipIds?, limit? }; super
|
|
1790
|
+
admin; `runtime` defaults to the deployment's own) or the "Move members to this
|
|
1791
|
+
benefit" action the panel shows on that version. Each member's Stripe
|
|
1792
|
+
subscription receives the version's forever coupon first, then the frozen row
|
|
1793
|
+
takes the version and its lower renewal amount under the row's revision, so a
|
|
1794
|
+
run that stops between the two is completed by running it again. It only ever
|
|
1795
|
+
widens: a version that would raise a member's renewal, a lapsed, refunded,
|
|
1796
|
+
canceled, declined or restarting purchase, another cohort, and a subscription
|
|
1797
|
+
the provider no longer matches are all left alone and named in the receipt
|
|
1798
|
+
with the reason. A chapter's own billing forecast reports a provider discount
|
|
1799
|
+
its frozen checkout terms do not explain as unknown, never as a full-price
|
|
1800
|
+
renewal.
|
|
1801
|
+
|
|
1785
1802
|
On a chapter, a person record with no linked application — someone invited or
|
|
1786
1803
|
a candidate — is managed by the chapter's own admins the way the network
|
|
1787
1804
|
owner manages it: the record's Invitation tab sets or revokes the local
|
|
@@ -3506,9 +3506,11 @@ function NetworkSeatBilling(props) {
|
|
|
3506
3506
|
}
|
|
3507
3507
|
|
|
3508
3508
|
// src/ui/membership-policy-versions.tsx
|
|
3509
|
-
import { DataTable as DataTable5 } from "@odla-ai/ui/components";
|
|
3509
|
+
import { Button as Button20, DataTable as DataTable5 } from "@odla-ai/ui/components";
|
|
3510
|
+
import { useState as useState31 } from "preact/hooks";
|
|
3510
3511
|
import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs34 } from "preact/jsx-runtime";
|
|
3511
3512
|
var PATH = "/api/admin/membership-policy-versions";
|
|
3513
|
+
var RETERM = "/api/admin/membership-discount/reterm";
|
|
3512
3514
|
var day = (ms) => new Date(ms).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
|
|
3513
3515
|
var money4 = (cents, currency) => new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
|
|
3514
3516
|
var STATE = { active: "Active", scheduled: "Scheduled", superseded: "Superseded", draft: "Draft", retired: "Retired" };
|
|
@@ -3549,7 +3551,7 @@ var offeringColumns = [
|
|
|
3549
3551
|
{ key: "supersededAt", header: "Superseded", cell: superseded },
|
|
3550
3552
|
{ key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx38(Source, { row }) }
|
|
3551
3553
|
];
|
|
3552
|
-
var policyColumns = [
|
|
3554
|
+
var policyColumns = (getToken) => [
|
|
3553
3555
|
{ key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs34(Fragment14, { children: [
|
|
3554
3556
|
/* @__PURE__ */ jsxs34("code", { children: [
|
|
3555
3557
|
"v",
|
|
@@ -3572,8 +3574,40 @@ var policyColumns = [
|
|
|
3572
3574
|
{ key: "benefit", header: "Benefit duration", cell: (row) => row.benefitDuration.kind === "fixed_end" ? `Until ${day(row.benefitDuration.endsAt)}` : row.benefitDuration.kind === "first_term" ? "First term" : "While active" },
|
|
3573
3575
|
{ key: "mechanism", header: "Provider mechanism", cell: (row) => MECHANISM[row.mechanism] },
|
|
3574
3576
|
{ key: "supersededAt", header: "Superseded", cell: superseded },
|
|
3575
|
-
{ key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx38(Source, { row }) }
|
|
3577
|
+
{ key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx38(Source, { row }) },
|
|
3578
|
+
{ key: "members", header: "Existing members", cell: (row) => /* @__PURE__ */ jsx38(RetermAction, { row, getToken }) }
|
|
3576
3579
|
];
|
|
3580
|
+
var summary = (receipt) => {
|
|
3581
|
+
const skipped = receipt.skipped.reduce((acc, item) => ({ ...acc, [item.reason]: (acc[item.reason] ?? 0) + 1 }), {});
|
|
3582
|
+
const parts = [
|
|
3583
|
+
`moved ${receipt.retermed.length}`,
|
|
3584
|
+
`already on it ${receipt.unchanged.length}`,
|
|
3585
|
+
...Object.entries(skipped).map(([reason, count]) => `skipped ${count} (${reason.replaceAll("_", " ")})`)
|
|
3586
|
+
];
|
|
3587
|
+
return `${parts.join(" \xB7 ")}${receipt.more ? " \xB7 more remain, run again" : ""}`;
|
|
3588
|
+
};
|
|
3589
|
+
function RetermAction({ row, getToken }) {
|
|
3590
|
+
const [busy, setBusy] = useState31(false);
|
|
3591
|
+
const [result, setResult] = useState31(null);
|
|
3592
|
+
if (row.state !== "active" || row.mechanism !== "stripe_coupon_forever") return /* @__PURE__ */ jsx38("span", { className: "muted", children: row.state !== "active" ? "Not the active version" : "Benefit is not while-active" });
|
|
3593
|
+
const run = async () => {
|
|
3594
|
+
if (!globalThis.confirm?.(`Move every paid ${row.cohortLabel} member of ${row.chapterId} onto v${row.version}? Their next renewal invoice carries ${row.percentOffBps / 100}% off while they stay active. A member whose renewal this would raise is left alone.`)) return;
|
|
3595
|
+
setBusy(true);
|
|
3596
|
+
setResult(null);
|
|
3597
|
+
try {
|
|
3598
|
+
const receipt = await adminFetch(getToken, RETERM, { method: "POST", body: JSON.stringify({ chapterId: row.chapterId, policyVersionId: row.id }) });
|
|
3599
|
+
setResult({ tone: "good", text: summary(receipt) });
|
|
3600
|
+
} catch (error) {
|
|
3601
|
+
setResult({ tone: "danger", text: error instanceof Error ? error.message : String(error) });
|
|
3602
|
+
} finally {
|
|
3603
|
+
setBusy(false);
|
|
3604
|
+
}
|
|
3605
|
+
};
|
|
3606
|
+
return /* @__PURE__ */ jsxs34("span", { style: { display: "inline-flex", flexDirection: "column", gap: 4 }, children: [
|
|
3607
|
+
/* @__PURE__ */ jsx38(Button20, { mini: true, disabled: busy, onClick: run, children: busy ? "Moving\u2026" : "Move members to this benefit" }),
|
|
3608
|
+
result ? /* @__PURE__ */ jsx38("span", { role: result.tone === "good" ? "status" : "alert", className: result.tone === "danger" ? "danger" : "muted", children: result.text }) : null
|
|
3609
|
+
] });
|
|
3610
|
+
}
|
|
3577
3611
|
function MembershipPolicyVersions(props) {
|
|
3578
3612
|
const path = props.chapterId ? `${PATH}?chapterId=${encodeURIComponent(props.chapterId)}` : PATH;
|
|
3579
3613
|
const { data, loading, error } = useAdminResource(props.getToken, path);
|
|
@@ -3606,7 +3640,7 @@ function MembershipPolicyVersions(props) {
|
|
|
3606
3640
|
DataTable5,
|
|
3607
3641
|
{
|
|
3608
3642
|
rows: data.discountPolicies,
|
|
3609
|
-
columns: policyColumns,
|
|
3643
|
+
columns: policyColumns(props.getToken),
|
|
3610
3644
|
rowKey: (row) => row.id,
|
|
3611
3645
|
ariaLabel: "Discount policy versions",
|
|
3612
3646
|
emptyState: `No discount policy versions are recorded for ${scope}.`
|
|
@@ -3670,4 +3704,4 @@ export {
|
|
|
3670
3704
|
NetworkSeatBilling,
|
|
3671
3705
|
MembershipPolicyVersions
|
|
3672
3706
|
};
|
|
3673
|
-
//# sourceMappingURL=chunk-
|
|
3707
|
+
//# sourceMappingURL=chunk-PTC43J7F.js.map
|