@fluid-app/portal-sdk 0.1.484 → 0.1.485

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.
@@ -2600,6 +2600,19 @@ function getMaxEditableBillDate(billingInterval = 1, billingIntervalUnit = "mont
2600
2600
  if (maxDate.getDate() !== day) maxDate.setDate(0);
2601
2601
  return maxDate;
2602
2602
  }
2603
+ /**
2604
+ * Latest date a whole bundle's bill date may be edited to. Every member moves
2605
+ * to one shared date, so the strictest member binds: a bundle holding one
2606
+ * monthly plan is capped at 12 months even if its other members are yearly.
2607
+ *
2608
+ * An empty list falls back to the 12-month cap, which is also what a member of
2609
+ * unknown cadence resolves to via getMaxEditableBillDate's own default.
2610
+ */
2611
+ function getMaxEditableBillDateForBundle(members, from = /* @__PURE__ */ new Date()) {
2612
+ const [first, ...rest] = members.map((member) => getMaxEditableBillDate(member.billingInterval, member.billingIntervalUnit, from));
2613
+ if (!first) return getMaxEditableBillDate(1, "month", from);
2614
+ return rest.reduce((strictest, current) => current < strictest ? current : strictest, first);
2615
+ }
2603
2616
  function calculateNextBillDate(billingInterval = 1, billingIntervalUnit = "month") {
2604
2617
  const now = /* @__PURE__ */ new Date();
2605
2618
  const nextBillDate = new Date(now);
@@ -2620,6 +2633,36 @@ function calculateNextBillDate(billingInterval = 1, billingIntervalUnit = "month
2620
2633
  }
2621
2634
  return nextBillDate.toISOString().split("T")[0] || "";
2622
2635
  }
