@bunnyapp/components 1.7.0-beta.15 → 1.7.0-beta.18

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/cjs/index.js CHANGED
@@ -1280,7 +1280,7 @@ const DEFAULT_CONFIG = {
1280
1280
  };
1281
1281
 
1282
1282
  // This will be replaced at build time by rollup-plugin-replace
1283
- const PACKAGE_VERSION = '1.7.0-beta.14';
1283
+ const PACKAGE_VERSION = '1.7.0-beta.17';
1284
1284
  const createRequestHeaders = (token) => {
1285
1285
  const headers = createClientDevHeaders({ token });
1286
1286
  // Add the components version header
@@ -24512,6 +24512,225 @@ const hasPriceTiers = (charge) => {
24512
24512
  var _a;
24513
24513
  return Boolean((_a = charge === null || charge === void 0 ? void 0 : charge.priceTiers) === null || _a === void 0 ? void 0 : _a.length);
24514
24514
  };
24515
+ const splitSubscriptionsByPriceList_SubscriptionFragment = t(`
24516
+ fragment splitSubscriptionsByPriceList_SubscriptionFragment on Subscription {
24517
+ id
24518
+ state
24519
+ evergreen
24520
+ startDate
24521
+ endDate
24522
+ addonSubscriptions {
24523
+ id
24524
+ priceList {
24525
+ id
24526
+ }
24527
+ plan {
24528
+ id
24529
+ }
24530
+ }
24531
+ chargeReport {
24532
+ kind
24533
+ startDate
24534
+ endDate
24535
+ priceList {
24536
+ id
24537
+ plan {
24538
+ id
24539
+ name
24540
+ selfServiceBuy
24541
+ selfServiceCancel
24542
+ selfServiceRenew
24543
+ addon
24544
+ addonPlans {
24545
+ id
24546
+ }
24547
+ }
24548
+ product {
24549
+ id
24550
+ name
24551
+ showProductNameOnLineItem
24552
+ }
24553
+ periodMonths
24554
+ name
24555
+ }
24556
+ }
24557
+ charges {
24558
+ priceList {
24559
+ id
24560
+ }
24561
+ }
24562
+ }
24563
+ `, []);
24564
+ // Helper to safely get date as string
24565
+ const getDateString = (date) => {
24566
+ return date;
24567
+ };
24568
+ /**
24569
+ * Splits a subscription into multiple subscriptions if it has RENEW charges
24570
+ * with different price lists.
24571
+ * This handles cases where a subscription has been changed to a different plan/price list.
24572
+ * Each resulting subscription is a complete duplicate with just the charges filtered.
24573
+ */
24574
+ function splitSubscriptionsByPriceList(maskedSubscriptions) {
24575
+ const result = [];
24576
+ maskedSubscriptions.forEach(maskedSubscription => {
24577
+ var _a;
24578
+ // Read the fragment to get typed data
24579
+ const subscription = readFragment(splitSubscriptionsByPriceList_SubscriptionFragment, maskedSubscription);
24580
+ // Skip if no chargeReport
24581
+ if (!subscription.chargeReport) {
24582
+ result.push(maskedSubscription);
24583
+ return;
24584
+ }
24585
+ // Group chargeReport by priceListId
24586
+ const chargesByPriceList = new Map();
24587
+ subscription.chargeReport.forEach(charge => {
24588
+ var _a;
24589
+ const priceListId = (_a = charge.priceList) === null || _a === void 0 ? void 0 : _a.id;
24590
+ if (!priceListId)
24591
+ return;
24592
+ if (!chargesByPriceList.has(priceListId)) {
24593
+ chargesByPriceList.set(priceListId, []);
24594
+ }
24595
+ chargesByPriceList.get(priceListId).push(charge);
24596
+ });
24597
+ // Check if there are RENEW charges with different price lists
24598
+ const hasRenewChargesWithDifferentPriceLists = chargesByPriceList.size > 1 &&
24599
+ subscription.chargeReport.some(charge => charge.kind === 'RENEW');
24600
+ // If all charges have the same priceListId or no RENEW charges, keep subscription as-is
24601
+ if (!hasRenewChargesWithDifferentPriceLists) {
24602
+ result.push(maskedSubscription);
24603
+ }
24604
+ else {
24605
+ // Find the charge with the latest endDate to determine which subscription will actually renew
24606
+ const latestCharge = subscription.chargeReport.reduce((latest, current) => {
24607
+ if (!latest)
24608
+ return current;
24609
+ // Compare endDates, and if equal, compare startDates
24610
+ const currentEnd = getDateString(current.endDate);
24611
+ const latestEnd = getDateString(latest.endDate);
24612
+ const currentStart = getDateString(current.startDate);
24613
+ const latestStart = getDateString(latest.startDate);
24614
+ if (currentEnd > latestEnd)
24615
+ return current;
24616
+ if (currentEnd === latestEnd && currentStart > latestStart)
24617
+ return current;
24618
+ return latest;
24619
+ }, subscription.chargeReport[0]);
24620
+ const latestPriceListId = (_a = latestCharge.priceList) === null || _a === void 0 ? void 0 : _a.id;
24621
+ if (!latestPriceListId) {
24622
+ result.push(maskedSubscription);
24623
+ return;
24624
+ }
24625
+ // Split into multiple subscriptions, one per price list
24626
+ // DUPLICATE the entire subscription, just filter the charges
24627
+ const splitSubscriptions = [];
24628
+ chargesByPriceList.forEach((charges, priceListId) => {
24629
+ var _a, _b, _c, _d, _e, _f;
24630
+ // Get the first charge to extract priceList, plan, and product info
24631
+ const firstCharge = charges === null || charges === void 0 ? void 0 : charges[0];
24632
+ if (!firstCharge || !charges)
24633
+ return;
24634
+ // Find the latest endDate from the charges in this group
24635
+ const latestEndDate = charges.reduce((latest, current) => {
24636
+ const currentEnd = getDateString(current.endDate);
24637
+ if (!latest)
24638
+ return currentEnd;
24639
+ return currentEnd > latest ? currentEnd : latest;
24640
+ }, getDateString(charges[0].endDate));
24641
+ // Find the earliest startDate from the charges in this group
24642
+ const earliestStartDate = charges.reduce((earliest, current) => {
24643
+ const currentStart = getDateString(current.startDate);
24644
+ if (!earliest)
24645
+ return currentStart;
24646
+ return currentStart < earliest ? currentStart : earliest;
24647
+ }, getDateString(charges[0].startDate));
24648
+ // Determine the state based on the date range of charges
24649
+ const now = new Date().toISOString().split('T')[0]; // Get current date in YYYY-MM-DD format
24650
+ let state = subscription.state;
24651
+ // If the latest endDate is before today, it's expired
24652
+ if (latestEndDate < now) {
24653
+ state = t.scalar('SubscriptionState', 'EXPIRED');
24654
+ }
24655
+ // If the earliest startDate is after today, it's pending
24656
+ else if (earliestStartDate > now) {
24657
+ state = t.scalar('SubscriptionState', 'PENDING');
24658
+ }
24659
+ // Otherwise, if current date is within the range, keep it active
24660
+ else if (earliestStartDate <= now && latestEndDate >= now) {
24661
+ state = t.scalar('SubscriptionState', 'ACTIVE');
24662
+ }
24663
+ // Only the subscription with the latest charge will actually renew
24664
+ const willRenew = priceListId === latestPriceListId;
24665
+ // Filter addon subscriptions to only include those compatible with the new plan
24666
+ // If the plan has changed, we need to check if the addon subscriptions are still valid
24667
+ const newPlan = (_a = firstCharge.priceList) === null || _a === void 0 ? void 0 : _a.plan;
24668
+ const addonPlanIds = (_c = (_b = newPlan === null || newPlan === void 0 ? void 0 : newPlan.addonPlans) === null || _b === void 0 ? void 0 : _b.map(plan => plan.id)) !== null && _c !== void 0 ? _c : [];
24669
+ // For split subscriptions that represent a future period (after plan change),
24670
+ // filter out addon subscriptions that are not compatible with the new plan
24671
+ const filteredAddonSubscriptions = (_d = subscription.addonSubscriptions) === null || _d === void 0 ? void 0 : _d.filter(addonSub => {
24672
+ var _a;
24673
+ // If the new plan has no addon plans defined, keep all addons (backward compatibility)
24674
+ if (!(newPlan === null || newPlan === void 0 ? void 0 : newPlan.addonPlans) || newPlan.addonPlans.length === 0) {
24675
+ return true;
24676
+ }
24677
+ // Check if this addon subscription's plan is in the new plan's addonPlans list
24678
+ const addonPlanId = (_a = addonSub.plan) === null || _a === void 0 ? void 0 : _a.id;
24679
+ return addonPlanId ? addonPlanIds.includes(addonPlanId) : false;
24680
+ });
24681
+ splitSubscriptions.push({
24682
+ ...maskedSubscription,
24683
+ // Keep the original subscription ID intact (from maskedSubscription spread)
24684
+ // Update priceList from the charge
24685
+ priceList: firstCharge.priceList,
24686
+ // Update plan from the charge's priceList.plan
24687
+ plan: firstCharge.priceList.plan,
24688
+ // Update product from the charge's priceList.product
24689
+ product: firstCharge.priceList.product,
24690
+ // Update endDate to match the latest charge endDate in this group
24691
+ endDate: latestEndDate,
24692
+ // Update startDate to match the earliest charge startDate in this group
24693
+ startDate: earliestStartDate,
24694
+ // Update state based on the charge dates
24695
+ state,
24696
+ // Set evergreen to false for subscriptions that won't renew (will transition instead)
24697
+ evergreen: willRenew ? subscription.evergreen : false,
24698
+ // Filter chargeReport to only include charges for this price list
24699
+ chargeReport: charges,
24700
+ // Filter charges array to only include charges for this price list
24701
+ charges: (_f = (_e = subscription.charges) === null || _e === void 0 ? void 0 : _e.filter(charge => {
24702
+ var _a;
24703
+ const chargeWithPriceList = charge;
24704
+ return ((_a = chargeWithPriceList.priceList) === null || _a === void 0 ? void 0 : _a.id) === priceListId;
24705
+ })) !== null && _f !== void 0 ? _f : [],
24706
+ // Filter addon subscriptions to only include those compatible with the new plan
24707
+ addonSubscriptions: filteredAddonSubscriptions,
24708
+ });
24709
+ });
24710
+ // Sort split subscriptions by endDate (earliest first)
24711
+ splitSubscriptions.sort((a, b) => {
24712
+ const aData = readFragment(splitSubscriptionsByPriceList_SubscriptionFragment, a);
24713
+ const bData = readFragment(splitSubscriptionsByPriceList_SubscriptionFragment, b);
24714
+ const aEnd = getDateString(aData.endDate);
24715
+ const bEnd = getDateString(bData.endDate);
24716
+ const aStart = getDateString(aData.startDate);
24717
+ const bStart = getDateString(bData.startDate);
24718
+ if (aEnd < bEnd)
24719
+ return -1;
24720
+ if (aEnd > bEnd)
24721
+ return 1;
24722
+ // If endDates are equal, sort by startDate
24723
+ if (aStart < bStart)
24724
+ return -1;
24725
+ if (aStart > bStart)
24726
+ return 1;
24727
+ return 0;
24728
+ });
24729
+ result.push(...splitSubscriptions);
24730
+ }
24731
+ });
24732
+ return result;
24733
+ }
24515
24734
 
24516
24735
  const SubscriptionCardActions_PriceListChangeOptionsFragment = t(`
24517
24736
  fragment SubscriptionCardActions_PriceListChangeOptionsFragment on PriceListChangeOptions {
@@ -25413,12 +25632,14 @@ const SubscriptionsList_SubscriptionFragment = t(`
25413
25632
  fragment SubscriptionsList_SubscriptionFragment on Subscription {
25414
25633
  id
25415
25634
  state
25635
+ ...splitSubscriptionsByPriceList_SubscriptionFragment
25416
25636
  ...findNonAddonSubscriptions_SubscriptionFragment
25417
25637
  ...SubscriptionCardDesktop_SubscriptionFragment
25418
25638
  ...SubscriptionCardMobile_SubscriptionFragment
25419
25639
  ...AddonSubscriptionsCards_SubscriptionFragment
25420
25640
  }
25421
25641
  `, [
25642
+ splitSubscriptionsByPriceList_SubscriptionFragment,
25422
25643
  findNonAddonSubscriptions_SubscriptionFragment,
25423
25644
  SubscriptionCardDesktop_SubscriptionFragment,
25424
25645
  SubscriptionCardMobile_SubscriptionFragment,
@@ -25429,15 +25650,17 @@ const SubscriptionsList = ({ showInactive, subscriptions: maskedSubscriptions, }
25429
25650
  const { gap } = useSubscriptionProps();
25430
25651
  // Read fragments
25431
25652
  const subscriptions = maskedSubscriptions.map(maskedSubscription => readFragment(SubscriptionsList_SubscriptionFragment, maskedSubscription));
25653
+ // Split subscriptions by price list
25654
+ const splitSubscriptions = splitSubscriptionsByPriceList(subscriptions);
25432
25655
  // Hooks
25433
25656
  const isMobile = useIsMobile();
25434
- const nonAddonSubscriptions = findNonAddonSubscriptions(subscriptions);
25657
+ const nonAddonSubscriptions = findNonAddonSubscriptions(splitSubscriptions);
25435
25658
  return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: nonAddonSubscriptions === null || nonAddonSubscriptions === void 0 ? void 0 : nonAddonSubscriptions.map((subscription, subscriptionIndex) => {
25436
25659
  if (!showInactive && isSubscriptionNotActive(subscription.state))
25437
25660
  return null;
25438
25661
  if (isMobile)
25439
- return (jsxRuntime.jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsxRuntime.jsx(SubscriptionCard, { subscription: subscription }), jsxRuntime.jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: subscriptions, showInactive: showInactive })] }, subscriptionIndex));
25440
- return (jsxRuntime.jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsxRuntime.jsx(SubscriptionCardDesktop, { subscription: subscription }), jsxRuntime.jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: subscriptions, showInactive: showInactive })] }, subscriptionIndex));
25662
+ return (jsxRuntime.jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsxRuntime.jsx(SubscriptionCard, { subscription: subscription }), jsxRuntime.jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: splitSubscriptions, showInactive: showInactive })] }, subscriptionIndex));
25663
+ return (jsxRuntime.jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsxRuntime.jsx(SubscriptionCardDesktop, { subscription: subscription }), jsxRuntime.jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: splitSubscriptions, showInactive: showInactive })] }, subscriptionIndex));
25441
25664
  }) }));
25442
25665
  };
25443
25666
 
@@ -3,6 +3,8 @@ export declare const SubscriptionsList_SubscriptionFragment: import("gql.tada").
3
3
  id: string;
4
4
  state: "ACTIVE" | "TRIAL" | "CANCELED" | "EXPIRED" | "TRIAL_EXPIRED" | "PENDING";
5
5
  [$tada.fragmentRefs]: {
6
+ splitSubscriptionsByPriceList_SubscriptionFragment: "Subscription";
7
+ } & {
6
8
  findNonAddonSubscriptions_SubscriptionFragment: "Subscription";
7
9
  } & {
8
10
  SubscriptionCardDesktop_SubscriptionFragment: "Subscription";
@@ -3,6 +3,7 @@ import { Plan } from '@/types/Plan';
3
3
  import Product from '@/types/Product';
4
4
  import Subscription from '@/types/Subscription';
5
5
  import { SubscriptionCharge } from '@/types/SubscriptionCharge';
6
+ import { FragmentOf } from 'gql.tada';
6
7
  export declare const isSubscriptionNotActive: (subscriptionState: SubscriptionState) => boolean;
7
8
  export declare const productPlanName: ({ plan, product }: {
8
9
  plan: Plan;
@@ -16,3 +17,61 @@ export declare const hasPriceTiers: (charge: {
16
17
  priceTiers: {}[] | null;
17
18
  }) => boolean;
18
19
  export declare const getUpdatingChargeQuantityId: (charge: SubscriptionCharge, subscription: Subscription) => string;
20
+ export declare const splitSubscriptionsByPriceList_SubscriptionFragment: import("gql.tada").TadaDocumentNode<{
21
+ id: string;
22
+ state: "ACTIVE" | "TRIAL" | "CANCELED" | "EXPIRED" | "TRIAL_EXPIRED" | "PENDING";
23
+ evergreen: boolean;
24
+ startDate: unknown;
25
+ endDate: unknown;
26
+ addonSubscriptions: {
27
+ id: string;
28
+ priceList: {
29
+ id: string;
30
+ } | null;
31
+ plan: {
32
+ id: string;
33
+ } | null;
34
+ }[] | null;
35
+ chargeReport: {
36
+ kind: "SUBSCRIBE" | "UPDATE" | "RENEW" | "REINSTATE" | "UNSUBSCRIBE" | "ADJUSTMENT" | "COUPON" | "DISCOUNT" | "CREDIT" | "PRICE_UPDATE" | "QUANTITY_UPDATE" | "FREE_PERIOD_DISCOUNT" | "ACTIVATE" | null;
37
+ startDate: unknown;
38
+ endDate: unknown;
39
+ priceList: {
40
+ id: string;
41
+ plan: {
42
+ id: string;
43
+ name: string;
44
+ selfServiceBuy: boolean | null;
45
+ selfServiceCancel: boolean | null;
46
+ selfServiceRenew: boolean | null;
47
+ addon: boolean | null;
48
+ addonPlans: {
49
+ id: string;
50
+ }[] | null;
51
+ } | null;
52
+ product: {
53
+ id: string;
54
+ name: string;
55
+ showProductNameOnLineItem: boolean | null;
56
+ } | null;
57
+ periodMonths: number | null;
58
+ name: string;
59
+ } | null;
60
+ }[] | null;
61
+ charges: {
62
+ priceList: {
63
+ id: string;
64
+ } | null;
65
+ }[] | null;
66
+ }, {}, {
67
+ fragment: "splitSubscriptionsByPriceList_SubscriptionFragment";
68
+ on: "Subscription";
69
+ masked: true;
70
+ }>;
71
+ /**
72
+ * Splits a subscription into multiple subscriptions if it has RENEW charges
73
+ * with different price lists.
74
+ * This handles cases where a subscription has been changed to a different plan/price list.
75
+ * Each resulting subscription is a complete duplicate with just the charges filtered.
76
+ */
77
+ export declare function splitSubscriptionsByPriceList<T extends FragmentOf<typeof splitSubscriptionsByPriceList_SubscriptionFragment>>(maskedSubscriptions: T[]): T[];
package/dist/esm/index.js CHANGED
@@ -1278,7 +1278,7 @@ const DEFAULT_CONFIG = {
1278
1278
  };
1279
1279
 
1280
1280
  // This will be replaced at build time by rollup-plugin-replace
1281
- const PACKAGE_VERSION = '1.7.0-beta.14';
1281
+ const PACKAGE_VERSION = '1.7.0-beta.17';
1282
1282
  const createRequestHeaders = (token) => {
1283
1283
  const headers = createClientDevHeaders({ token });
1284
1284
  // Add the components version header
@@ -24510,6 +24510,225 @@ const hasPriceTiers = (charge) => {
24510
24510
  var _a;
24511
24511
  return Boolean((_a = charge === null || charge === void 0 ? void 0 : charge.priceTiers) === null || _a === void 0 ? void 0 : _a.length);
24512
24512
  };
24513
+ const splitSubscriptionsByPriceList_SubscriptionFragment = t(`
24514
+ fragment splitSubscriptionsByPriceList_SubscriptionFragment on Subscription {
24515
+ id
24516
+ state
24517
+ evergreen
24518
+ startDate
24519
+ endDate
24520
+ addonSubscriptions {
24521
+ id
24522
+ priceList {
24523
+ id
24524
+ }
24525
+ plan {
24526
+ id
24527
+ }
24528
+ }
24529
+ chargeReport {
24530
+ kind
24531
+ startDate
24532
+ endDate
24533
+ priceList {
24534
+ id
24535
+ plan {
24536
+ id
24537
+ name
24538
+ selfServiceBuy
24539
+ selfServiceCancel
24540
+ selfServiceRenew
24541
+ addon
24542
+ addonPlans {
24543
+ id
24544
+ }
24545
+ }
24546
+ product {
24547
+ id
24548
+ name
24549
+ showProductNameOnLineItem
24550
+ }
24551
+ periodMonths
24552
+ name
24553
+ }
24554
+ }
24555
+ charges {
24556
+ priceList {
24557
+ id
24558
+ }
24559
+ }
24560
+ }
24561
+ `, []);
24562
+ // Helper to safely get date as string
24563
+ const getDateString = (date) => {
24564
+ return date;
24565
+ };
24566
+ /**
24567
+ * Splits a subscription into multiple subscriptions if it has RENEW charges
24568
+ * with different price lists.
24569
+ * This handles cases where a subscription has been changed to a different plan/price list.
24570
+ * Each resulting subscription is a complete duplicate with just the charges filtered.
24571
+ */
24572
+ function splitSubscriptionsByPriceList(maskedSubscriptions) {
24573
+ const result = [];
24574
+ maskedSubscriptions.forEach(maskedSubscription => {
24575
+ var _a;
24576
+ // Read the fragment to get typed data
24577
+ const subscription = readFragment(splitSubscriptionsByPriceList_SubscriptionFragment, maskedSubscription);
24578
+ // Skip if no chargeReport
24579
+ if (!subscription.chargeReport) {
24580
+ result.push(maskedSubscription);
24581
+ return;
24582
+ }
24583
+ // Group chargeReport by priceListId
24584
+ const chargesByPriceList = new Map();
24585
+ subscription.chargeReport.forEach(charge => {
24586
+ var _a;
24587
+ const priceListId = (_a = charge.priceList) === null || _a === void 0 ? void 0 : _a.id;
24588
+ if (!priceListId)
24589
+ return;
24590
+ if (!chargesByPriceList.has(priceListId)) {
24591
+ chargesByPriceList.set(priceListId, []);
24592
+ }
24593
+ chargesByPriceList.get(priceListId).push(charge);
24594
+ });
24595
+ // Check if there are RENEW charges with different price lists
24596
+ const hasRenewChargesWithDifferentPriceLists = chargesByPriceList.size > 1 &&
24597
+ subscription.chargeReport.some(charge => charge.kind === 'RENEW');
24598
+ // If all charges have the same priceListId or no RENEW charges, keep subscription as-is
24599
+ if (!hasRenewChargesWithDifferentPriceLists) {
24600
+ result.push(maskedSubscription);
24601
+ }
24602
+ else {
24603
+ // Find the charge with the latest endDate to determine which subscription will actually renew
24604
+ const latestCharge = subscription.chargeReport.reduce((latest, current) => {
24605
+ if (!latest)
24606
+ return current;
24607
+ // Compare endDates, and if equal, compare startDates
24608
+ const currentEnd = getDateString(current.endDate);
24609
+ const latestEnd = getDateString(latest.endDate);
24610
+ const currentStart = getDateString(current.startDate);
24611
+ const latestStart = getDateString(latest.startDate);
24612
+ if (currentEnd > latestEnd)
24613
+ return current;
24614
+ if (currentEnd === latestEnd && currentStart > latestStart)
24615
+ return current;
24616
+ return latest;
24617
+ }, subscription.chargeReport[0]);
24618
+ const latestPriceListId = (_a = latestCharge.priceList) === null || _a === void 0 ? void 0 : _a.id;
24619
+ if (!latestPriceListId) {
24620
+ result.push(maskedSubscription);
24621
+ return;
24622
+ }
24623
+ // Split into multiple subscriptions, one per price list
24624
+ // DUPLICATE the entire subscription, just filter the charges
24625
+ const splitSubscriptions = [];
24626
+ chargesByPriceList.forEach((charges, priceListId) => {
24627
+ var _a, _b, _c, _d, _e, _f;
24628
+ // Get the first charge to extract priceList, plan, and product info
24629
+ const firstCharge = charges === null || charges === void 0 ? void 0 : charges[0];
24630
+ if (!firstCharge || !charges)
24631
+ return;
24632
+ // Find the latest endDate from the charges in this group
24633
+ const latestEndDate = charges.reduce((latest, current) => {
24634
+ const currentEnd = getDateString(current.endDate);
24635
+ if (!latest)
24636
+ return currentEnd;
24637
+ return currentEnd > latest ? currentEnd : latest;
24638
+ }, getDateString(charges[0].endDate));
24639
+ // Find the earliest startDate from the charges in this group
24640
+ const earliestStartDate = charges.reduce((earliest, current) => {
24641
+ const currentStart = getDateString(current.startDate);
24642
+ if (!earliest)
24643
+ return currentStart;
24644
+ return currentStart < earliest ? currentStart : earliest;
24645
+ }, getDateString(charges[0].startDate));
24646
+ // Determine the state based on the date range of charges
24647
+ const now = new Date().toISOString().split('T')[0]; // Get current date in YYYY-MM-DD format
24648
+ let state = subscription.state;
24649
+ // If the latest endDate is before today, it's expired
24650
+ if (latestEndDate < now) {
24651
+ state = t.scalar('SubscriptionState', 'EXPIRED');
24652
+ }
24653
+ // If the earliest startDate is after today, it's pending
24654
+ else if (earliestStartDate > now) {
24655
+ state = t.scalar('SubscriptionState', 'PENDING');
24656
+ }
24657
+ // Otherwise, if current date is within the range, keep it active
24658
+ else if (earliestStartDate <= now && latestEndDate >= now) {
24659
+ state = t.scalar('SubscriptionState', 'ACTIVE');
24660
+ }
24661
+ // Only the subscription with the latest charge will actually renew
24662
+ const willRenew = priceListId === latestPriceListId;
24663
+ // Filter addon subscriptions to only include those compatible with the new plan
24664
+ // If the plan has changed, we need to check if the addon subscriptions are still valid
24665
+ const newPlan = (_a = firstCharge.priceList) === null || _a === void 0 ? void 0 : _a.plan;
24666
+ const addonPlanIds = (_c = (_b = newPlan === null || newPlan === void 0 ? void 0 : newPlan.addonPlans) === null || _b === void 0 ? void 0 : _b.map(plan => plan.id)) !== null && _c !== void 0 ? _c : [];
24667
+ // For split subscriptions that represent a future period (after plan change),
24668
+ // filter out addon subscriptions that are not compatible with the new plan
24669
+ const filteredAddonSubscriptions = (_d = subscription.addonSubscriptions) === null || _d === void 0 ? void 0 : _d.filter(addonSub => {
24670
+ var _a;
24671
+ // If the new plan has no addon plans defined, keep all addons (backward compatibility)
24672
+ if (!(newPlan === null || newPlan === void 0 ? void 0 : newPlan.addonPlans) || newPlan.addonPlans.length === 0) {
24673
+ return true;
24674
+ }
24675
+ // Check if this addon subscription's plan is in the new plan's addonPlans list
24676
+ const addonPlanId = (_a = addonSub.plan) === null || _a === void 0 ? void 0 : _a.id;
24677
+ return addonPlanId ? addonPlanIds.includes(addonPlanId) : false;
24678
+ });
24679
+ splitSubscriptions.push({
24680
+ ...maskedSubscription,
24681
+ // Keep the original subscription ID intact (from maskedSubscription spread)
24682
+ // Update priceList from the charge
24683
+ priceList: firstCharge.priceList,
24684
+ // Update plan from the charge's priceList.plan
24685
+ plan: firstCharge.priceList.plan,
24686
+ // Update product from the charge's priceList.product
24687
+ product: firstCharge.priceList.product,
24688
+ // Update endDate to match the latest charge endDate in this group
24689
+ endDate: latestEndDate,
24690
+ // Update startDate to match the earliest charge startDate in this group
24691
+ startDate: earliestStartDate,
24692
+ // Update state based on the charge dates
24693
+ state,
24694
+ // Set evergreen to false for subscriptions that won't renew (will transition instead)
24695
+ evergreen: willRenew ? subscription.evergreen : false,
24696
+ // Filter chargeReport to only include charges for this price list
24697
+ chargeReport: charges,
24698
+ // Filter charges array to only include charges for this price list
24699
+ charges: (_f = (_e = subscription.charges) === null || _e === void 0 ? void 0 : _e.filter(charge => {
24700
+ var _a;
24701
+ const chargeWithPriceList = charge;
24702
+ return ((_a = chargeWithPriceList.priceList) === null || _a === void 0 ? void 0 : _a.id) === priceListId;
24703
+ })) !== null && _f !== void 0 ? _f : [],
24704
+ // Filter addon subscriptions to only include those compatible with the new plan
24705
+ addonSubscriptions: filteredAddonSubscriptions,
24706
+ });
24707
+ });
24708
+ // Sort split subscriptions by endDate (earliest first)
24709
+ splitSubscriptions.sort((a, b) => {
24710
+ const aData = readFragment(splitSubscriptionsByPriceList_SubscriptionFragment, a);
24711
+ const bData = readFragment(splitSubscriptionsByPriceList_SubscriptionFragment, b);
24712
+ const aEnd = getDateString(aData.endDate);
24713
+ const bEnd = getDateString(bData.endDate);
24714
+ const aStart = getDateString(aData.startDate);
24715
+ const bStart = getDateString(bData.startDate);
24716
+ if (aEnd < bEnd)
24717
+ return -1;
24718
+ if (aEnd > bEnd)
24719
+ return 1;
24720
+ // If endDates are equal, sort by startDate
24721
+ if (aStart < bStart)
24722
+ return -1;
24723
+ if (aStart > bStart)
24724
+ return 1;
24725
+ return 0;
24726
+ });
24727
+ result.push(...splitSubscriptions);
24728
+ }
24729
+ });
24730
+ return result;
24731
+ }
24513
24732
 
24514
24733
  const SubscriptionCardActions_PriceListChangeOptionsFragment = t(`
24515
24734
  fragment SubscriptionCardActions_PriceListChangeOptionsFragment on PriceListChangeOptions {
@@ -25411,12 +25630,14 @@ const SubscriptionsList_SubscriptionFragment = t(`
25411
25630
  fragment SubscriptionsList_SubscriptionFragment on Subscription {
25412
25631
  id
25413
25632
  state
25633
+ ...splitSubscriptionsByPriceList_SubscriptionFragment
25414
25634
  ...findNonAddonSubscriptions_SubscriptionFragment
25415
25635
  ...SubscriptionCardDesktop_SubscriptionFragment
25416
25636
  ...SubscriptionCardMobile_SubscriptionFragment
25417
25637
  ...AddonSubscriptionsCards_SubscriptionFragment
25418
25638
  }
25419
25639
  `, [
25640
+ splitSubscriptionsByPriceList_SubscriptionFragment,
25420
25641
  findNonAddonSubscriptions_SubscriptionFragment,
25421
25642
  SubscriptionCardDesktop_SubscriptionFragment,
25422
25643
  SubscriptionCardMobile_SubscriptionFragment,
@@ -25427,15 +25648,17 @@ const SubscriptionsList = ({ showInactive, subscriptions: maskedSubscriptions, }
25427
25648
  const { gap } = useSubscriptionProps();
25428
25649
  // Read fragments
25429
25650
  const subscriptions = maskedSubscriptions.map(maskedSubscription => readFragment(SubscriptionsList_SubscriptionFragment, maskedSubscription));
25651
+ // Split subscriptions by price list
25652
+ const splitSubscriptions = splitSubscriptionsByPriceList(subscriptions);
25430
25653
  // Hooks
25431
25654
  const isMobile = useIsMobile();
25432
- const nonAddonSubscriptions = findNonAddonSubscriptions(subscriptions);
25655
+ const nonAddonSubscriptions = findNonAddonSubscriptions(splitSubscriptions);
25433
25656
  return (jsx(Fragment, { children: nonAddonSubscriptions === null || nonAddonSubscriptions === void 0 ? void 0 : nonAddonSubscriptions.map((subscription, subscriptionIndex) => {
25434
25657
  if (!showInactive && isSubscriptionNotActive(subscription.state))
25435
25658
  return null;
25436
25659
  if (isMobile)
25437
- return (jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsx(SubscriptionCard, { subscription: subscription }), jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: subscriptions, showInactive: showInactive })] }, subscriptionIndex));
25438
- return (jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsx(SubscriptionCardDesktop, { subscription: subscription }), jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: subscriptions, showInactive: showInactive })] }, subscriptionIndex));
25660
+ return (jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsx(SubscriptionCard, { subscription: subscription }), jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: splitSubscriptions, showInactive: showInactive })] }, subscriptionIndex));
25661
+ return (jsxs("div", { className: `bunny-flex bunny-flex-col bunny-gap-${gap}`, children: [jsx(SubscriptionCardDesktop, { subscription: subscription }), jsx(AddonSubscriptionsCards, { subscription: subscription, subscriptions: splitSubscriptions, showInactive: showInactive })] }, subscriptionIndex));
25439
25662
  }) }));
