@umituz/react-native-subscription 2.27.123 → 2.27.124

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 (31) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/creditOperationUtils.ts +65 -144
  3. package/src/domains/credits/application/creditOperationUtils.types.ts +19 -0
  4. package/src/domains/credits/presentation/useCredits.ts +1 -11
  5. package/src/domains/paywall/components/PaywallModal.tsx +14 -107
  6. package/src/domains/paywall/components/PaywallModal.types.ts +26 -0
  7. package/src/domains/paywall/components/PlanCard.tsx +45 -148
  8. package/src/domains/paywall/components/PlanCard.types.ts +12 -0
  9. package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +116 -0
  10. package/src/domains/subscription/application/SubscriptionSyncService.ts +10 -96
  11. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +0 -2
  12. package/src/domains/subscription/core/SubscriptionConstants.ts +1 -13
  13. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +7 -13
  14. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +15 -0
  15. package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +20 -5
  16. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +13 -92
  17. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +47 -0
  18. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +34 -126
  19. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.types.ts +12 -0
  20. package/src/domains/subscription/presentation/usePremium.ts +3 -22
  21. package/src/domains/subscription/presentation/usePremium.types.ts +16 -0
  22. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +30 -22
  23. package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +7 -0
  24. package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +3 -16
  25. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +1 -13
  26. package/src/domains/wallet/presentation/screens/WalletScreen.tsx +25 -112
  27. package/src/domains/wallet/presentation/screens/WalletScreen.types.ts +15 -0
  28. package/src/shared/utils/appValidators.ts +38 -0
  29. package/src/shared/utils/validators.ts +4 -122
  30. package/src/domains/paywall/components/README.md +0 -41
  31. package/src/domains/subscription/presentation/screens/README.md +0 -52
@@ -1,90 +1,20 @@
1
- /**
2
- * Subscription Detail Screen
3
- * Composition of subscription components
4
- * No business logic - pure presentation
5
- */
6
-
7
1
  import React, { useMemo } from "react";
8
- import { StyleSheet, View } from "react-native";
9
- import {
10
- useAppDesignTokens,
11
- NavigationHeader,
12
- AtomicIcon,
13
- } from "@umituz/react-native-design-system";
14
- import { Pressable } from "react-native";
2
+ import { StyleSheet, View, Pressable } from "react-native";
3
+ import { useAppDesignTokens, NavigationHeader, AtomicIcon } from "@umituz/react-native-design-system";
15
4
  import { ScreenLayout } from "../../../../shared/presentation";
16
5
  import { SubscriptionHeader } from "./components/SubscriptionHeader";
17
- import { CreditsList, type CreditItem } from "./components/CreditsList";
18
- import { UpgradePrompt, type Benefit } from "./components/UpgradePrompt";
19
-
20
- export interface SubscriptionDisplayFlags {
21
- showHeader: boolean;
22
- showCredits: boolean;
23
- showUpgradePrompt: boolean;
24
- showExpirationDate: boolean;
25
- }
26
-
27
- export interface SubscriptionDetailTranslations {
28
- title: string;
29
- statusActive: string;
30
- statusExpired: string;
31
- statusFree: string;
32
- statusCanceled: string;
33
- statusLabel: string;
34
- lifetimeLabel: string;
35
- expiresLabel: string;
36
- purchasedLabel: string;
37
- usageTitle?: string;
38
- creditsTitle: string;
39
- creditsResetInfo?: string;
40
- remainingLabel?: string;
41
- upgradeButton: string;
42
- }
43
-
44
- export interface UpgradePromptConfig {
45
- title: string;
46
- subtitle?: string;
47
- benefits?: readonly Benefit[];
48
- onUpgrade?: () => void;
49
- }
50
-
51
- export interface SubscriptionDetailConfig {
52
- display: SubscriptionDisplayFlags;
53
- statusType: "active" | "expired" | "none" | "canceled";
54
- isLifetime: boolean;
55
- expirationDate?: string;
56
- purchaseDate?: string;
57
- daysRemaining?: number | null;
58
- credits?: readonly CreditItem[];
59
- translations: SubscriptionDetailTranslations;
60
- upgradePrompt?: UpgradePromptConfig;
61
- onClose?: () => void;
62
- }
63
-
64
- export interface SubscriptionDetailScreenProps {
65
- config: SubscriptionDetailConfig;
66
- }
6
+ import { CreditsList } from "./components/CreditsList";
7
+ import { UpgradePrompt } from "./components/UpgradePrompt";
8
+ import { SubscriptionDetailScreenProps } from "./SubscriptionDetailScreen.types";
67
9
 
