@usethrottle/auth 0.6.1 → 0.8.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/dist/index.d.cts CHANGED
@@ -96,7 +96,10 @@ interface Order {
96
96
  billingAddress: unknown | null;
97
97
  shippingAddress: unknown | null;
98
98
  discountCode: string | null;
99
+ /** Set only when the order is linked to exactly one subscription. Read `subscriptionIds`. */
99
100
  subscriptionId: string | null;
101
+ /** Every subscription this order created, in line order. One cart can hold several. */
102
+ subscriptionIds: string[];
100
103
  /**
101
104
  * The invoice raised for this order, when one exists. Resolved on the
102
105
  * detail read only — the list would need a lookup per row.
@@ -120,6 +123,8 @@ interface OrderLineItem {
120
123
  discountAmount: number;
121
124
  total: number;
122
125
  imageUrl: string | null;
126
+ /** The subscription this line created, or null for a one-time line. */
127
+ subscriptionId: string | null;
123
128
  }
124
129
  /**
125
130
  * What `getOrder` returns. Deliberately NOT folded into `Order`: the list route
@@ -175,6 +180,21 @@ interface Subscription {
175
180
  totalPayments: number | null;
176
181
  paymentsMade: number;
177
182
  completedAt: string | null;
183
+ /** Installment plans only: every payment in order; dueAt is an estimate. */
184
+ paymentSchedule?: Array<{
185
+ index: number;
186
+ amount: number;
187
+ discount: number;
188
+ net: number;
189
+ intervalUnit: string | null;
190
+ intervalCount: number | null;
191
+ status: 'paid' | 'due' | 'upcoming';
192
+ dueAt: string | null;
193
+ }> | null;
194
+ /** Installment plans only: the sum of every payment's net amount. */
195
+ scheduleTotal?: number | null;
196
+ /** Installment plans only: what the next payment will charge; null once complete. */
197
+ nextPaymentAmount?: number | null;
178
198
  amount: number;
179
199
  quantity: number;
180
200
  currency: string;
package/dist/index.d.ts CHANGED
@@ -96,7 +96,10 @@ interface Order {
96
96
  billingAddress: unknown | null;
97
97
  shippingAddress: unknown | null;
98
98
  discountCode: string | null;
99
+ /** Set only when the order is linked to exactly one subscription. Read `subscriptionIds`. */
99
100
  subscriptionId: string | null;
101
+ /** Every subscription this order created, in line order. One cart can hold several. */
102
+ subscriptionIds: string[];
100
103
  /**
101
104
  * The invoice raised for this order, when one exists. Resolved on the
102
105
  * detail read only — the list would need a lookup per row.
@@ -120,6 +123,8 @@ interface OrderLineItem {
120
123
  discountAmount: number;
121
124
  total: number;
122
125
  imageUrl: string | null;
126
+ /** The subscription this line created, or null for a one-time line. */
127
+ subscriptionId: string | null;
123
128
  }
124
129
  /**
125
130
  * What `getOrder` returns. Deliberately NOT folded into `Order`: the list route
@@ -175,6 +180,21 @@ interface Subscription {
175
180
  totalPayments: number | null;
176
181
  paymentsMade: number;
177
182
  completedAt: string | null;
183
+ /** Installment plans only: every payment in order; dueAt is an estimate. */
184
+ paymentSchedule?: Array<{
185
+ index: number;
186
+ amount: number;
187
+ discount: number;
188
+ net: number;
189
+ intervalUnit: string | null;
190
+ intervalCount: number | null;
191
+ status: 'paid' | 'due' | 'upcoming';
192
+ dueAt: string | null;
193
+ }> | null;
194
+ /** Installment plans only: the sum of every payment's net amount. */
195
+ scheduleTotal?: number | null;
196
+ /** Installment plans only: what the next payment will charge; null once complete. */
197
+ nextPaymentAmount?: number | null;
178
198
  amount: number;
179
199
  quantity: number;
180
200
  currency: string;
