@umituz/react-native-subscription 2.11.7 → 2.11.9

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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +8 -0
  3. package/src/presentation/components/details/CreditRow.tsx +83 -0
  4. package/src/presentation/components/details/DetailRow.tsx +53 -0
  5. package/src/presentation/components/details/PremiumDetailsCard.tsx +5 -155
  6. package/src/presentation/components/details/PremiumDetailsCardTypes.ts +41 -0
  7. package/src/presentation/components/paywall/PaywallLegalFooter.tsx +5 -62
  8. package/src/presentation/components/paywall/PaywallLegalFooterStyles.ts +53 -0
  9. package/src/presentation/components/paywall/PaywallLegalFooterTypes.ts +19 -0
  10. package/src/presentation/components/paywall/PaywallModal.tsx +1 -1
  11. package/src/presentation/components/paywall/SubscriptionFooter.tsx +0 -4
  12. package/src/presentation/components/paywall/SubscriptionModal.tsx +1 -26
  13. package/src/presentation/components/paywall/SubscriptionPackageList.tsx +14 -5
  14. package/src/presentation/components/paywall/SubscriptionPlanCard.tsx +5 -67
  15. package/src/presentation/components/paywall/SubscriptionPlanCardStyles.ts +61 -0
  16. package/src/presentation/components/paywall/SubscriptionPlanCardTypes.ts +15 -0
  17. package/src/presentation/components/paywall/accordion/AccordionPlanCard.tsx +93 -0
  18. package/src/presentation/components/paywall/accordion/AccordionPlanCardTypes.ts +33 -0
  19. package/src/presentation/components/paywall/accordion/PlanCardDetails.tsx +101 -0
  20. package/src/presentation/components/paywall/accordion/PlanCardHeader.tsx +159 -0
  21. package/src/presentation/components/paywall/accordion/index.ts +12 -0
  22. package/src/revenuecat/presentation/hooks/subscriptionQueryKeys.ts +16 -0
  23. package/src/revenuecat/presentation/hooks/useInitializeSubscription.ts +56 -0
  24. package/src/revenuecat/presentation/hooks/usePurchasePackage.ts +79 -0
  25. package/src/revenuecat/presentation/hooks/useRestorePurchase.ts +68 -0
  26. package/src/revenuecat/presentation/hooks/useSubscriptionPackages.ts +44 -0
  27. package/src/revenuecat/presentation/hooks/useSubscriptionQueries.ts +9 -216
@@ -1,9 +1,9 @@
1
- import React from "react";
1
+ import React, { useState, useCallback } from "react";
2
2
  import { View, StyleSheet, ActivityIndicator } from "react-native";
3
3
  import { AtomicText } from "@umituz/react-native-design-system";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
5
  import type { PurchasesPackage } from "react-native-purchases";
6
- import { SubscriptionPlanCard } from "./SubscriptionPlanCard";
6
+ import { AccordionPlanCard } from "./accordion";
7
7
  import { isYearlyPackage, isMonthlyPackage, isWeeklyPackage } from "../../../utils/packagePeriodUtils";
8
8
 