68
- export const SubscriptionDetailScreen: React.FC<
69
- SubscriptionDetailScreenProps
70
- > = ({ config }) => {
10
+ export const SubscriptionDetailScreen: React.FC<SubscriptionDetailScreenProps> = ({ config }) => {
71
11
  const tokens = useAppDesignTokens();
72
12
  const { showHeader, showCredits, showUpgradePrompt, showExpirationDate } = config.display;
73
13
 
74
- const styles = useMemo(
75
- () =>
76
- StyleSheet.create({
77
- content: {
78
- flexGrow: 1,
79
- padding: tokens.spacing.lg,
80
- gap: tokens.spacing.lg,
81
- },
82
- cardsContainer: {
83
- gap: tokens.spacing.xl,
84
- },
85
- }),
86
- [tokens]
87
- );
14
+ const styles = useMemo(() => StyleSheet.create({
15
+ content: { flexGrow: 1, padding: tokens.spacing.lg, gap: tokens.spacing.lg },
16
+ cardsContainer: { gap: tokens.spacing.xl }
17
+ }), [tokens]);
88
18
 
89
19
  return (
90
20
  <ScreenLayout
@@ -100,10 +30,7 @@ export const SubscriptionDetailScreen: React.FC<
100
30
  <Pressable
101
31
  onPress={config.onClose}
102
32
  style={({ pressed }) => ({
103
- width: 44,
104
- height: 44,
105
- justifyContent: "center",
106
- alignItems: "center",
33
+ width: 44, height: 44, justifyContent: "center", alignItems: "center",
107
34
  backgroundColor: pressed ? tokens.colors.surfaceVariant : tokens.colors.surface,
108
35
  borderRadius: tokens.radius.full,
109
36
  })}
@@ -126,18 +53,14 @@ export const SubscriptionDetailScreen: React.FC<
126
53
  translations={config.translations}
127
54
  />
128
55
  )}
129
-
130
56
  {showCredits && config.credits && (
131
57
  <CreditsList
132
- credits={config.credits}
133
- title={
134
- config.translations.usageTitle || config.translations.creditsTitle
135
- }
58
+ credits={config.credits as any}
59
+ title={config.translations.usageTitle || config.translations.creditsTitle}
136
60
  description={config.translations.creditsResetInfo}
137
61
  remainingLabel={config.translations.remainingLabel}
138
62
  />
139
63
  )}
140
-
141
64
  {showUpgradePrompt && config.upgradePrompt && (
142
65
  <UpgradePrompt
143
66
  title={config.upgradePrompt.title}
@@ -148,8 +71,6 @@ export const SubscriptionDetailScreen: React.FC<
148
71
  />
149
72
  )}
150
73
  </View>
151
-
152
-
153
74
  </ScreenLayout>
154
75
  );
155
76
  };
@@ -0,0 +1,47 @@
1
+ export interface SubscriptionDisplayFlags {
2
+ showHeader: boolean;
3
+ showCredits: boolean;
4
+ showUpgradePrompt: boolean;
5
+ showExpirationDate: boolean;
6
+ }
7
+
8
+ export interface SubscriptionDetailTranslations {
9
+ title: string;
10
+ statusActive: string;
11
+ statusExpired: string;
12
+ statusFree: string;
13
+ statusCanceled: string;
14
+ statusLabel: string;
15
+ lifetimeLabel: string;
16
+ expiresLabel: string;
17
+ purchasedLabel: string;
18
+ usageTitle?: string;
19
+ creditsTitle: string;
20
+ creditsResetInfo?: string;
21
+ remainingLabel?: string;
22
+ upgradeButton: string;
23
+ }
24
+
25
+ export interface UpgradePromptConfig {
26
+ title: string;
27
+ subtitle?: string;
28
+ benefits?: readonly { icon?: string; text: string }[];
29
+ onUpgrade?: () => void;
30
+ }
31
+
32
+ export interface SubscriptionDetailConfig {
33
+ display: SubscriptionDisplayFlags;
34
+ statusType: "active" | "expired" | "none" | "canceled";
35
+ isLifetime: boolean;
36
+ expirationDate?: string;
37
+ purchaseDate?: string;
38
+ daysRemaining?: number | null;
39
+ credits?: readonly any[];
40
+ translations: SubscriptionDetailTranslations;
41
+ upgradePrompt?: UpgradePromptConfig;
42
+ onClose?: () => void;
43
+ }
44
+
45
+ export interface SubscriptionDetailScreenProps {
46
+ config: SubscriptionDetailConfig;
47
+ }
@@ -1,150 +1,58 @@
1
- /**
2
- * Upgrade Prompt Component
3
- * Displays premium benefits for free users to encourage upgrade
4
- */
5
-
6
1
  import React, { useMemo } from "react";