@@ -780,6 +780,7 @@ var THROTTLE_ELEMENT_KEYS = [
780
780
  "orderDetail.cancelButton",
781
781
  "orderDetail.returnButton",
782
782
  "orderDetail.invoiceButton",
783
+ "orderDetail.subscriptionButton",
783
784
  "orderDetail.actions",
784
785
  "invoiceDetail.layout",
785
786
  "invoiceDetail.main",
@@ -1187,7 +1188,8 @@ var enUS = {
1187
1188
  quantity: "Qty",
1188
1189
  shippingAddress: "Shipping address",
1189
1190
  billingAddress: "Billing address",
1190
- viewSubscription: "View subscription"
1191
+ viewSubscription: "View subscription",
1192
+ viewSubscriptionFor: "View subscription for"
1191
1193
  },
1192
1194
  invoices: {
1193
1195
  empty: "No invoices yet",
@@ -1226,6 +1228,11 @@ var enUS = {
1226
1228
  cancelledOn: "Cancelled on",
1227
1229
  trialEnds: "Trial ends",
1228
1230
  installmentProgress: "Payment {paid} of {total}",
1231
+ installmentTotal: "{amount} total",
1232
+ installmentNext: "Next {amount} on {date}",
1233
+ /** Spec §5: a varied schedule's dated list; dates are estimates, hence "~". */
1234
+ upcomingPayments: "Upcoming payments",
1235
+ upcomingPayment: "{amount} \xB7 ~{date}",
1229
1236
  cancelButton: "Cancel",
1230
1237
  cancelAtPeriodEnd: "Cancel at period end",
1231
1238
  cancelNow: "Cancel immediately",
@@ -3862,6 +3869,16 @@ function OrderTimeline(props) {
3862
3869
  ] });
3863
3870
  }
3864
3871
 
3872
+ // src/react/components/account/order-subscription-links.ts
3873
+ function orderSubscriptionLinks(order, labels) {
3874
+ const ids = order.subscriptionIds?.length ? order.subscriptionIds : order.subscriptionId ? [order.subscriptionId] : [];
3875
+ if (ids.length === 1) return [{ id: ids[0], label: labels.single }];
3876
+ return ids.map((id, index) => {
3877
+ const name = order.lineItems.find((li) => li.subscriptionId === id)?.name;
3878
+ return { id, label: name ? `${labels.named} ${name}` : `${labels.single} ${index + 1}` };
3879
+ });
3880
+ }
3881
+
3865
3882
  // src/react/components/account/OrderDetail.tsx
3866
3883
  var import_jsx_runtime39 = require("react/jsx-runtime");
3867
3884
  var CAN_PAY = /* @__PURE__ */ new Set(["draft", "pending"]);
@@ -4022,14 +4039,19 @@ function OrderDetail(props) {
4022
4039
  children: t.orders.reorder
4023
4040
  }
4024
4041
  ) : null,
4025
- order.subscriptionId && props.onViewSubscription ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4042
+ props.onViewSubscription ? orderSubscriptionLinks(order, {
4043
+ single: t.orders.viewSubscription,
4044
+ named: t.orders.viewSubscriptionFor
4045
+ }).map((link) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4026
4046
  Button,
4027
4047
  {
4028
4048
  variant: "secondary",
4029
- onClick: () => props.onViewSubscription(order.subscriptionId),
4030
- children: t.orders.viewSubscription
4031
- }
4032
- ) : null,
4049
+ elementKey: "orderDetail.subscriptionButton",
4050
+ onClick: () => props.onViewSubscription(link.id),
4051
+ children: link.label
4052
+ },
4053
+ link.id
4054
+ )) : null,
4033
4055
  showReturn ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4034
4056
  Button,
4035
4057
  {
@@ -4397,6 +4419,18 @@ function isIntervalUnit(value) {
4397
4419
  return typeof value === "string" && INTERVAL_UNITS.includes(value);
4398
4420
  }
4399
4421
 
4422
+ // ../core-cadence/src/schedule.ts
4423
+ function isVariedSchedule(rows) {
4424
+ if (!rows || rows.length === 0) return false;
4425
+ return rows.some(
4426
+ (r, i) => r.discount !== 0 || r.amount !== rows[0].amount || i > 1 && (r.intervalUnit !== rows[1].intervalUnit || r.intervalCount !== rows[1].intervalCount)
4427
+ );
4428
+ }
4429
+ function upcomingSchedulePayments(rows) {
4430
+ if (!rows || !isVariedSchedule(rows)) return [];
4431
+ return rows.filter((r) => r.status !== "paid" && r.dueAt != null);
4432
+ }
4433
+
4400
4434
  // ../core-cadence/src/labels.ts
4401
4435
  var NAMED_NOUN = {
4402
4436
  weekly: "week",
@@ -4412,6 +4446,10 @@ function cadenceNoun(cadence) {
4412
4446
  }
4413
4447
  return cadence.interval;
4414
4448
  }
4449
+ function scheduleDeal(sub) {
4450
+ if (!sub.paymentSchedule || sub.scheduleTotal == null || !isVariedSchedule(sub.paymentSchedule)) return null;
4451
+ return { total: sub.scheduleTotal, payments: sub.paymentSchedule.length };
4452
+ }
4415
4453
 
4416
4454
  // src/react/components/account/SubscriptionsPanel.tsx
4417
4455
  var import_jsx_runtime43 = require("react/jsx-runtime");
@@ -4438,7 +4476,20 @@ function SubscriptionsPanel(props) {
4438
4476
  await run(fn);
4439
4477
  })();