2636
+ /** Longest pause the dialogs have ever offered, before the cap narrows it. */
2637
+ const MAX_SKIPPABLE_ORDERS = 99;
2638
+ /**
2639
+ * How many upcoming orders a pause may skip: the largest count whose resume
2640
+ * date still lands inside the bill-date cap.
2641
+ *
2642
+ * Pausing for a number of orders sets a bill date like any other edit, so the
2643
+ * API caps the date it derives from that count, 2 years out for a yearly cadence
2644
+ * and 12 months for everything else, and rejects the request naming
2645
+ * number_of_orders. Offering counts past that offers a pause the API refuses.
2646
+ *
2647
+ * Returns 0 when even a single skip overshoots, which happens on a subscription
2648
+ * whose next bill date already sits near the cap. Callers should read that as
2649
+ * "pausing by order count is unavailable here" and leave the indefinite pause.
2650
+ *
2651
+ * The two ends deliberately measure from different places: the resume date
2652
+ * counts forward from the current bill date, the cap counts forward from today.
2653
+ * The API stays the backstop, so the exact boundary count depends on its
2654
+ * resume-date arithmetic agreeing with calculateResumeDate.
2655
+ */
2656
+ function getMaxSkippableOrders(billingInterval = 1, billingIntervalUnit = "month", currentNextBillDate, from = /* @__PURE__ */ new Date()) {
2657
+ const cap = getMaxEditableBillDate(billingInterval, billingIntervalUnit, from);
2658
+ let allowed = 0;
2659
+ for (let count = 1; count <= MAX_SKIPPABLE_ORDERS; count += 1) {
2660
+ const resume = calculateResumeDate(billingInterval, billingIntervalUnit, count, currentNextBillDate ?? from);
2661
+ if (new Date(resume.getUTCFullYear(), resume.getUTCMonth(), resume.getUTCDate()) > cap) break;
2662
+ allowed = count;
2663
+ }
2664
+ return allowed;
2665
+ }
2623
2666
  function formatDate(dateString, locale = "en-US") {
2624
2667
  if (!dateString) return "";
2625
2668
  const parts = (dateString.split("T")[0] ?? dateString).split("-");
@@ -4194,6 +4237,30 @@ function isPaymentMethodUsable(pm) {
4194
4237
  return Boolean(addr.address1 && addr.city && addr.state && addr.zip && addr.country_code);
4195
4238
  }
4196
4239
  //#endregion
4240
+ //#region src/screens/subscriptions/bundle-drawer/bill-date-bounds.ts
4241
+ function getDaysInMonth(year, month) {
4242
+ return new Date(year, month + 1, 0).getDate();
4243
+ }
4244
+ function isYearOutOfRange(year, { today, maxDate }) {
4245
+ return year < today.getFullYear() || year > maxDate.getFullYear();
4246
+ }
4247
+ function isMonthOutOfRange(year, month, bounds) {
4248
+ const { today, maxDate } = bounds;
4249
+ if (isYearOutOfRange(year, bounds)) return true;
4250
+ if (year === maxDate.getFullYear() && month > maxDate.getMonth()) return true;
4251
+ if (year > today.getFullYear()) return false;
4252
+ return month < today.getMonth();
4253
+ }
4254
+ function isDayOutOfRange(year, month, day, bounds) {
4255
+ const { today, maxDate } = bounds;
4256
+ if (day > getDaysInMonth(year, month)) return true;
4257
+ if (isMonthOutOfRange(year, month, bounds)) return true;
4258
+ if (year === maxDate.getFullYear() && month === maxDate.getMonth() && day > maxDate.getDate()) return true;
4259
+ if (year > today.getFullYear()) return false;
4260
+ if (month > today.getMonth()) return false;
4261
+ return day < today.getDate();
4262
+ }
4263
+ //#endregion
4197
4264
  //#region src/screens/subscriptions/bundle-drawer/use-bundle-form.ts
4198
4265
  function pickDefaultAddressId(addresses, defaultAddressId) {
4199
4266
  if (defaultAddressId != null) {
@@ -4214,11 +4281,11 @@ function pickDefaultPaymentMethodId(paymentMethods, defaultPaymentMethodId) {
4214
4281
  if (flagged) return flagged.id;
4215
4282
  return usable[0]?.id ?? null;
4216
4283
  }
4217
- function useBundleForm({ addresses, paymentMethods, defaultAddressId, defaultPaymentMethodId }) {
4284
+ function useBundleForm({ subscriptions: subscriptionList, addresses, paymentMethods, defaultAddressId, defaultPaymentMethodId }) {
4218
4285
  const [step, setStep] = (0, react.useState)(1);
4219
4286
  const [selectedTokens, setSelectedTokens] = (0, react.useState)(() => /* @__PURE__ */ new Set());
4220
4287
  const today = (0, react.useMemo)(() => /* @__PURE__ */ new Date(), []);
4221
- const [selectedYear, setYearState] = (0, react.useState)(today.getUTCFullYear());
4288
+ const [selectedYear, setYearState] = (0, react.useState)(today.getFullYear());
4222
4289
  const [selectedMonth, setMonthState] = (0, react.useState)(null);
4223
4290
  const [selectedDay, setDayState] = (0, react.useState)(null);
4224
4291
  const [selectedAddressId, setSelectedAddressId] = (0, react.useState)(() => pickDefaultAddressId(addresses, defaultAddressId));
@@ -4237,55 +4304,73 @@ function useBundleForm({ addresses, paymentMethods, defaultAddressId, defaultPay
4237
4304
  paymentMethods,
4238
4305
  defaultPaymentMethodId
4239
4306
  ]);
4307
+ const toggleToken = (0, react.useCallback)((token) => {
4308
+ setSelectedTokens((prev) => {
4309
+ const next = new Set(prev);
4310
+ if (next.has(token)) next.delete(token);
4311
+ else next.add(token);
4312
+ return next;
4313
+ });
4314
+ }, []);
4315
+ const bounds = (0, react.useMemo)(() => {
4316
+ return {
4317
+ today,
4318
+ maxDate: getMaxEditableBillDateForBundle(Array.from(selectedTokens).map((token) => {
4319
+ const plan = subscriptionList.find((sub) => sub.subscription_token === token)?.subscription_plan;
4320
+ return {
4321
+ billingInterval: plan?.billing_interval,
4322
+ billingIntervalUnit: plan?.billing_interval_unit
4323
+ };
4324
+ }), today)
4325
+ };
4326
+ }, [
4327
+ selectedTokens,
4328
+ subscriptionList,
4329
+ today
4330
+ ]);
4331
+ const setYear = (0, react.useCallback)((y) => {
4332
+ setYearState(y);
4333
+ if (selectedMonth == null) return;
4334
+ if (isMonthOutOfRange(y, selectedMonth, bounds)) {
4335
+ setMonthState(null);
4336
+ setDayState(null);
4337
+ return;
4338
+ }
4339
+ if (selectedDay != null && isDayOutOfRange(y, selectedMonth, selectedDay, bounds)) setDayState(null);
4340
+ }, [
4341
+ selectedMonth,
4342
+ selectedDay,
4343
+ bounds
4344
+ ]);
4345
+ const setMonth = (0, react.useCallback)((m) => {
4346
+ setMonthState(m);
4347
+ if (selectedDay != null && isDayOutOfRange(selectedYear, m, selectedDay, bounds)) setDayState(null);
4348
+ }, [
4349
+ selectedDay,
4350
+ selectedYear,
4351
+ bounds
4352
+ ]);
4353
+ const setDay = (0, react.useCallback)((d) => setDayState(d), []);
4354
+ const canProceedFromStep1 = selectedTokens.size >= 2;
4355
+ const selectedDateOutOfRange = selectedMonth !== null && selectedDay !== null && isDayOutOfRange(selectedYear, selectedMonth, selectedDay, bounds);
4240
4356
  return {
4241
4357
  step,
4242
4358
  setStep,
4243
4359
  selectedTokens,
4244
- toggleToken: (0, react.useCallback)((token) => {
4245
- setSelectedTokens((prev) => {
4246
- const next = new Set(prev);
4247
- if (next.has(token)) next.delete(token);
4248
- else next.add(token);
4249
- return next;
4250
- });
4251
- }, []),
4360
+ toggleToken,
4252
4361
  selectedYear,
4253
4362
  selectedMonth,
4254
4363
  selectedDay,
4255
- setYear: (0, react.useCallback)((y) => {
4256
- setYearState(y);
4257
- if (selectedMonth != null) {
4258
- const isPastMonth = y < today.getUTCFullYear() || y === today.getUTCFullYear() && selectedMonth < today.getUTCMonth();
4259
- const isPastDay = y === today.getUTCFullYear() && selectedMonth === today.getUTCMonth() && selectedDay != null && selectedDay < today.getUTCDate();
4260
- if (isPastMonth) {
4261
- setMonthState(null);
4262
- setDayState(null);
4263
- } else if (isPastDay) setDayState(null);
4264
- }
4265
- }, [
4266
- selectedMonth,
4267
- selectedDay,
4268
- today
4269
- ]),
4270
- setMonth: (0, react.useCallback)((m) => {
4271
- setMonthState(m);
4272
- if (selectedDay != null) {
4273
- const daysInNewMonth = new Date(Date.UTC(selectedYear, m + 1, 0)).getUTCDate();
4274
- const isPastDay = selectedYear === today.getUTCFullYear() && m === today.getUTCMonth() && selectedDay < today.getUTCDate();
4275
- if (selectedDay > daysInNewMonth || isPastDay) setDayState(null);
4276
- }
4277
- }, [
4278
- selectedDay,
4279
- selectedYear,
4280
- today
4281
- ]),
4282
- setDay: (0, react.useCallback)((d) => setDayState(d), []),
4364
+ setYear,
4365
+ setMonth,
4366
+ setDay,
4283
4367
  selectedAddressId,
4284
4368
  setSelectedAddressId,
4285
4369
  selectedPaymentMethodId,
4286
4370
  setSelectedPaymentMethodId,
4287
- canProceedFromStep1: selectedTokens.size >= 2,
4288
- canProceedFromStep2: selectedMonth !== null && selectedDay !== null,
4371
+ bounds,
4372
+ canProceedFromStep1,
4373
+ canProceedFromStep2: selectedMonth !== null && selectedDay !== null && !selectedDateOutOfRange,
4289
4374
  canProceedFromStep3: selectedAddressId != null,
4290
4375
  canProceedFromStep4: selectedPaymentMethodId != null,
4291
4376
  formattedDate: (0, react.useMemo)(() => {
@@ -4514,34 +4599,20 @@ const MONTHS = [
4514
4599
  "Nov",
4515
4600
  "Dec"
4516
4601
  ];
4517
- function getDaysInMonth(year, month) {
4518
- return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
4519
- }
4520
- function DrawerStepDate({ selectedYear, selectedMonth, selectedDay, onYearChange, onMonthChange, onDayChange }) {
4602
+ function DrawerStepDate({ selectedYear, selectedMonth, selectedDay, onYearChange, onMonthChange, onDayChange, bounds }) {
4521
4603
  const { t } = useSubscriptionsTranslation();
4522
- const today = (0, react.useMemo)(() => /* @__PURE__ */ new Date(), []);
4523
- const currentYear = today.getUTCFullYear();
4524
- const currentMonth = today.getUTCMonth();
4525
- const currentDay = today.getUTCDate();
4604
+ const currentYear = bounds.today.getFullYear();
4526
4605
  const availableYears = (0, react.useMemo)(() => [
4527
4606
  currentYear,
4528
4607
  currentYear + 1,
4529
4608
  currentYear + 2
4530
4609
  ], [currentYear]);
4531
- const isMonthDisabled = (monthIndex) => {
4532
- if (selectedYear < currentYear) return true;
4533
- if (selectedYear > currentYear) return false;
4534
- return monthIndex < currentMonth;
4535
- };
4610
+ const isYearDisabled = (year) => isYearOutOfRange(year, bounds);
4611
+ const isMonthDisabled = (monthIndex) => isMonthOutOfRange(selectedYear, monthIndex, bounds);
4536
4612
  const daysInSelectedMonth = selectedMonth == null ? 31 : getDaysInMonth(selectedYear, selectedMonth);
4537
4613
  const isDayDisabled = (day) => {
4538
4614
  if (selectedMonth == null) return true;
4539
- if (day > getDaysInMonth(selectedYear, selectedMonth)) return true;
4540
- if (selectedYear < currentYear) return true;
4541
- if (selectedYear > currentYear) return false;
4542
- if (selectedMonth > currentMonth) return false;
4543
- if (selectedMonth < currentMonth) return true;
4544
- return day < currentDay;
4615
+ return isDayOutOfRange(selectedYear, selectedMonth, day, bounds);
4545
4616
  };
4546
4617
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4547
4618
  className: "flex-1 overflow-y-auto px-6 py-4",
@@ -4564,10 +4635,13 @@ function DrawerStepDate({ selectedYear, selectedMonth, selectedDay, onYearChange
4564
4635
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4565
4636
  className: "grid grid-cols-3 gap-2",
4566
4637
  children: availableYears.map((year) => {
4638
+ const isActive = selectedYear === year;
4639
+ const disabled = isYearDisabled(year);
4567
4640
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4568
4641
  type: "button",
4642
+ disabled,
4569
4643
  onClick: () => onYearChange(year),
4570
- className: `rounded-lg px-3 py-2.5 text-xs font-semibold transition-all ${selectedYear === year ? "bg-primary text-primary-foreground" : "text-foreground ring-border hover:ring-foreground/20 bg-transparent ring-1"}`,
4644
+ className: `rounded-lg px-3 py-2.5 text-xs font-semibold transition-all ${isActive ? "bg-primary text-primary-foreground" : "text-foreground ring-border hover:ring-foreground/20 bg-transparent ring-1"} ${disabled ? "cursor-not-allowed opacity-40" : ""}`,
4571
4645
  children: year
4572
4646
  }, year);
4573
4647
  })
@@ -4811,6 +4885,7 @@ function BundleSubscriptionsDrawer({ open, onOpenChange, subscriptions: subscrip
4811
4885
  const { t } = useSubscriptionsTranslation();
4812
4886
  const { isMobile } = require_sidebar.useSidebar();
4813
4887
  const form = useBundleForm({
4888
+ subscriptions: subscriptionList,
4814
4889
  addresses,
4815
4890
  paymentMethods,
4816
4891
  defaultAddressId,
@@ -4855,7 +4930,8 @@ function BundleSubscriptionsDrawer({ open, onOpenChange, subscriptions: subscrip
4855
4930
  selectedDay: form.selectedDay,
4856
4931
  onYearChange: form.setYear,
4857
4932
  onMonthChange: form.setMonth,
4858
- onDayChange: form.setDay
4933
+ onDayChange: form.setDay,
4934
+ bounds: form.bounds
4859
4935
  }),
4860
4936
  form.step === 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerStepAddress, {
4861
4937
  addresses,
@@ -5416,7 +5492,8 @@ function PauseSubscriptionDialog({ open, onOpenChange, onConfirm, isLoading, err
5416
5492
  const { t, locale } = useSubscriptionsTranslation();
5417
5493
  const [pauseType, setPauseType] = (0, react.useState)("indefinite");
5418
5494
  const [selectedOrderCount, setSelectedOrderCount] = (0, react.useState)(null);
5419
- const orderCountOptions = Array.from({ length: 99 }, (_, i) => i + 1);
5495
+ const maxSkippableOrders = getMaxSkippableOrders(billingInterval, billingIntervalUnit, currentNextBillDate);
5496
+ const orderCountOptions = Array.from({ length: maxSkippableOrders }, (_, i) => i + 1);
5420
5497
  const calculatedResumeDate = selectedOrderCount && currentNextBillDate ? calculateResumeDate(billingInterval, billingIntervalUnit, selectedOrderCount, currentNextBillDate) : null;
5421
5498
  (0, react.useEffect)(() => {
5422
5499
  if (!open) return;
@@ -5496,10 +5573,11 @@ function PauseSubscriptionDialog({ open, onOpenChange, onConfirm, isLoading, err
5496
5573
  type: "radio",
5497
5574
  checked: pauseType === "order_count",
5498
5575
  onChange: () => setPauseType("order_count"),
5499
- className: "border-border accent-primary size-4 focus:ring-0"
5576
+ disabled: maxSkippableOrders === 0,
5577
+ className: "border-border accent-primary size-4 focus:ring-0 disabled:opacity-40"
5500
5578
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Label, {
5501
5579
  htmlFor: "pause-order-count",
5502
- className: "text-foreground cursor-pointer text-sm font-medium",
5580
+ className: `text-sm font-medium ${maxSkippableOrders === 0 ? "text-muted-foreground cursor-not-allowed" : "text-foreground cursor-pointer"}`,
5503
5581
  children: t("pause_for_orders")
5504
5582
  })]
5505
5583
  }), pauseType === "order_count" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -8615,4 +8693,4 @@ Object.defineProperty(exports, "subscriptionsScreenPropertySchema", {
8615
8693
  }
8616
8694
  });
8617
8695
 
8618
- //# sourceMappingURL=SubscriptionsScreen-DPymXVC4.cjs.map
8696
+ //# sourceMappingURL=SubscriptionsScreen-9NJY-K63.cjs.map