@umituz/react-native-subscription 2.27.95 → 2.27.97

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/core/Credits.ts +3 -11
  3. package/src/domains/credits/infrastructure/CreditsRepository.ts +28 -1
  4. package/src/domains/paywall/components/PaywallContainer.tsx +17 -1
  5. package/src/domains/paywall/components/PaywallContainer.types.ts +2 -1
  6. package/src/domains/paywall/hooks/usePaywallActions.ts +1 -1
  7. package/src/domains/subscription/application/SubscriptionInitializer.ts +1 -1
  8. package/src/domains/subscription/application/SubscriptionSyncService.ts +32 -5
  9. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
  10. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +32 -12
  11. package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +4 -4
  12. package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +1 -7
  13. package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -2
  14. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +5 -4
  15. package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +20 -7
  16. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +1 -1
  17. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +55 -16
  18. package/src/domains/subscription/presentation/screens/components/CreditsList.tsx +14 -1
  19. package/src/domains/subscription/presentation/screens/components/DevTestSection.tsx +10 -2
  20. package/src/domains/subscription/presentation/screens/components/SubscriptionActions.tsx +6 -1
  21. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +20 -1
  22. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +13 -1
  23. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +1 -1
  24. package/src/domains/subscription/presentation/useFeatureGate.ts +11 -7
  25. package/src/domains/subscription/presentation/usePaywallVisibility.ts +1 -1
  26. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -5
  27. package/src/init/index.ts +0 -3
  28. package/src/presentation/hooks/index.ts +0 -4
  29. package/src/shared/infrastructure/SubscriptionEventBus.ts +27 -0
  30. package/src/types/i18next.d.ts +2 -0
  31. package/src/utils/packageTypeDetector.ts +0 -4
  32. package/src/domains/subscription/presentation/types/README.md +0 -22
  33. package/src/domains/subscription/presentation/types/SubscriptionDetailTypes.ts +0 -153
  34. package/src/domains/subscription/presentation/types/SubscriptionSettingsTypes.ts +0 -74
  35. package/src/domains/subscription/presentation/useAuthSubscriptionSync.ts +0 -63
  36. package/src/domains/subscription/presentation/usePremiumGate.ts +0 -84
  37. package/src/domains/subscription/presentation/useSavedPurchaseAutoExecution.ts +0 -148
  38. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.ts +0 -115
  39. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.utils.ts +0 -57
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.27.95",
3
+ "version": "2.27.97",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { SubscriptionPackageType } from "../../../utils/packageTypeDetector";
9
+ // Types imported from SubscriptionConstants are used directly in UserCredits interface
9
10
  import type {
10
11
  SubscriptionStatusType,
11
12
  PeriodType,
@@ -15,15 +16,6 @@ import type {
15
16
  PurchaseType
16
17
  } from "../../subscription/core/SubscriptionConstants";
17
18
 
18
- export type {
19
- SubscriptionStatusType,
20
- PeriodType,
21
- PackageType,
22
- Platform,
23
- PurchaseSource,
24
- PurchaseType
25
- };
26
-
27
19
  export type CreditType = "text" | "image";
28
20
 
29
21
  /** Single Source of Truth for user subscription + credits data */
@@ -67,10 +59,10 @@ export interface CreditAllocation {
67
59
  credits: number;
68
60
  }
69
61
 
70
- export type PackageAllocationMap = Record<
62
+ export type PackageAllocationMap = Partial<Record<
71
63
  Exclude<SubscriptionPackageType, "unknown">,
72
64
  CreditAllocation
73
- >;
65
+ >>;
74
66
 
75
67
  export interface CreditsConfig {
76
68
  collectionName: string;
@@ -12,6 +12,8 @@ import { CreditsMapper } from "../core/CreditsMapper";
12
12
  import type { RevenueCatData } from "../../subscription/core/RevenueCatData";
13
13
  import { DeductCreditsCommand } from "../application/DeductCreditsCommand";
14
14
  import { CreditLimitCalculator } from "../application/CreditLimitCalculator";
15
+ import { PURCHASE_TYPE, type PurchaseType } from "../../subscription/core/SubscriptionConstants";
16
+ import { updateDoc } from "firebase/firestore";
15
17
 
16
18
  export class CreditsRepository extends BaseRepository {
17
19
  private deductCommand: DeductCreditsCommand;
@@ -47,7 +49,8 @@ export class CreditsRepository extends BaseRepository {
47
49
  purchaseId: string,
48
50
  productId: string,
49
51
  source: PurchaseSource,
50
- revenueCatData: RevenueCatData
52
+ revenueCatData: RevenueCatData,
53
+ type: PurchaseType = PURCHASE_TYPE.INITIAL
51
54
  ): Promise<CreditsResult> {
52
55
  const db = getFirestore();
53
56
  if (!db) {
@@ -70,6 +73,7 @@ export class CreditsRepository extends BaseRepository {
70
73
  originalTransactionId: revenueCatData.originalTransactionId,
71
74
  isPremium: revenueCatData.isPremium,
72
75
  periodType: revenueCatData.periodType,
76
+ type,
73
77
  }
74
78
  );
75
79
 
@@ -86,4 +90,27 @@ export class CreditsRepository extends BaseRepository {
86
90
  async deductCredit(userId: string, cost: number): Promise<DeductCreditsResult> {
87
91
  return this.deductCommand.execute(userId, cost);
88
92
  }
93
+
94
+ async hasCredits(userId: string, cost: number): Promise<boolean> {
95
+ const result = await this.getCredits(userId);
96
+ if (!result.success || !result.data) return false;
97
+ return result.data.credits >= cost;
98
+ }
99
+
100
+ async syncExpiredStatus(userId: string): Promise<void> {
101
+ const db = getFirestore();
102
+ if (!db) throw new Error("Firestore instance is not available");
103
+
104
+ const ref = this.getRef(db, userId);
105
+ await updateDoc(ref, {
106
+ isPremium: false,
107
+ status: "expired",
108
+ willRenew: false,
109
+ expirationDate: new Date().toISOString()
110
+ });
111
+ }
112
+ }
113
+
114
+ export function createCreditsRepository(config: CreditsConfig): CreditsRepository {
115
+ return new CreditsRepository(config);
89
116
  }
@@ -49,9 +49,25 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
49
49
  });
50
50
 
51
51
  // Check trial eligibility only if trialConfig is enabled
52
+ // Use ref to track if we've already checked for these packages to avoid redundant calls
53
+ const checkedPackagesRef = React.useRef<string[]>([]);
54
+
52
55
  useEffect(() => {
53
56
  if (!trialConfig?.enabled) return;
54
57
  if (packages.length === 0) return;
58
+ if (isLoading) return; // Wait for packages to fully load
59
+
60
+ // Get current package identifiers
61
+ const currentPackageIds = packages.map((pkg) => pkg.product.identifier);
62
+ const sortedIds = [...currentPackageIds].sort().join(",");
63
+
64
+ // Skip if we've already checked these exact packages
65
+ if (checkedPackagesRef.current.join(",") === sortedIds) {
66
+ return;
67
+ }
68
+
69
+ // Update ref
70
+ checkedPackagesRef.current = currentPackageIds;
55
71
 
56
72
  // Get all actual product IDs from packages
57
73
  const allProductIds = packages.map((pkg) => pkg.product.identifier);
@@ -72,7 +88,7 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
72
88
  if (productIdsToCheck.length > 0) {
73
89
  checkEligibility(productIdsToCheck);
74
90
  }
75
- }, [packages, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
91
+ }, [packages, isLoading, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
76
92
 
77
93
  // Convert eligibility map to format expected by PaywallModal
78
94
  // Only process if trial is enabled
@@ -5,7 +5,8 @@
5
5
 
6
6
  import type { ImageSourcePropType } from "react-native";
7
7
  import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
8
- import type { PurchaseSource, PackageAllocationMap } from "../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
9
+ import type { PackageAllocationMap } from "../../credits/core/Credits";
9
10
 
10
11
  /**
11
12
  * Trial display configuration
@@ -2,7 +2,7 @@ import { useCallback } from "react";
2
2
  import type { PurchasesPackage } from "react-native-purchases";
3
3
  import { useRestorePurchase } from "../../subscription/infrastructure/hooks/useRestorePurchase";
4
4
  import { useAuthAwarePurchase } from "../../subscription/presentation/useAuthAwarePurchase";
5
- import type { PurchaseSource } from "../../credits/core/Credits";
5
+ import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
6
6
 
7
7
  interface UsePaywallActionsProps {
8
8
  source?: PurchaseSource;
@@ -92,7 +92,7 @@ export const initializeSubscription = async (config: SubscriptionInitConfig): Pr
92
92
  showAuthModal,
93
93
  });
94
94
 
95
- const initializeInBackground = async (userId: string): Promise<void> => {
95
+ const initializeInBackground = async (userId?: string): Promise<void> => {
96
96
  await SubscriptionManager.initialize(userId);
97
97
  if (__DEV__) {
98
98
  console.log('[SubscriptionInitializer] Background init complete');
@@ -1,6 +1,6 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
2
  import type { RevenueCatData } from "../core/RevenueCatData";
3
- import { type PeriodType, type PurchaseSource } from "../core/SubscriptionConstants";
3
+ import { type PeriodType, type PurchaseSource, PURCHASE_SOURCE, PURCHASE_TYPE } from "../core/SubscriptionConstants";
4
4
  import { getCreditsRepository } from "../../credits/infrastructure/CreditsRepositoryProvider";
5
5
  import { extractRevenueCatData } from "./SubscriptionSyncUtils";
6
6
  import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
@@ -19,7 +19,14 @@ export class SubscriptionSyncService {
19
19
  ? `purchase_${revenueCatData.originalTransactionId}`
20
20
  : `purchase_${productId}_${Date.now()}`;
21
21
 
22
- await getCreditsRepository().initializeCredits(userId, purchaseId, productId, source, revenueCatData);
22
+ await getCreditsRepository().initializeCredits(
23
+ userId,
24
+ purchaseId,
25
+ productId,
26
+ source ?? PURCHASE_SOURCE.SETTINGS, // Default to settings if source unknown
27
+ revenueCatData,
28
+ PURCHASE_TYPE.INITIAL // Default to INITIAL
29
+ );
23
30
 
24
31
  // Notify listeners via Event Bus
25
32
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, userId);
@@ -37,7 +44,14 @@ export class SubscriptionSyncService {
37
44
  ? `renewal_${revenueCatData.originalTransactionId}_${newExpirationDate}`
38
45
  : `renewal_${productId}_${Date.now()}`;
39
46
 
40
- await getCreditsRepository().initializeCredits(userId, purchaseId, productId, "renewal", revenueCatData);
47
+ await getCreditsRepository().initializeCredits(
48
+ userId,
49
+ purchaseId,
50
+ productId,
51
+ PURCHASE_SOURCE.RENEWAL,
52
+ revenueCatData,
53
+ PURCHASE_TYPE.RENEWAL
54
+ );
41
55
 
42
56
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, userId);
43
57
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.RENEWAL_DETECTED, { userId, productId });
@@ -61,14 +75,27 @@ export class SubscriptionSyncService {
61
75
  return;
62
76
  }
63
77
 
78
+ // If productId is missing, we can't initialize credits fully,
79
+ // but if isPremium is true, we should have it.
80
+ // Fallback to 'unknown' if missing, but this might throw in CreditLimitCalculator.
81
+ const validProductId = productId ?? 'unknown_product';
82
+
64
83
  const revenueCatData: RevenueCatData = {
65
84
  expirationDate: expiresAt ?? null,
66
85
  willRenew: willRenew ?? false,
67
86
  isPremium,
68
- periodType
87
+ periodType: periodType ?? null, // Fix undefined vs null
88
+ originalTransactionId: null // Initialize with null as we might not have it here
69
89
  };
70
90
 
71
- await getCreditsRepository().initializeCredits(userId, `status_sync_${Date.now()}`, productId, "settings", revenueCatData);
91
+ await getCreditsRepository().initializeCredits(
92
+ userId,
93
+ `status_sync_${Date.now()}`,
94
+ validProductId,
95
+ PURCHASE_SOURCE.SETTINGS,
96
+ revenueCatData,
97
+ PURCHASE_TYPE.INITIAL // Status sync treated as Initial or Update
98
+ );
72
99
 
73
100
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, userId);
74
101
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.PREMIUM_STATUS_CHANGED, { userId, isPremium });
@@ -12,7 +12,7 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
12
12
  willRenew: entitlement?.willRenew ?? false,
13
13
  // Use latestPurchaseDate if originalPurchaseDate is missing, or a combine id
14
14
  originalTransactionId: entitlement?.originalPurchaseDate || customerInfo.firstSeen,
15
- periodType: entitlement?.periodType as PeriodType | undefined,
15
+ periodType: (entitlement?.periodType as PeriodType) ?? null,
16
16
  isPremium: !!customerInfo.entitlements.active[entitlementId],
17
17
  };
18
18
  };
@@ -25,21 +25,41 @@ export class PackageHandler {
25
25
 
26
26
  async fetchPackages(): Promise<PurchasesPackage[]> {
27
27
  if (!this.service.isInitialized()) {
28
- throw new Error("Service not initialized");
28
+ throw new Error("Service not initialized. Please initialize before fetching packages.");
29
29
  }
30
30
 
31
- const offering = await this.service.fetchOfferings();
32
-
33
- if (!offering) {
34
- throw new Error("No offerings available");
31
+ try {
32
+ const offering = await this.service.fetchOfferings();
33
+
34
+ if (!offering) {
35
+ if (__DEV__) {
36
+ console.warn("[PackageHandler] No offerings available from RevenueCat");
37
+ }
38
+ // Return empty array instead of throwing - allows graceful degradation
39
+ return [];
40
+ }
41
+
42
+ const packages = offering.availablePackages;
43
+ if (!packages || packages.length === 0) {
44
+ if (__DEV__) {
45
+ console.warn("[PackageHandler] No packages available in offering");
46
+ }
47
+ // Return empty array instead of throwing - allows graceful degradation
48
+ return [];
49
+ }
50
+
51
+ return packages;
52
+ } catch (error) {
53
+ if (__DEV__) {
54
+ console.error("[PackageHandler] Failed to fetch packages:", error);
55
+ }
56
+ // Re-throw with more context
57
+ throw new Error(
58
+ `Failed to fetch subscription packages. ${
59
+ error instanceof Error ? error.message : "Unknown error"
60
+ }`
61
+ );
35
62
  }
36
-
37
- const packages = offering.availablePackages;
38
- if (!packages) {
39
- throw new Error("No packages available in offering");
40
- }
41
-
42
- return packages;
43
63
  }
44
64
 
45
65
  async purchase(pkg: PurchasesPackage, userId: string): Promise<boolean> {
@@ -3,6 +3,9 @@
3
3
  * TanStack Query keys and constants for subscription state
4
4
  */
5
5
 
6
+ /** Query cache time constants */
7
+
8
+
6
9
  /**
7
10
  * Query keys for TanStack Query
8
11
  */
@@ -12,7 +15,4 @@ export const SUBSCRIPTION_QUERY_KEYS = {
12
15
  ["subscription", "initialized", userId] as const,
13
16
  } as const;
14
17
 
15
- // No cache - always fetch fresh data for subscription packages
16
- // This ensures users always see real-time subscription status
17
- export const STALE_TIME = 0; // Always stale - refetch immediately
18
- export const GC_TIME = 0; // Don't cache - garbage collect immediately
18
+
@@ -12,8 +12,6 @@ import {
12
12
  import { SubscriptionManager } from '../../infrastructure/managers/SubscriptionManager';
13
13
  import {
14
14
  SUBSCRIPTION_QUERY_KEYS,
15
- STALE_TIME,
16
- GC_TIME,
17
15
  } from "./subscriptionQueryKeys";
18
16
 
19
17
  /**
@@ -41,11 +39,7 @@ export const useSubscriptionPackages = () => {
41
39
 
42
40
  return SubscriptionManager.getPackages();
43
41
  },
44
- staleTime: STALE_TIME,
45
- gcTime: GC_TIME,
46
42
  enabled: isConfigured,
47
- refetchOnMount: true,
48
- refetchOnWindowFocus: true, // Refetch when app becomes active
49
- refetchOnReconnect: true, // Refetch when network reconnects
43
+
50
44
  });
51
45
  };
@@ -6,8 +6,6 @@
6
6
 
7
7
  export {
8
8
  SUBSCRIPTION_QUERY_KEYS,
9
- STALE_TIME,
10
- GC_TIME,
11
9
  } from "./subscriptionQueryKeys";
12
10
  export { useInitializeSubscription } from "./useInitializeSubscription";
13
11
  export { useSubscriptionPackages } from "./useSubscriptionPackages";
@@ -53,10 +53,11 @@ class SubscriptionManagerImpl {
53
53
  }
54
54
  }
55
55
 
56
- async initialize(userId: string): Promise<boolean> {
56
+ async initialize(userId?: string): Promise<boolean> {
57
57
  this.ensureConfigured();
58
58
 
59
- const { shouldInit, existingPromise } = this.state.initCache.tryAcquireInitialization(userId);
59
+ const actualUserId = userId ?? (await this.managerConfig!.getAnonymousUserId());
60
+ const { shouldInit, existingPromise } = this.state.initCache.tryAcquireInitialization(actualUserId);
60
61
 
61
62
  if (!shouldInit && existingPromise) {
62
63
  return existingPromise;
@@ -71,11 +72,11 @@ class SubscriptionManagerImpl {
71
72
  }
72
73
 
73
74
  this.ensurePackageHandlerInitialized();
74
- const result = await this.serviceInstance.initialize(userId);
75
+ const result = await this.serviceInstance.initialize(actualUserId);
75
76
  return result.success;
76
77
  })();
77
78
 
78
- this.state.initCache.setPromise(promise, userId);
79
+ this.state.initCache.setPromise(promise, actualUserId);
79
80
  return promise;
80
81
  }
81
82
 
@@ -11,6 +11,8 @@ export class InitializationCache {
11
11
  private initializationInProgress = false;
12
12
  // Track which userId the promise is for (separate from currentUserId which is set after completion)
13
13
  private promiseUserId: string | null = null;
14
+ // Track promise completion state to avoid returning failed promises
15
+ private promiseCompleted = true;
14
16
 
15
17
  /**
16
18
  * Atomically check if reinitialization is needed AND reserve the slot
@@ -22,17 +24,24 @@ export class InitializationCache {
22
24
  return { shouldInit: false, existingPromise: this.initPromise };
23
25
  }
24
26
 
25
- // If already initialized for this user and promise resolved successfully, return it
26
- if (this.initPromise && this.currentUserId === userId && !this.initializationInProgress) {
27
+ // If already initialized for this user and promise completed successfully, return it
28
+ // Only return cached promise if it completed AND it's for the same user
29
+ if (this.initPromise && this.currentUserId === userId && !this.initializationInProgress && this.promiseCompleted) {
27
30
  return { shouldInit: false, existingPromise: this.initPromise };
28
31
  }
29
32
 
30
- // Different user or no initialization - need to reinitialize
31
- // Atomically set the flag
32
- this.initializationInProgress = true;
33
- this.promiseUserId = userId;
33
+ // Different user, no initialization, or failed promise - need to reinitialize
34
+ // Atomically set the flag and clear previous state if needed
35
+ if (!this.initializationInProgress) {
36
+ this.initializationInProgress = true;
37
+ this.promiseUserId = userId;
38
+ this.promiseCompleted = false;
39
+ return { shouldInit: true, existingPromise: null };
40
+ }
34
41
 
35
- return { shouldInit: true, existingPromise: null };
42
+ // If we reach here, initialization is in progress for a different user
43
+ // Wait for current initialization to complete
44
+ return { shouldInit: false, existingPromise: this.initPromise };
36
45
  }
37
46
 
38
47
  setPromise(promise: Promise<boolean>, userId: string): void {
@@ -45,6 +54,7 @@ export class InitializationCache {
45
54
  if (result && this.promiseUserId === userId) {
46
55
  this.currentUserId = userId;
47
56
  }
57
+ this.promiseCompleted = true;
48
58
  return result;
49
59
  })
50
60
  .catch(() => {
@@ -52,7 +62,9 @@ export class InitializationCache {
52
62
  if (this.promiseUserId === userId) {
53
63
  this.initPromise = null;
54
64
  this.promiseUserId = null;
65
+ this.currentUserId = null; // Clear user on failure
55
66
  }
67
+ this.promiseCompleted = true;
56
68
  })
57
69
  .finally(() => {
58
70
  // Always release the mutex
@@ -71,5 +83,6 @@ export class InitializationCache {
71
83
  this.currentUserId = null;
72
84
  this.initializationInProgress = false;
73
85
  this.promiseUserId = null;
86
+ this.promiseCompleted = true;
74
87
  }
75
88
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { CustomerInfo } from "react-native-purchases";
7
7
  import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
8
- import type { PurchaseSource } from "../../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
9
9
  import { getPremiumEntitlement } from "../../core/RevenueCatTypes";
10
10
 
11
11
  export async function syncPremiumStatus(
@@ -11,22 +11,61 @@ import {
11
11
  ScreenLayout,
12
12
  } from "@umituz/react-native-design-system";
13
13
  import { SubscriptionHeader } from "./components/SubscriptionHeader";
14
- import { CreditsList } from "./components/CreditsList";
15
- import { UpgradePrompt } from "./components/UpgradePrompt";
16
- import { DevTestSection } from "./components/DevTestSection";
17
- import type { SubscriptionDetailScreenProps } from "../types/SubscriptionDetailTypes";
14
+ import { CreditsList, type CreditItem } from "./components/CreditsList";
15
+ import { UpgradePrompt, type Benefit } from "./components/UpgradePrompt";
16
+ import { DevTestSection, type DevTestActions } from "./components/DevTestSection";
18
17
 
19
- export type {
20
- SubscriptionDisplayFlags,
21
- SubscriptionDetailTranslations,
22
- SubscriptionDetailConfig,
23
- SubscriptionDetailScreenProps,
24
- DevTestActions,
25
- DevToolsConfig,
26
- UpgradeBenefit,
27
- UpgradePromptConfig,
28
- UpgradePromptProps,
29
- } from "../types/SubscriptionDetailTypes";
18
+ export interface SubscriptionDisplayFlags {
19
+ showHeader: boolean;
20
+ showCredits: boolean;
21
+ showUpgradePrompt: boolean;
22
+ showExpirationDate: boolean;
23
+ }
24
+
25
+ export interface SubscriptionDetailTranslations {
26
+ title: string;
27
+ statusActive: string;
28
+ statusExpired: string;
29
+ statusFree: string;
30
+ statusCanceled: string;
31
+ statusLabel: string;
32
+ lifetimeLabel: string;
33
+ expiresLabel: string;
34
+ purchasedLabel: string;
35
+ usageTitle?: string;
36
+ creditsTitle: string;
37
+ creditsResetInfo?: string;
38
+ remainingLabel?: string;
39
+ upgradeButton: string;
40
+ }
41
+
42
+ export interface DevToolsConfig {
43
+ actions: DevTestActions;
44
+ title?: string;
45
+ }
46
+
47
+ export interface UpgradePromptConfig {
48
+ title: string;
49
+ subtitle?: string;
50
+ benefits?: readonly Benefit[];
51
+ }
52
+
53
+ export interface SubscriptionDetailConfig {
54
+ display: SubscriptionDisplayFlags;
55
+ statusType: "active" | "expired" | "none" | "canceled";
56
+ isLifetime: boolean;
57
+ expirationDate?: string;
58
+ purchaseDate?: string;
59
+ daysRemaining?: number | null;
60
+ credits?: readonly CreditItem[];
61
+ translations: SubscriptionDetailTranslations;
62
+ upgradePrompt?: UpgradePromptConfig & { onUpgrade?: () => void };
63
+ devTools?: DevToolsConfig;
64
+ }
65
+
66
+ export interface SubscriptionDetailScreenProps {
67
+ config: SubscriptionDetailConfig;
68
+ }
30
69
 
31
70
  export const SubscriptionDetailScreen: React.FC<
32
71
  SubscriptionDetailScreenProps
@@ -98,7 +137,7 @@ export const SubscriptionDetailScreen: React.FC<
98
137
  subtitle={config.upgradePrompt.subtitle}
99
138
  benefits={config.upgradePrompt.benefits}
100
139
  upgradeButtonLabel={config.translations.upgradeButton}
101
- onUpgrade={config.onUpgrade}
140
+ onUpgrade={config.upgradePrompt.onUpgrade ?? (() => {})}
102
141
  />
103
142
  )}
104
143
  </View>
@@ -7,7 +7,20 @@ import React, { useMemo } from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
9
  import { CreditRow } from "../../components/details/CreditRow";
10
- import type { CreditsListProps } from "../../types/SubscriptionDetailTypes";
10
+
11
+ export interface CreditItem {
12
+ id: string;
13
+ label: string;
14
+ current: number;
15
+ total: number;
16
+ }
17
+
18
+ export interface CreditsListProps {
19
+ credits: readonly CreditItem[];
20
+ title?: string;
21
+ description?: string;
22
+ remainingLabel?: string;
23
+ }
11
24
 
12
25
  export const CreditsList: React.FC<CreditsListProps> = ({
13
26
  credits,
@@ -7,9 +7,17 @@
7
7
  import React, { useMemo } from "react";
8
8
  import { View, TouchableOpacity, StyleSheet } from "react-native";
9
9
  import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
10
- import type { DevTestSectionProps } from "../../types/SubscriptionDetailTypes";
11
10
 
12
- export type { DevTestActions, DevTestSectionProps } from "../../types/SubscriptionDetailTypes";
11
+ export interface DevTestActions {
12
+ onTestRenewal: () => void;
13
+ onCheckCredits: () => void;
14
+ onTestDuplicate: () => void;
15
+ }
16
+
17
+ export interface DevTestSectionProps {
18
+ actions: DevTestActions;
19
+ title?: string;
20
+ }
13
21
 
14
22
  /** Dev test button translations */
15
23
  export interface DevTestTranslations {
@@ -6,7 +6,12 @@
6
6
  import React, { useMemo } from "react";
7
7
  import { View, StyleSheet, TouchableOpacity } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
- import type { SubscriptionActionsProps } from "../../types/SubscriptionDetailTypes";
9
+
10
+ export interface SubscriptionActionsProps {
11
+ isPremium: boolean;
12
+ upgradeButtonLabel?: string;
13
+ onUpgrade?: () => void;
14
+ }
10
15
 
11
16
  export const SubscriptionActions: React.FC<SubscriptionActionsProps> = ({
12
17
  isPremium,
@@ -8,7 +8,26 @@ import { View, StyleSheet } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
9
  import { PremiumStatusBadge } from "../../components/details/PremiumStatusBadge";
10
10
  import { DetailRow } from "../../components/details/DetailRow";
11
- import type { SubscriptionHeaderProps } from "../../types/SubscriptionDetailTypes";
11
+
12
+ export interface SubscriptionHeaderProps {
13
+ statusType: "active" | "expired" | "none" | "canceled";
14
+ showExpirationDate: boolean;
15
+ isLifetime: boolean;
16
+ expirationDate?: string;
17
+ purchaseDate?: string;
18
+ daysRemaining?: number | null;
19
+ translations: {
20
+ title: string;
21
+ statusActive: string;
22
+ statusExpired: string;
23
+ statusFree: string;
24
+ statusCanceled: string;
25
+ statusLabel: string;
26
+ lifetimeLabel: string;
27
+ expiresLabel: string;
28
+ purchasedLabel: string;
29
+ };
30
+ }
12
31
 
13
32
  export const SubscriptionHeader: React.FC<SubscriptionHeaderProps> = ({
14
33
  statusType,
@@ -10,7 +10,19 @@ import {
10
10
  AtomicText,
11
11
  AtomicIcon,
12
12
  } from "@umituz/react-native-design-system";
13
- import type { UpgradePromptProps } from "../../types/SubscriptionDetailTypes";
13
+
14
+ export interface Benefit {
15
+ icon?: string;
16
+ text: string;
17
+ }
18
+
19
+ export interface UpgradePromptProps {
20
+ title: string;
21
+ subtitle?: string;
22
+ benefits?: readonly Benefit[];
23
+ upgradeButtonLabel: string;
24
+ onUpgrade: () => void;
25
+ }
14
26
 
15
27
  export const UpgradePrompt: React.FC<UpgradePromptProps> = ({
16
28
  title,
@@ -6,7 +6,7 @@
6
6
  import { useCallback } from "react";
7
7
  import type { PurchasesPackage } from "react-native-purchases";
8
8
  import { usePremium } from "./usePremium";
9
- import type { PurchaseSource } from "../../credits/core/Credits";
9
+ import type { PurchaseSource } from "../core/SubscriptionConstants";
10
10
 
11
11
  export interface PurchaseAuthProvider {
12
12
  isAuthenticated: () => boolean;