25440
25663
  };
25441
25664
 
@@ -3,6 +3,8 @@ export declare const SubscriptionsList_SubscriptionFragment: import("gql.tada").
3
3
  id: string;
4
4
  state: "ACTIVE" | "TRIAL" | "CANCELED" | "EXPIRED" | "TRIAL_EXPIRED" | "PENDING";
5
5
  [$tada.fragmentRefs]: {
6
+ splitSubscriptionsByPriceList_SubscriptionFragment: "Subscription";
7
+ } & {
6
8
  findNonAddonSubscriptions_SubscriptionFragment: "Subscription";
7
9
  } & {
8
10
  SubscriptionCardDesktop_SubscriptionFragment: "Subscription";
@@ -3,6 +3,7 @@ import { Plan } from '@/types/Plan';
3
3
  import Product from '@/types/Product';
4
4
  import Subscription from '@/types/Subscription';
5
5
  import { SubscriptionCharge } from '@/types/SubscriptionCharge';
6
+ import { FragmentOf } from 'gql.tada';
6
7
  export declare const isSubscriptionNotActive: (subscriptionState: SubscriptionState) => boolean;
7
8
  export declare const productPlanName: ({ plan, product }: {
8
9
  plan: Plan;
@@ -16,3 +17,61 @@ export declare const hasPriceTiers: (charge: {
16
17
  priceTiers: {}[] | null;
17
18
  }) => boolean;
18
19
  export declare const getUpdatingChargeQuantityId: (charge: SubscriptionCharge, subscription: Subscription) => string;
20
+ export declare const splitSubscriptionsByPriceList_SubscriptionFragment: import("gql.tada").TadaDocumentNode<{
21
+ id: string;
22
+ state: "ACTIVE" | "TRIAL" | "CANCELED" | "EXPIRED" | "TRIAL_EXPIRED" | "PENDING";
23
+ evergreen: boolean;
24
+ startDate: unknown;
25
+ endDate: unknown;
26
+ addonSubscriptions: {
27
+ id: string;
28
+ priceList: {
29
+ id: string;
30
+ } | null;
31
+ plan: {
32
+ id: string;
33
+ } | null;
34
+ }[] | null;
35
+ chargeReport: {
36
+ kind: "SUBSCRIBE" | "UPDATE" | "RENEW" | "REINSTATE" | "UNSUBSCRIBE" | "ADJUSTMENT" | "COUPON" | "DISCOUNT" | "CREDIT" | "PRICE_UPDATE" | "QUANTITY_UPDATE" | "FREE_PERIOD_DISCOUNT" | "ACTIVATE" | null;
37
+ startDate: unknown;
38
+ endDate: unknown;
39
+ priceList: {
40
+ id: string;
41
+ plan: {
42
+ id: string;
43
+ name: string;
44
+ selfServiceBuy: boolean | null;
45
+ selfServiceCancel: boolean | null;
46
+ selfServiceRenew: boolean | null;
47
+ addon: boolean | null;
48
+ addonPlans: {
49
+ id: string;
50
+ }[] | null;
51
+ } | null;
52
+ product: {
53
+ id: string;
54
+ name: string;
55
+ showProductNameOnLineItem: boolean | null;
56
+ } | null;
57
+ periodMonths: number | null;
58
+ name: string;
59
+ } | null;
60
+ }[] | null;
61
+ charges: {
62
+ priceList: {
63
+ id: string;
64
+ } | null;
65
+ }[] | null;
66
+ }, {}, {
67
+ fragment: "splitSubscriptionsByPriceList_SubscriptionFragment";
68
+ on: "Subscription";
69
+ masked: true;
70
+ }>;
71
+ /**
72
+ * Splits a subscription into multiple subscriptions if it has RENEW charges
73
+ * with different price lists.
74
+ * This handles cases where a subscription has been changed to a different plan/price list.
75
+ * Each resulting subscription is a complete duplicate with just the charges filtered.
76
+ */
77
+ export declare function splitSubscriptionsByPriceList<T extends FragmentOf<typeof splitSubscriptionsByPriceList_SubscriptionFragment>>(maskedSubscriptions: T[]): T[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunnyapp/components",
3
- "version": "1.7.0-beta.15",
3
+ "version": "1.7.0-beta.18",
4
4
  "description": "Components from the Bunny portal to embed Bunny UI functionality into your application.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",