4440
4478
  };
4441
- const priceLabel = (sub) => `${formatMoney(sub.amount, sub.currency, locale)} / ${cadenceNoun(sub)}${sub.quantity > 1 ? ` \xD7 ${sub.quantity}` : ""}`;
4479
+ const priceLabel = (sub) => {
4480
+ const deal = scheduleDeal(sub);
4481
+ return deal ? t.subscriptions.installmentTotal.replace("{amount}", formatMoney(deal.total, sub.currency, locale)) : `${formatMoney(sub.amount, sub.currency, locale)} / ${cadenceNoun(sub)}${sub.quantity > 1 ? ` \xD7 ${sub.quantity}` : ""}`;
4482
+ };
4483
+ const nextPaymentLabel = (sub) => {
4484
+ if (!isVariedSchedule(sub.paymentSchedule) || !sub.paymentSchedule || sub.nextPaymentAmount == null) return null;
4485
+ const upcoming = sub.paymentSchedule.find((r) => r.status !== "paid");
4486
+ return t.subscriptions.installmentNext.replace("{amount}", formatMoney(sub.nextPaymentAmount, sub.currency, locale)).replace("{date}", formatDate(upcoming?.dueAt ?? sub.currentPeriodEnd, locale));
4487
+ };
4488
+ const upcomingList = (sub) => {
4489
+ const upcoming = upcomingSchedulePayments(sub.paymentSchedule);
4490
+ if (upcoming.length === 0) return null;
4491
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className: "throttle-muted", "aria-label": t.subscriptions.upcomingPayments, children: upcoming.map((r) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { children: t.subscriptions.upcomingPayment.replace("{amount}", formatMoney(r.net, sub.currency, locale)).replace("{date}", formatDate(r.dueAt, locale)) }, r.dueAt)) });
4492
+ };
4442
4493
  const renewsLabel = (sub) => formatDate(
4443
4494
  sub.status === "completed" ? sub.completedAt ?? sub.currentPeriodEnd : sub.status === "cancelled" ? sub.cancelledAt ?? sub.currentPeriodEnd : sub.currentPeriodEnd,
4444
4495
  locale
@@ -4520,7 +4571,9 @@ function SubscriptionsPanel(props) {
4520
4571
  columns: [
4521
4572
  { key: "plan", label: t.subscriptions.columnPlan, render: (sub) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
4522
4573
  sub.planName ?? sub.planReference,
4523
- sub.totalPayments != null ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "throttle-muted", children: t.subscriptions.installmentProgress.replace("{paid}", String(Math.min(sub.paymentsMade ?? 0, sub.totalPayments))).replace("{total}", String(sub.totalPayments)) }) : null
4574
+ sub.totalPayments != null ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "throttle-muted", children: t.subscriptions.installmentProgress.replace("{paid}", String(Math.min(sub.paymentsMade ?? 0, sub.totalPayments))).replace("{total}", String(sub.totalPayments)) }) : null,
4575
+ nextPaymentLabel(sub) ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "throttle-muted", children: nextPaymentLabel(sub) }) : null,
4576
+ upcomingList(sub)
4524
4577
  ] }) },
4525
4578
  { key: "price", label: t.subscriptions.columnPrice, render: (sub) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "throttle-table__money", children: priceLabel(sub) }) },
4526
4579
  { key: "renews", label: t.subscriptions.columnRenews, render: (sub) => renewsLabel(sub) },
@@ -4538,12 +4591,9 @@ function SubscriptionsPanel(props) {
4538
4591
  /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(StatusBadge, { status: sub.status, labels: t.subscriptions.statusLabels })
4539
4592
  ] }),
4540
4593
  sub.totalPayments != null ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "throttle-muted", children: t.subscriptions.installmentProgress.replace("{paid}", String(Math.min(sub.paymentsMade ?? 0, sub.totalPayments))).replace("{total}", String(sub.totalPayments)) }) : null,
4541
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { className: "throttle-muted", children: [
4542
- formatMoney(sub.amount, sub.currency, locale),
4543
- " / ",
4544
- cadenceNoun(sub),
4545
- sub.quantity > 1 ? ` \xD7 ${sub.quantity}` : ""
4546
- ] }),
4594
+ nextPaymentLabel(sub) ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "throttle-muted", children: nextPaymentLabel(sub) }) : null,
4595
+ upcomingList(sub),
4596
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "throttle-muted", children: priceLabel(sub) }),
4547
4597
  sub.status === "completed" ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { className: "throttle-muted", children: [
4548
4598
  t.subscriptions.statusLabels.completed,
4549
4599
  " ",