9
9
  interface SubscriptionPackageListProps {
@@ -34,6 +34,12 @@ export const SubscriptionPackageList: React.FC<SubscriptionPackageListProps> = R
34
34
  const hasPackages = packages.length > 0;
35
35
  const showLoading = isLoading && !hasPackages;
36
36
 
37
+ const [expandedPackageId, setExpandedPackageId] = useState<string | null>(null);
38
+
39
+ const handleToggleExpand = useCallback((packageId: string) => {
40
+ setExpandedPackageId((prev) => (prev === packageId ? null : packageId));
41
+ }, []);
42
+
37
43
  if (showLoading) {
38
44
  return (
39
45
  <View style={styles.centerContent}>
@@ -115,13 +121,16 @@ export const SubscriptionPackageList: React.FC<SubscriptionPackageListProps> = R
115
121
  };
116
122
 
117
123
  const creditAmount = findCreditAmount();
124
+ const packageId = pkg.product.identifier;
118
125
 
119
126
  return (
120
- <SubscriptionPlanCard
121
- key={pkg.product.identifier}
127
+ <AccordionPlanCard
128
+ key={packageId}
122
129
  package={pkg}
123
- isSelected={selectedPkg?.product.identifier === pkg.product.identifier}
130
+ isSelected={selectedPkg?.product.identifier === packageId}
131
+ isExpanded={expandedPackageId === packageId}
124
132
  onSelect={() => onSelect(pkg)}
133
+ onToggleExpand={() => handleToggleExpand(packageId)}
125
134
  isBestValue={isBestValue}
126
135
  creditAmount={creditAmount}
127
136
  />
@@ -4,25 +4,18 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { View, TouchableOpacity, StyleSheet } from "react-native";
8
- import type { PurchasesPackage } from "react-native-purchases";
7
+ import { View, TouchableOpacity } from "react-native";
9
8
  import { AtomicText } from "@umituz/react-native-design-system";
10
9
  import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system";
11
10
  import { formatPrice } from "../../../utils/priceUtils";
12
11
  import { useLocalization } from "@umituz/react-native-localization";
13
12
  import { BestValueBadge } from "./BestValueBadge";
14
-
15
13
  import { getPeriodLabel, isYearlyPackage } from "../../../utils/packagePeriodUtils";
16
14
  import { LinearGradient } from "expo-linear-gradient";
15
+ import type { SubscriptionPlanCardProps } from "./SubscriptionPlanCardTypes";
16
+ import { styles } from "./SubscriptionPlanCardStyles";
17
17
 
18
- interface SubscriptionPlanCardProps {
19
- package: PurchasesPackage;
20
- isSelected: boolean;
21
- onSelect: () => void;
22
- isBestValue?: boolean;
23
- /** Optional: Number of credits/generations included with this package */
24
- creditAmount?: number;
25
- }
18
+ export type { SubscriptionPlanCardProps } from "./SubscriptionPlanCardTypes";
26
19
 
27
20
  export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
28
21
  React.memo(({ package: pkg, isSelected, onSelect, isBestValue = false, creditAmount }) => {
@@ -147,59 +140,4 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
147
140
  });
148
141
 
149
142
 
150
- SubscriptionPlanCard.displayName = "SubscriptionPlanCard";
151
-
152
- const styles = StyleSheet.create({
153
- container: {
154
- borderRadius: 16,
155
- position: "relative",
156
- overflow: "hidden", // Important for gradient borders/corners
157
- },
158
- gradientWrapper: {
159
- flex: 1,
160
- padding: 18,
161
- },
162
- content: {
163
- flexDirection: "row",
164
- justifyContent: "space-between",
165
- alignItems: "center",
166
- },
167
- leftSection: {
168
- flexDirection: "row",
169
- alignItems: "center",
170
- flex: 1,
171
- },
172
- radio: {
173
- width: 24,
174
- height: 24,
175
- borderRadius: 12,
176
- borderWidth: 2,
177
- alignItems: "center",
178
- justifyContent: "center",
179
- marginRight: 16,
180
- },
181
- radioInner: {
182
- width: 12,
183
- height: 12,
184
- borderRadius: 6,
185
- },
186
- textContainer: {
187
- flex: 1,
188
- },
189
- title: {
190
- fontWeight: "600",
191
- marginBottom: 2,
192
- },
193
- creditBadge: {
194
- paddingHorizontal: 10,
195
- paddingVertical: 4,
196
- borderRadius: 12,
197
- marginBottom: 4,
198
- },
199
- rightSection: {
200
- alignItems: "flex-end",
201
- },
202
- price: {
203
- fontWeight: "700",
204
- },
205
- });
143
+ SubscriptionPlanCard.displayName = "SubscriptionPlanCard";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Subscription Plan Card Styles
3
+ * StyleSheet definitions for subscription plan card
4
+ */
5
+
6
+ import { StyleSheet } from "react-native";
7
+
8
+ export const styles = StyleSheet.create({
9
+ container: {
10
+ borderRadius: 16,
11
+ position: "relative",
12
+ overflow: "hidden",
13
+ },
14
+ gradientWrapper: {
15
+ flex: 1,
16
+ padding: 18,
17
+ },
18
+ content: {
19
+ flexDirection: "row",
20
+ justifyContent: "space-between",
21
+ alignItems: "center",
22
+ },
23
+ leftSection: {
24
+ flexDirection: "row",
25
+ alignItems: "center",
26
+ flex: 1,
27
+ },
28
+ radio: {
29
+ width: 24,
30
+ height: 24,
31
+ borderRadius: 12,
32
+ borderWidth: 2,
33
+ alignItems: "center",
34
+ justifyContent: "center",
35
+ marginRight: 16,
36
+ },
37
+ radioInner: {
38
+ width: 12,
39
+ height: 12,
40
+ borderRadius: 6,
41
+ },
42
+ textContainer: {
43
+ flex: 1,
44
+ },
45
+ title: {
46
+ fontWeight: "600",
47
+ marginBottom: 2,
48
+ },
49
+ creditBadge: {
50
+ paddingHorizontal: 10,
51
+ paddingVertical: 4,
52
+ borderRadius: 12,
53
+ marginBottom: 4,
54
+ },
55
+ rightSection: {
56
+ alignItems: "flex-end",
57
+ },
58
+ price: {
59
+ fontWeight: "700",
60
+ },
61
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Subscription Plan Card Types
3
+ * Type definitions for subscription plan display
4
+ */
5
+
6
+ import type { PurchasesPackage } from "react-native-purchases";
7
+
8
+ export interface SubscriptionPlanCardProps {
9
+ package: PurchasesPackage;
10
+ isSelected: boolean;
11
+ onSelect: () => void;
12
+ isBestValue?: boolean;
13
+ /** Optional: Number of credits/generations included with this package */
14
+ creditAmount?: number;
15
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Accordion Plan Card
3
+ * Expandable subscription plan card with credit display
4
+ */
5
+
6
+ import React, { useCallback } from "react";
7
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
9
+ import { formatPrice } from "../../../../utils/priceUtils";
10
+ import { getPeriodLabel, isYearlyPackage } from "../../../../utils/packagePeriodUtils";
11
+ import { useLocalization } from "@umituz/react-native-localization";
12
+ import { PlanCardHeader } from "./PlanCardHeader";
13
+ import { PlanCardDetails } from "./PlanCardDetails";
14
+ import type { AccordionPlanCardProps } from "./AccordionPlanCardTypes";
15
+
16
+ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
17
+ ({
18
+ package: pkg,
19
+ isSelected,
20
+ isExpanded,
21
+ onSelect,
22
+ onToggleExpand,
23
+ isBestValue = false,
24
+ creditAmount,
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+ const { t } = useLocalization();
28
+
29
+ const period = pkg.product.subscriptionPeriod;
30
+ const isYearly = isYearlyPackage(pkg);
31
+ const periodLabel = getPeriodLabel(period);
32
+ const price = formatPrice(pkg.product.price, pkg.product.currencyCode);
33
+ const monthlyEquivalent = isYearly
34
+ ? formatPrice(pkg.product.price / 12, pkg.product.currencyCode)
35
+ : null;
36
+
37
+ const title = pkg.product.title || t(`paywall.period.${periodLabel}`);
38
+ const displayPrice = isYearly && monthlyEquivalent
39
+ ? `${monthlyEquivalent}/mo`
40
+ : price;
41
+
42
+ const handleHeaderPress = useCallback(() => {
43
+ onSelect();
44
+ if (!isExpanded) {
45
+ onToggleExpand();
46
+ }
47
+ }, [onSelect, onToggleExpand, isExpanded]);
48
+
49
+ const containerStyle: StyleProp<ViewStyle> = [
50
+ styles.container,
51
+ {
52
+ borderColor: isSelected
53
+ ? tokens.colors.primary
54
+ : tokens.colors.borderLight,
55
+ borderWidth: isSelected ? 2 : 1,
56
+ backgroundColor: tokens.colors.surface,
57
+ },
58
+ ];
59
+
60
+ return (
61
+ <View style={containerStyle}>
62
+ <PlanCardHeader
63
+ title={title}
64
+ price={displayPrice}
65
+ creditAmount={creditAmount}
66
+ isSelected={isSelected}
67
+ isExpanded={isExpanded}
68
+ isBestValue={isBestValue}
69
+ onToggle={handleHeaderPress}
70
+ />
71
+
72
+ {isExpanded && (
73
+ <PlanCardDetails
74
+ fullPrice={price}
75
+ monthlyEquivalent={monthlyEquivalent}
76
+ periodLabel={periodLabel}
77
+ isYearly={isYearly}
78
+ />
79
+ )}
80
+ </View>
81
+ );
82
+ }
83
+ );
84
+
85
+ AccordionPlanCard.displayName = "AccordionPlanCard";
86
+
87
+ const styles = StyleSheet.create({
88
+ container: {
89
+ borderRadius: 16,
90
+ overflow: "hidden",
91
+ marginBottom: 12,
92
+ },
93
+ });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Accordion Plan Card Types
3
+ * Type definitions for accordion-style subscription cards
4
+ */
5
+
6
+ import type { PurchasesPackage } from "react-native-purchases";
7
+
8
+ export interface AccordionPlanCardProps {
9
+ package: PurchasesPackage;
10
+ isSelected: boolean;
11
+ isExpanded: boolean;
12
+ onSelect: () => void;
13
+ onToggleExpand: () => void;
14
+ isBestValue?: boolean;
15
+ creditAmount?: number;
16
+ }
17
+
18
+ export interface PlanCardHeaderProps {
19
+ title: string;
20
+ price: string;
21
+ creditAmount?: number;
22
+ isSelected: boolean;
23
+ isExpanded: boolean;
24
+ isBestValue?: boolean;
25
+ onToggle: () => void;
26
+ }
27
+
28
+ export interface PlanCardDetailsProps {
29
+ fullPrice: string;
30
+ monthlyEquivalent: string | null;
31
+ periodLabel: string;
32
+ isYearly: boolean;
33
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Plan Card Details
3
+ * Expanded state of accordion subscription card
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import { useLocalization } from "@umituz/react-native-localization";
13
+ import type { PlanCardDetailsProps } from "./AccordionPlanCardTypes";
14
+
15
+ export const PlanCardDetails: React.FC<PlanCardDetailsProps> = ({
16
+ fullPrice,
17
+ monthlyEquivalent,
18
+ periodLabel,
19
+ isYearly,
20
+ }) => {
21
+ const tokens = useAppDesignTokens();
22
+ const { t } = useLocalization();
23
+
24
+ return (
25
+ <View
26
+ style={[
27
+ styles.container,
28
+ {
29
+ backgroundColor: tokens.colors.surfaceSecondary,
30
+ borderTopColor: tokens.colors.border,
31
+ },
32
+ ]}
33
+ >
34
+ <View style={styles.row}>
35
+ <AtomicText
36
+ type="bodyMedium"
37
+ style={{ color: tokens.colors.textSecondary }}
38
+ >
39
+ {t("paywall.billingPeriod") || "Billing Period"}
40
+ </AtomicText>
41
+ <AtomicText
42
+ type="bodyMedium"
43
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
44
+ >
45
+ {t(`paywall.period.${periodLabel}`)}
46
+ </AtomicText>
47
+ </View>
48
+
49
+ {isYearly && (
50
+ <View style={styles.row}>
51
+ <AtomicText
52
+ type="bodyMedium"
53
+ style={{ color: tokens.colors.textSecondary }}
54
+ >
55
+ {t("paywall.totalPrice") || "Total Price"}
56
+ </AtomicText>
57
+ <AtomicText
58
+ type="bodyMedium"
59
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
60
+ >
61
+ {fullPrice}
62
+ </AtomicText>
63
+ </View>
64
+ )}
65
+
66
+ {monthlyEquivalent && (
67
+ <View style={styles.row}>
68
+ <AtomicText
69
+ type="bodyMedium"
70
+ style={{ color: tokens.colors.textSecondary }}
71
+ >
72
+ {t("paywall.perMonth") || "Per Month"}
73
+ </AtomicText>
74
+ <AtomicText
75
+ type="bodyMedium"
76
+ style={{
77
+ color: tokens.colors.primary,
78
+ fontWeight: "700",
79
+ }}
80
+ >
81
+ {monthlyEquivalent}
82
+ </AtomicText>
83
+ </View>
84
+ )}
85
+ </View>
86
+ );
87
+ };
88
+
89
+ const styles = StyleSheet.create({
90
+ container: {
91
+ paddingHorizontal: 16,
92
+ paddingVertical: 12,
93
+ borderTopWidth: 1,
94
+ gap: 10,
95
+ },
96
+ row: {
97
+ flexDirection: "row",
98
+ justifyContent: "space-between",
99
+ alignItems: "center",
100
+ },
101
+ });
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Plan Card Header
3
+ * Collapsed state of accordion subscription card
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ withAlpha,
13
+ } from "@umituz/react-native-design-system";
14
+ import { BestValueBadge } from "../BestValueBadge";
15
+ import { useLocalization } from "@umituz/react-native-localization";
16
+ import type { PlanCardHeaderProps } from "./AccordionPlanCardTypes";
17
+
18
+ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
19
+ title,
20
+ price,
21
+ creditAmount,
22
+ isSelected,
23
+ isExpanded,
24
+ isBestValue,
25
+ onToggle,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+ const { t } = useLocalization();
29
+
30
+ return (
31
+ <TouchableOpacity
32
+ onPress={onToggle}
33
+ activeOpacity={0.7}
34
+ style={styles.container}
35
+ >
36
+ <BestValueBadge text={t("paywall.bestValue")} visible={isBestValue} />
37
+
38
+ <View style={styles.content}>
39
+ <View style={styles.leftSection}>
40
+ <View
41
+ style={[
42
+ styles.radio,
43
+ {
44
+ borderColor: isSelected
45
+ ? tokens.colors.primary
46
+ : tokens.colors.border,
47
+ },
48
+ ]}
49
+ >
50
+ {isSelected && (
51
+ <View
52
+ style={[
53
+ styles.radioInner,
54
+ { backgroundColor: tokens.colors.primary },
55
+ ]}
56
+ />
57
+ )}
58
+ </View>
59
+
60
+ <View style={styles.textContainer}>
61
+ <AtomicText
62
+ type="titleSmall"
63
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
64
+ >
65
+ {title}
66
+ </AtomicText>
67
+ {creditAmount && (
68
+ <View
69
+ style={[
70
+ styles.creditBadge,
71
+ {
72
+ backgroundColor: withAlpha(tokens.colors.primary, 0.15),
73
+ borderColor: withAlpha(tokens.colors.primary, 0.3),
74
+ },
75
+ ]}
76
+ >
77
+ <AtomicText
78
+ type="labelSmall"
79
+ style={{
80
+ color: tokens.colors.primary,
81
+ fontWeight: "700",
82
+ fontSize: 11,
83
+ }}
84
+ >
85
+ {creditAmount} {t("paywall.credits") || "Credits"}
86
+ </AtomicText>
87
+ </View>
88
+ )}
89
+ </View>
90
+ </View>
91
+
92
+ <View style={styles.rightSection}>
93
+ <AtomicText
94
+ type="titleMedium"
95
+ style={{
96
+ color: tokens.colors.textPrimary,
97
+ fontWeight: "700",
98
+ marginRight: 8,
99
+ }}
100
+ >
101
+ {price}
102
+ </AtomicText>
103
+ <AtomicIcon
104
+ name={isExpanded ? "ChevronUp" : "ChevronDown"}
105
+ size={20}
106
+ color={tokens.colors.textSecondary}
107
+ />
108
+ </View>
109
+ </View>
110
+ </TouchableOpacity>
111
+ );
112
+ };
113
+
114
+ const styles = StyleSheet.create({
115
+ container: {
116
+ width: "100%",
117
+ },
118
+ content: {
119
+ flexDirection: "row",
120
+ alignItems: "center",
121
+ justifyContent: "space-between",
122
+ paddingVertical: 16,
123
+ paddingHorizontal: 16,
124
+ },
125
+ leftSection: {
126
+ flexDirection: "row",
127
+ alignItems: "center",
128
+ flex: 1,
129
+ },
130
+ radio: {
131
+ width: 22,
132
+ height: 22,
133
+ borderRadius: 11,
134
+ borderWidth: 2,
135
+ alignItems: "center",
136
+ justifyContent: "center",
137
+ marginRight: 12,
138
+ },
139
+ radioInner: {
140
+ width: 12,
141
+ height: 12,
142
+ borderRadius: 6,
143
+ },
144
+ textContainer: {
145
+ flex: 1,
146
+ gap: 6,
147
+ },
148
+ creditBadge: {
149
+ paddingHorizontal: 10,
150
+ paddingVertical: 4,
151
+ borderRadius: 12,
152
+ borderWidth: 1,
153
+ alignSelf: "flex-start",
154
+ },
155
+ rightSection: {
156
+ flexDirection: "row",
157
+ alignItems: "center",
158
+ },
159
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Accordion Components Barrel Export
3
+ */
4
+
5
+ export { AccordionPlanCard } from "./AccordionPlanCard";
6
+ export { PlanCardHeader } from "./PlanCardHeader";
7
+ export { PlanCardDetails } from "./PlanCardDetails";
8
+ export type {
9
+ AccordionPlanCardProps,
10
+ PlanCardHeaderProps,
11
+ PlanCardDetailsProps,
12
+ } from "./AccordionPlanCardTypes";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Subscription Query Keys
3
+ * TanStack Query keys and constants for subscription state
4
+ */
5
+
6
+ /**
7
+ * Query keys for TanStack Query
8
+ */
9
+ export const SUBSCRIPTION_QUERY_KEYS = {
10
+ packages: ["subscription", "packages"] as const,
11
+ initialized: (userId: string) =>
12
+ ["subscription", "initialized", userId] as const,
13
+ } as const;
14
+
15
+ export const STALE_TIME = 5 * 60 * 1000; // 5 minutes
16
+ export const GC_TIME = 30 * 60 * 1000; // 30 minutes
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Initialize Subscription Hook
3
+ * TanStack mutation for initializing RevenueCat
4
+ */
5
+
6
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
7
+ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
8
+ import {
9
+ trackPackageError,
10
+ addPackageBreadcrumb,
11
+ } from "@umituz/react-native-sentry";
12
+ import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
13
+
14
+ /**
15
+ * Initialize subscription with RevenueCat
16
+ */
17
+ export const useInitializeSubscription = (userId: string | undefined) => {
18
+ const queryClient = useQueryClient();
19
+
20
+ return useMutation({
21
+ mutationFn: async () => {
22
+ if (!userId) {
23
+ throw new Error("User not authenticated");
24
+ }
25
+
26
+ addPackageBreadcrumb("subscription", "Initialize mutation started", {
27
+ userId,
28
+ });
29
+
30
+ return SubscriptionManager.initialize(userId);
31
+ },
32
+ onSuccess: () => {
33
+ if (userId) {
34
+ queryClient.invalidateQueries({
35
+ queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
36
+ });
37
+
38
+ addPackageBreadcrumb(
39
+ "subscription",
40
+ "Initialize mutation success - packages invalidated",
41
+ { userId }
42
+ );
43
+ }
44
+ },
45
+ onError: (error) => {
46
+ trackPackageError(
47
+ error instanceof Error ? error : new Error(String(error)),
48
+ {
49
+ packageName: "subscription",
50
+ operation: "initialize_mutation",
51
+ userId: userId ?? "NO_USER",
52
+ }
53
+ );
54
+ },
55
+ });
56
+ };