7
2
  import { View, StyleSheet, TouchableOpacity } from "react-native";
8
- import {
9
- useAppDesignTokens,
10
- AtomicText,
11
- AtomicIcon,
12
- } from "@umituz/react-native-design-system";
13
-
14
- export interface Benefit {
15
- icon?: string;
16
- text: string;
17
- }
3
+ import { useAppDesignTokens, AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
4
+ import { UpgradePromptProps } from "./UpgradePrompt.types";
18
5
 
19
- export interface UpgradePromptProps {
20
- title: string;
21
- subtitle?: string;
22
- benefits?: readonly Benefit[];
23
- upgradeButtonLabel: string;
24
- onUpgrade: () => void;
25
- }
26
-
27
- export const UpgradePrompt: React.FC<UpgradePromptProps> = ({
28
- title,
29
- subtitle,
30
- benefits,
31
- upgradeButtonLabel,
32
- onUpgrade,
33
- }) => {
6
+ export const UpgradePrompt: React.FC<UpgradePromptProps> = ({ title, subtitle, benefits, upgradeButtonLabel, onUpgrade }) => {
34
7
  const tokens = useAppDesignTokens();
35
-
36
- const styles = useMemo(
37
- () =>
38
- StyleSheet.create({
39
- container: {
40
- gap: tokens.spacing.lg,
41
- },
42
- header: {
43
- alignItems: "center",
44
- gap: tokens.spacing.md,
45
- paddingVertical: tokens.spacing.md,
46
- },
47
- iconContainer: {
48
- width: 64,
49
- height: 64,
50
- borderRadius: 32,
51
- alignItems: "center",
52
- justifyContent: "center",
53
- backgroundColor: tokens.colors.primaryContainer,
54
- },
55
- title: {
56
- fontWeight: "700",
57
- textAlign: "center",
58
- },
59
- subtitle: {
60
- textAlign: "center",
61
- lineHeight: 22,
62
- },
63
- benefitsCard: {
64
- borderRadius: tokens.radius.lg,
65
- padding: tokens.spacing.lg,
66
- gap: tokens.spacing.md,
67
- backgroundColor: tokens.colors.surface,
68
- },
69
- benefitItem: {
70
- flexDirection: "row",
71
- alignItems: "center",
72
- gap: tokens.spacing.md,
73
- },
74
- benefitIconWrapper: {
75
- width: 32,
76
- height: 32,
77
- borderRadius: 16,
78
- alignItems: "center",
79
- justifyContent: "center",
80
- backgroundColor: tokens.colors.primaryContainer,
81
- },
82
- benefitText: {
83
- flex: 1,
84
- },
85
- upgradeButton: {
86
- paddingVertical: tokens.spacing.lg,
87
- borderRadius: tokens.radius.lg,
88
- alignItems: "center",
89
- backgroundColor: tokens.colors.primary,
90
- },
91
- buttonText: {
92
- color: tokens.colors.onPrimary,
93
- fontWeight: "700",
94
- },
95
- }),
96
- [tokens]
97
- );
8
+ const styles = useMemo(() => StyleSheet.create({
9
+ container: { gap: tokens.spacing.lg },
10
+ header: { alignItems: "center", gap: tokens.spacing.md, paddingVertical: tokens.spacing.md },
11
+ iconContainer: {
12
+ width: 64, height: 64, borderRadius: 32, alignItems: "center", justifyContent: "center",
13
+ backgroundColor: tokens.colors.primaryContainer,
14
+ },
15
+ title: { fontWeight: "700", textAlign: "center" },
16
+ subtitle: { textAlign: "center", lineHeight: 22 },
17
+ benefitsCard: {
18
+ borderRadius: tokens.radius.lg, padding: tokens.spacing.lg, gap: tokens.spacing.md,
19
+ backgroundColor: tokens.colors.surface,
20
+ },
21
+ benefitItem: { flexDirection: "row", alignItems: "center", gap: tokens.spacing.md },
22
+ benefitIconWrapper: {
23
+ width: 32, height: 32, borderRadius: 16, alignItems: "center", justifyContent: "center",
24
+ backgroundColor: tokens.colors.primaryContainer,
25
+ },
26
+ benefitText: { flex: 1 },
27
+ upgradeButton: {
28
+ paddingVertical: tokens.spacing.lg, borderRadius: tokens.radius.lg,
29
+ alignItems: "center", backgroundColor: tokens.colors.primary,
30
+ },
31
+ buttonText: { color: tokens.colors.onPrimary, fontWeight: "700" },
32
+ }), [tokens]);
98
33
 
99
34
  return (
100
35
  <View style={styles.container}>
101
36
  <View style={styles.header}>
102
- <View style={styles.iconContainer}>
103
- <AtomicIcon name="sparkles" customSize={32} color="primary" />
104
- </View>
105
- <AtomicText
106
- type="headlineSmall"
107
- style={[styles.title, { color: tokens.colors.textPrimary }]}
108
- >
109
- {title}
110
- </AtomicText>
111
- {subtitle && (
112
- <AtomicText
113
- type="bodyMedium"
114
- style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
115
- >
116
- {subtitle}
117
- </AtomicText>
118
- )}
37
+ <View style={styles.iconContainer}><AtomicIcon name="sparkles" customSize={32} color="primary" /></View>
38
+ <AtomicText type="headlineSmall" style={[styles.title, { color: tokens.colors.textPrimary }]}>{title}</AtomicText>
39
+ {subtitle && <AtomicText type="bodyMedium" style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>{subtitle}</AtomicText>}
119
40
  </View>
120
-
121
41
  {benefits && benefits.length > 0 && (
122
42
  <View style={styles.benefitsCard}>
123
43
  {benefits.map((benefit, index) => (
124
44
  <View key={index} style={styles.benefitItem}>
125
45
  <View style={styles.benefitIconWrapper}>
126
- <AtomicIcon
127
- name={benefit.icon || "checkmark-circle-outline"}
128
- customSize={16}
129
- color="primary"
130
- />
46
+ <AtomicIcon name={benefit.icon || "checkmark-circle-outline"} customSize={16} color="primary" />
131
47
  </View>
132
- <AtomicText
133
- type="bodyMedium"
134
- style={[styles.benefitText, { color: tokens.colors.textPrimary }]}
135
- >
136
- {benefit.text}
137
- </AtomicText>
48
+ <AtomicText type="bodyMedium" style={[styles.benefitText, { color: tokens.colors.textPrimary }]}>{benefit.text}</AtomicText>
138
49
  </View>
139
50
  ))}
140
51
  </View>
141
52
  )}
142
-
143
53
  {onUpgrade && upgradeButtonLabel && (
144
54
  <TouchableOpacity style={styles.upgradeButton} onPress={onUpgrade}>
145
- <AtomicText type="titleMedium" style={styles.buttonText}>
146
- {upgradeButtonLabel}
147
- </AtomicText>
55
+ <AtomicText type="titleMedium" style={styles.buttonText}>{upgradeButtonLabel}</AtomicText>
148
56
  </TouchableOpacity>
149
57
  )}
150
58
  </View>
@@ -0,0 +1,12 @@
1
+ export interface Benefit {
2
+ icon?: string;
3
+ text: string;
4
+ }
5
+
6
+ export interface UpgradePromptProps {
7
+ title: string;
8
+ subtitle?: string;
9
+ benefits?: readonly Benefit[];
10
+ upgradeButtonLabel: string;
11
+ onUpgrade: () => void;
12
+ }
@@ -1,13 +1,5 @@
1
- /**
2
- * usePremium Hook
3
- *
4
- * Complete subscription management.
5
- * Auth info automatically read from @umituz/react-native-auth.
6
- */
7
-
8
1
  import { useCallback } from 'react';
9
2
  import type { PurchasesPackage } from 'react-native-purchases';
10
- import type { UserCredits } from '../../credits/core/Credits';
11
3
  import { useCredits } from '../../credits/presentation/useCredits';
12
4
  import { useSubscriptionStatus } from './useSubscriptionStatus';
13
5
  import {
@@ -17,21 +9,10 @@ import {
17
9
  } from '../infrastructure/hooks/useSubscriptionQueries';
18
10
  import { usePaywallVisibility } from './usePaywallVisibility';
19
11
 
20
- export interface UsePremiumResult {
21
- isPremium: boolean;
22
- isLoading: boolean;
23
- packages: PurchasesPackage[];
24
- credits: UserCredits | null;
25
- showPaywall: boolean;
26
- isSyncing: boolean;
27
- purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
28
- restorePurchase: () => Promise<boolean>;
29
- setShowPaywall: (show: boolean) => void;
30
- closePaywall: () => void;
31
- openPaywall: () => void;
32
- }
12
+ import { UsePremiumResult } from './usePremium.types';
33
13
 
34
14
  export const usePremium = (): UsePremiumResult => {
15
+
35
16
  const { isPremium: subscriptionActive, isLoading: statusLoading } = useSubscriptionStatus();
36
17
  const { credits, isLoading: creditsLoading } = useCredits();
37
18
 
@@ -42,7 +23,7 @@ export const usePremium = (): UsePremiumResult => {
42
23
 
43
24
  const { showPaywall, setShowPaywall, closePaywall, openPaywall } = usePaywallVisibility();
44
25
 
45
- const isPremium = subscriptionActive;
26
+ const isPremium = subscriptionActive || (credits?.isPremium ?? false);
46
27
  const isSyncing = subscriptionActive && credits !== null && !credits.isPremium;
47
28
 
48
29
  const handlePurchase = useCallback(
@@ -0,0 +1,16 @@
1
+ import type { PurchasesPackage } from 'react-native-purchases';
2
+ import type { UserCredits } from '../../credits/core/Credits';
3
+
4
+ export interface UsePremiumResult {
5
+ isPremium: boolean;
6
+ isLoading: boolean;
7
+ packages: PurchasesPackage[];
8
+ credits: UserCredits | null;
9
+ showPaywall: boolean;
10
+ isSyncing: boolean;
11
+ purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
12
+ restorePurchase: () => Promise<boolean>;
13
+ setShowPaywall: (show: boolean) => void;
14
+ closePaywall: () => void;
15
+ openPaywall: () => void;
16
+ }
@@ -1,34 +1,20 @@
1
- /**
2
- * useSubscriptionStatus Hook
3
- *
4
- * Checks real subscription status from RevenueCat.
5
- * Auth info automatically read from @umituz/react-native-auth.
6
- */
7
-
8
- import { useQuery } from "@umituz/react-native-design-system";
9
- import {
10
- useAuthStore,
11
- selectUserId,
12
- } from "@umituz/react-native-auth";
1
+ import { useQuery, useQueryClient } from "@umituz/react-native-design-system";
2
+ import { useEffect } from "react";
3
+ import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
13
4
  import { SubscriptionManager } from "../infrastructure/managers/SubscriptionManager";
5
+ import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
6
+ import { SubscriptionStatusResult } from "./useSubscriptionStatus.types";
14
7
 
15
8
  export const subscriptionStatusQueryKeys = {
16
9
  all: ["subscriptionStatus"] as const,
17
10
  user: (userId: string) => ["subscriptionStatus", userId] as const,
18
11
  };
19
12
 
20
- export interface SubscriptionStatusResult {
21
- isPremium: boolean;
22
- expirationDate: Date | null;
23
- isLoading: boolean;
24
- error: Error | null;
25
- refetch: () => void;
26
- }
27
-
28
13
  export const useSubscriptionStatus = (): SubscriptionStatusResult => {
29
14
  const userId = useAuthStore(selectUserId);
15
+ const queryClient = useQueryClient();
30
16
 
31
- const { data, isLoading, error, refetch } = useQuery({
17
+ const { data, status, error, refetch } = useQuery({
32
18
  queryKey: subscriptionStatusQueryKeys.user(userId ?? ""),
33
19
  queryFn: async () => {
34
20
  if (!userId) {
@@ -43,9 +29,27 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
43
29
  }
44
30
  },
45
31
  enabled: !!userId && SubscriptionManager.isInitializedForUser(userId),
46
-
47
32
  });
48
33
 
34
+ useEffect(() => {
35
+ if (!userId) return;
36
+
37
+ const unsubscribe = subscriptionEventBus.on(
38
+ SUBSCRIPTION_EVENTS.PREMIUM_STATUS_CHANGED,
39
+ (event: { userId: string; isPremium: boolean }) => {
40
+ if (event.userId === userId) {
41
+ queryClient.invalidateQueries({
42
+ queryKey: subscriptionStatusQueryKeys.user(userId),
43
+ });
44
+ }
45
+ }
46
+ );
47
+
48
+ return unsubscribe;
49
+ }, [userId, queryClient]);
50
+
51
+ const isLoading = status === "pending";
52
+
49
53
  return {
50
54
  isPremium: data?.isPremium ?? false,
51
55
  expirationDate: data?.expirationDate ?? null,
@@ -54,3 +58,7 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
54
58
  refetch,
55
59
  };
56
60
  };
61
+
62
+
63
+
64
+
@@ -0,0 +1,7 @@
1
+ export interface SubscriptionStatusResult {
2
+ isPremium: boolean;
3
+ expirationDate: Date | null;
4
+ isLoading: boolean;
5
+ error: Error | null;
6
+ refetch: () => void;
7
+ }
@@ -1,10 +1,3 @@
1
- /**
2
- * useProductMetadata Hook
3
- *
4
- * TanStack Query hook for fetching product metadata.
5
- * Generic and reusable - uses config from ProductMetadataService.
6
- */
7
-
8
1
  import { useQuery } from "@umituz/react-native-design-system";
9
2
  import { useMemo } from "react";
10
3
  import type {
@@ -14,11 +7,6 @@ import type {
14
7
  } from "../../domain/types/wallet.types";
15
8
  import { ProductMetadataService } from "../../infrastructure/services/ProductMetadataService";
16
9
 
17
- const CACHE_CONFIG = {
18
- staleTime: 5 * 60 * 1000, // 5 minutes
19
- gcTime: 30 * 60 * 1000, // 30 minutes
20
- };
21
-
22
10
  export const productMetadataQueryKeys = {
23
11
  all: ["productMetadata"] as const,
24
12
  byType: (type: ProductType) => ["productMetadata", type] as const,
@@ -44,7 +32,6 @@ export function useProductMetadata({
44
32
  type,
45
33
  enabled = true,
46
34
  }: UseProductMetadataParams): UseProductMetadataResult {
47
- // Memoize service to prevent recreation on every render
48
35
  const service = useMemo(
49
36
  () => new ProductMetadataService(config),
50
37
  [config]
@@ -54,7 +41,7 @@ export function useProductMetadata({
54
41
  ? productMetadataQueryKeys.byType(type)
55
42
  : productMetadataQueryKeys.all;
56
43
 
57
- const { data, isLoading, error, refetch } = useQuery({
44
+ const { data, status, error, refetch } = useQuery({
58
45
  queryKey,
59
46
  queryFn: async () => {
60
47
  if (type) {
@@ -63,11 +50,10 @@ export function useProductMetadata({
63
50
  return service.getAll();
64
51
  },
65
52
  enabled,
66
- staleTime: CACHE_CONFIG.staleTime,
67
- gcTime: CACHE_CONFIG.gcTime,
68
53
  });
69
54
 
70
55
  const products = data ?? [];
56
+ const isLoading = status === "pending";
71
57
 
72
58
  const creditsPackages = products.filter((p) => p.type === "credits");
73
59
  const subscriptionPackages = products.filter((p) => p.type === "subscription");
@@ -81,3 +67,4 @@ export function useProductMetadata({
81
67
  subscriptionPackages,
82
68
  };
83
69
  }
70
+
@@ -1,16 +1,6 @@
1
- /**
2
- * useTransactionHistory Hook
3
- *
4
- * TanStack Query hook for fetching credit transaction history.
5
- * Auth info automatically read from @umituz/react-native-auth.
6
- */
7
-
8
1
  import { useQuery } from "@umituz/react-native-design-system";
9
2
  import { useMemo } from "react";
10
- import {
11
- useAuthStore,
12
- selectUserId,
13
- } from "@umituz/react-native-auth";
3
+ import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
14
4
  import type {
15
5
  CreditLog,
16
6
  TransactionRepositoryConfig,
@@ -63,8 +53,6 @@ export function useTransactionHistory({
63
53
  return result.data ?? [];
64
54
  },
65
55
  enabled: !!userId,
66
- staleTime: 0,
67
- gcTime: 0,
68
56
  });
69
57
 
70
58
  const transactions = data ?? [];