@umituz/react-native-subscription 2.27.115 → 2.27.117
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/package.json +4 -4
- package/src/domains/credits/infrastructure/CreditsRepository.ts +16 -19
- package/src/domains/credits/utils/creditCalculations.ts +6 -11
- package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +17 -41
- package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +2 -6
- package/src/shared/infrastructure/firestore/collectionUtils.ts +67 -0
- package/src/shared/infrastructure/firestore/index.ts +6 -0
- package/src/shared/infrastructure/firestore/resultUtils.ts +68 -0
- package/src/shared/infrastructure/index.ts +6 -0
- package/src/shared/presentation/hooks/index.ts +6 -0
- package/src/shared/presentation/hooks/useAsyncState.ts +72 -0
- package/src/shared/presentation/hooks/useServiceCall.ts +66 -0
- package/src/shared/types/CommonTypes.ts +6 -1
- package/src/shared/types/ReactTypes.ts +80 -0
- package/src/shared/utils/arrayUtils.core.ts +81 -0
- package/src/shared/utils/arrayUtils.query.ts +118 -0
- package/src/shared/utils/arrayUtils.transforms.ts +116 -0
- package/src/shared/utils/arrayUtils.ts +19 -0
- package/src/shared/utils/index.ts +14 -0
- package/src/shared/utils/numberUtils.aggregate.ts +35 -0
- package/src/shared/utils/numberUtils.core.ts +73 -0
- package/src/shared/utils/numberUtils.format.ts +42 -0
- package/src/shared/utils/numberUtils.math.ts +48 -0
- package/src/shared/utils/numberUtils.ts +9 -0
- package/src/shared/utils/stringUtils.case.ts +64 -0
- package/src/shared/utils/stringUtils.check.ts +65 -0
- package/src/shared/utils/stringUtils.format.ts +84 -0
- package/src/shared/utils/stringUtils.generate.ts +47 -0
- package/src/shared/utils/stringUtils.modify.ts +67 -0
- package/src/shared/utils/stringUtils.ts +10 -0
- package/src/shared/utils/validators.ts +187 -0
- package/src/utils/dateUtils.compare.ts +65 -0
- package/src/utils/dateUtils.core.ts +67 -0
- package/src/utils/dateUtils.format.ts +138 -0
- package/src/utils/dateUtils.math.ts +112 -0
- package/src/utils/dateUtils.ts +6 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.27.
|
|
3
|
+
"version": "2.27.117",
|
|
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",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"@types/react-native": "^0.72.8",
|
|
62
62
|
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
63
63
|
"@typescript-eslint/parser": "^8.50.1",
|
|
64
|
-
"@umituz/react-native-auth": "
|
|
65
|
-
"@umituz/react-native-design-system": "
|
|
66
|
-
"@umituz/react-native-firebase": "
|
|
64
|
+
"@umituz/react-native-auth": "latest",
|
|
65
|
+
"@umituz/react-native-design-system": "latest",
|
|
66
|
+
"@umituz/react-native-firebase": "latest",
|
|
67
67
|
"eslint": "^9.39.2",
|
|
68
68
|
"eslint-plugin-react": "^7.37.5",
|
|
69
69
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Optimized to use Design Patterns: Command, Observer, and Strategy.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { BaseRepository
|
|
6
|
+
import { getDoc, setDoc, type Firestore } from "firebase/firestore";
|
|
7
|
+
import { BaseRepository } from "@umituz/react-native-firebase";
|
|
8
8
|
import type { CreditsConfig, CreditsResult, DeductCreditsResult } from "../core/Credits";
|
|
9
9
|
import type { UserCreditsDocumentRead, PurchaseSource } from "../core/UserCreditsDocument";
|
|
10
10
|
import { initializeCreditsTransaction } from "../application/CreditsInitializer";
|
|
@@ -13,7 +13,7 @@ import type { RevenueCatData } from "../../subscription/core/RevenueCatData";
|
|
|
13
13
|
import { DeductCreditsCommand } from "../application/DeductCreditsCommand";
|
|
14
14
|
import { CreditLimitCalculator } from "../application/CreditLimitCalculator";
|
|
15
15
|
import { PURCHASE_TYPE, type PurchaseType } from "../../subscription/core/SubscriptionConstants";
|
|
16
|
-
import {
|
|
16
|
+
import { requireFirestore, buildDocRef, type CollectionConfig } from "../../../shared/infrastructure/firestore";
|
|
17
17
|
|
|
18
18
|
export class CreditsRepository extends BaseRepository {
|
|
19
19
|
private deductCommand: DeductCreditsCommand;
|
|
@@ -23,19 +23,22 @@ export class CreditsRepository extends BaseRepository {
|
|
|
23
23
|
this.deductCommand = new DeductCreditsCommand((db, uid) => this.getRef(db, uid));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
private getCollectionConfig(): CollectionConfig {
|
|
27
|
+
return {
|
|
28
|
+
collectionName: "credits",
|
|
29
|
+
useUserSubcollection: this.config.useUserSubcollection,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
private getRef(db: Firestore, userId: string) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
: doc(db, this.config.collectionName, userId);
|
|
34
|
+
const config = this.getCollectionConfig();
|
|
35
|
+
return buildDocRef(db, userId, "balance", config);
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
async getCredits(userId: string): Promise<CreditsResult> {
|
|
33
|
-
const db =
|
|
34
|
-
if (!db) {
|
|
35
|
-
throw new Error("Firestore instance is not available");
|
|
36
|
-
}
|
|
37
|
-
|
|
39
|
+
const db = requireFirestore();
|
|
38
40
|
const snap = await getDoc(this.getRef(db, userId));
|
|
41
|
+
|
|
39
42
|
if (!snap.exists()) {
|
|
40
43
|
return { success: true, data: null, error: null };
|
|
41
44
|
}
|
|
@@ -52,11 +55,7 @@ export class CreditsRepository extends BaseRepository {
|
|
|
52
55
|
revenueCatData: RevenueCatData,
|
|
53
56
|
type: PurchaseType = PURCHASE_TYPE.INITIAL
|
|
54
57
|
): Promise<CreditsResult> {
|
|
55
|
-
const db =
|
|
56
|
-
if (!db) {
|
|
57
|
-
throw new Error("Firestore instance is not available");
|
|
58
|
-
}
|
|
59
|
-
|
|
58
|
+
const db = requireFirestore();
|
|
60
59
|
const creditLimit = CreditLimitCalculator.calculate(productId, this.config);
|
|
61
60
|
const cfg = { ...this.config, creditLimit };
|
|
62
61
|
|
|
@@ -98,9 +97,7 @@ export class CreditsRepository extends BaseRepository {
|
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
async syncExpiredStatus(userId: string): Promise<void> {
|
|
101
|
-
const db =
|
|
102
|
-
if (!db) throw new Error("Firestore instance is not available");
|
|
103
|
-
|
|
100
|
+
const db = requireFirestore();
|
|
104
101
|
const ref = this.getRef(db, userId);
|
|
105
102
|
await setDoc(ref, {
|
|
106
103
|
isPremium: false,
|
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credit Calculation Utilities
|
|
3
3
|
* Centralized logic for credit mathematical operations
|
|
4
|
+
* Uses shared number utilities for consistency
|
|
4
5
|
*/
|
|
5
6
|
|
|
7
|
+
import { calculateCreditPercentage as calcPct, canAfford as canAffordCheck, calculateRemaining } from "../../../shared/utils/numberUtils";
|
|
8
|
+
|
|
6
9
|
export const calculateCreditPercentage = (
|
|
7
10
|
currentCredits: number | null | undefined,
|
|
8
11
|
creditLimit: number
|
|
9
12
|
): number => {
|
|
10
|
-
|
|
11
|
-
return 0;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const percent = Math.round((currentCredits / creditLimit) * 100);
|
|
15
|
-
return Math.min(Math.max(percent, 0), 100); // Clamp between 0-100
|
|
13
|
+
return calcPct(currentCredits, creditLimit);
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
export const canAffordCost = (
|
|
19
17
|
currentCredits: number | null | undefined,
|
|
20
18
|
cost: number
|
|
21
19
|
): boolean => {
|
|
22
|
-
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
return currentCredits >= cost;
|
|
20
|
+
return canAffordCheck(currentCredits, cost);
|
|
26
21
|
};
|
|
27
22
|
|
|
28
23
|
export const calculateRemainingCredits = (
|
|
29
24
|
currentCredits: number,
|
|
30
25
|
cost: number
|
|
31
26
|
): number => {
|
|
32
|
-
return
|
|
27
|
+
return calculateRemaining(currentCredits, cost);
|
|
33
28
|
};
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
collection,
|
|
10
9
|
getDocs,
|
|
11
10
|
addDoc,
|
|
12
11
|
query,
|
|
@@ -14,10 +13,9 @@ import {
|
|
|
14
13
|
orderBy,
|
|
15
14
|
limit as firestoreLimit,
|
|
16
15
|
serverTimestamp,
|
|
17
|
-
type Firestore,
|
|
18
16
|
type QueryConstraint,
|
|
19
17
|
} from "firebase/firestore";
|
|
20
|
-
import { BaseRepository
|
|
18
|
+
import { BaseRepository } from "@umituz/react-native-firebase";
|
|
21
19
|
import type {
|
|
22
20
|
CreditLog,
|
|
23
21
|
TransactionRepositoryConfig,
|
|
@@ -26,6 +24,7 @@ import type {
|
|
|
26
24
|
TransactionReason,
|
|
27
25
|
} from "../../domain/types/transaction.types";
|
|
28
26
|
import { TransactionMapper } from "../../domain/mappers/TransactionMapper";
|
|
27
|
+
import { requireFirestore, buildCollectionRef, type CollectionConfig, mapErrorToResult } from "../../../../shared/infrastructure/firestore";
|
|
29
28
|
|
|
30
29
|
export class TransactionRepository extends BaseRepository {
|
|
31
30
|
private config: TransactionRepositoryConfig;
|
|
@@ -35,25 +34,23 @@ export class TransactionRepository extends BaseRepository {
|
|
|
35
34
|
this.config = config;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
private getCollectionConfig(): CollectionConfig {
|
|
38
|
+
return {
|
|
39
|
+
collectionName: this.config.collectionName,
|
|
40
|
+
useUserSubcollection: this.config.useUserSubcollection ?? false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private getCollectionRef(db: any, userId: string) {
|
|
45
|
+
const config = this.getCollectionConfig();
|
|
46
|
+
return buildCollectionRef(db, userId, config);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
async getTransactions(
|
|
46
50
|
options: TransactionQueryOptions
|
|
47
51
|
): Promise<TransactionResult> {
|
|
48
|
-
const db = getFirestore();
|
|
49
|
-
if (!db) {
|
|
50
|
-
return {
|
|
51
|
-
success: false,
|
|
52
|
-
error: { message: "Database not available", code: "DB_NOT_AVAILABLE" },
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
52
|
try {
|
|
53
|
+
const db = requireFirestore();
|
|
57
54
|
const colRef = this.getCollectionRef(db, options.userId);
|
|
58
55
|
const constraints: QueryConstraint[] = [];
|
|
59
56
|
|
|
@@ -67,20 +64,13 @@ export class TransactionRepository extends BaseRepository {
|
|
|
67
64
|
const q = query(colRef, ...constraints);
|
|
68
65
|
const snapshot = await getDocs(q);
|
|
69
66
|
|
|
70
|
-
const transactions: CreditLog[] = snapshot.docs.map((docSnap) =>
|
|
67
|
+
const transactions: CreditLog[] = snapshot.docs.map((docSnap) =>
|
|
71
68
|
TransactionMapper.toEntity(docSnap, options.userId)
|
|
72
69
|
);
|
|
73
70
|
|
|
74
71
|
return { success: true, data: transactions };
|
|
75
72
|
} catch (error) {
|
|
76
|
-
return
|
|
77
|
-
success: false,
|
|
78
|
-
error: {
|
|
79
|
-
message:
|
|
80
|
-
error instanceof Error ? error.message : "Failed to get logs",
|
|
81
|
-
code: "FETCH_FAILED",
|
|
82
|
-
},
|
|
83
|
-
};
|
|
73
|
+
return mapErrorToResult(error);
|
|
84
74
|
}
|
|
85
75
|
}
|
|
86
76
|
|
|
@@ -90,15 +80,8 @@ export class TransactionRepository extends BaseRepository {
|
|
|
90
80
|
reason: TransactionReason,
|
|
91
81
|
metadata?: Partial<CreditLog>
|
|
92
82
|
): Promise<TransactionResult<CreditLog>> {
|
|
93
|
-
const db = getFirestore();
|
|
94
|
-
if (!db) {
|
|
95
|
-
return {
|
|
96
|
-
success: false,
|
|
97
|
-
error: { message: "Database not available", code: "DB_NOT_AVAILABLE" },
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
83
|
try {
|
|
84
|
+
const db = requireFirestore();
|
|
102
85
|
const colRef = this.getCollectionRef(db, userId);
|
|
103
86
|
const docData = {
|
|
104
87
|
...TransactionMapper.toFirestore(userId, change, reason, metadata),
|
|
@@ -119,14 +102,7 @@ export class TransactionRepository extends BaseRepository {
|
|
|
119
102
|
},
|
|
120
103
|
};
|
|
121
104
|
} catch (error) {
|
|
122
|
-
return
|
|
123
|
-
success: false,
|
|
124
|
-
error: {
|
|
125
|
-
message:
|
|
126
|
-
error instanceof Error ? error.message : "Failed to add log",
|
|
127
|
-
code: "ADD_FAILED",
|
|
128
|
-
},
|
|
129
|
-
};
|
|
105
|
+
return mapErrorToResult<CreditLog>(error);
|
|
130
106
|
}
|
|
131
107
|
}
|
|
132
108
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { collection, getDocs, orderBy, query } from "firebase/firestore";
|
|
9
|
-
import {
|
|
9
|
+
import { requireFirestore } from "../../../../shared/infrastructure";
|
|
10
10
|
import type {
|
|
11
11
|
ProductMetadata,
|
|
12
12
|
ProductMetadataConfig,
|
|
@@ -35,11 +35,7 @@ export class ProductMetadataService {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
private async fetchFromFirebase(): Promise<ProductMetadata[]> {
|
|
38
|
-
const db =
|
|
39
|
-
if (!db) {
|
|
40
|
-
throw new Error("Firestore not initialized");
|
|
41
|
-
}
|
|
42
|
-
|
|
38
|
+
const db = requireFirestore();
|
|
43
39
|
const colRef = collection(db, this.config.collectionName);
|
|
44
40
|
const q = query(colRef, orderBy("order", "asc"));
|
|
45
41
|
const snapshot = await getDocs(q);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore Collection Utilities
|
|
3
|
+
* Shared utilities for building Firestore collection and document references
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
collection,
|
|
8
|
+
doc,
|
|
9
|
+
type CollectionReference,
|
|
10
|
+
type DocumentReference,
|
|
11
|
+
type Firestore,
|
|
12
|
+
} from "firebase/firestore";
|
|
13
|
+
import { getFirestore } from "@umituz/react-native-firebase";
|
|
14
|
+
|
|
15
|
+
export interface CollectionConfig {
|
|
16
|
+
collectionName: string;
|
|
17
|
+
useUserSubcollection: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build a collection reference based on configuration
|
|
22
|
+
* Supports both root collections and user subcollections
|
|
23
|
+
*/
|
|
24
|
+
export function buildCollectionRef(
|
|
25
|
+
db: Firestore,
|
|
26
|
+
userId: string,
|
|
27
|
+
config: CollectionConfig
|
|
28
|
+
): CollectionReference {
|
|
29
|
+
if (config.useUserSubcollection) {
|
|
30
|
+
return collection(db, "users", userId, config.collectionName);
|
|
31
|
+
}
|
|
32
|
+
return collection(db, config.collectionName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a document reference based on configuration
|
|
37
|
+
* Supports both root collections and user subcollections
|
|
38
|
+
*/
|
|
39
|
+
export function buildDocRef(
|
|
40
|
+
db: Firestore,
|
|
41
|
+
userId: string,
|
|
42
|
+
docId: string,
|
|
43
|
+
config: CollectionConfig
|
|
44
|
+
): DocumentReference {
|
|
45
|
+
if (config.useUserSubcollection) {
|
|
46
|
+
return doc(db, "users", userId, config.collectionName, docId);
|
|
47
|
+
}
|
|
48
|
+
return doc(db, config.collectionName, docId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get Firestore instance or throw error
|
|
53
|
+
*/
|
|
54
|
+
export function requireFirestore(): Firestore {
|
|
55
|
+
const db = getFirestore();
|
|
56
|
+
if (!db) {
|
|
57
|
+
throw new Error("Firestore instance is not available");
|
|
58
|
+
}
|
|
59
|
+
return db;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Safe check for Firestore availability
|
|
64
|
+
*/
|
|
65
|
+
export function isFirestoreAvailable(): boolean {
|
|
66
|
+
return getFirestore() !== null;
|
|
67
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result Utilities
|
|
3
|
+
* Shared helpers for working with Result pattern
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Result } from "../../utils/Result";
|
|
7
|
+
import { failure, success } from "../../utils/Result";
|
|
8
|
+
|
|
9
|
+
export interface ApiError {
|
|
10
|
+
message: string;
|
|
11
|
+
code: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a standard error result
|
|
16
|
+
*/
|
|
17
|
+
export function createErrorResult(
|
|
18
|
+
message: string,
|
|
19
|
+
code: string = "UNKNOWN_ERROR"
|
|
20
|
+
): Result<never, ApiError> {
|
|
21
|
+
return failure({ message, code });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a database unavailable error result
|
|
26
|
+
*/
|
|
27
|
+
export function createDbUnavailableResult<T>(): Result<T, ApiError> {
|
|
28
|
+
return createErrorResult("Database not available", "DB_NOT_AVAILABLE");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Map Error to ApiError result
|
|
33
|
+
*/
|
|
34
|
+
export function mapErrorToResult<T>(error: unknown): Result<T, ApiError> {
|
|
35
|
+
const message = error instanceof Error ? error.message : "An unknown error occurred";
|
|
36
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : "UNKNOWN_ERROR";
|
|
37
|
+
return createErrorResult(message, code);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Execute async function and return Result
|
|
42
|
+
*/
|
|
43
|
+
export async function executeAsResult<T>(
|
|
44
|
+
fn: () => Promise<T>
|
|
45
|
+
): Promise<Result<T, ApiError>> {
|
|
46
|
+
try {
|
|
47
|
+
const data = await fn();
|
|
48
|
+
return success(data);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return mapErrorToResult<T>(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate database availability before executing
|
|
56
|
+
*/
|
|
57
|
+
export async function withDbCheck<T>(
|
|
58
|
+
fn: (db: any) => Promise<T>
|
|
59
|
+
): Promise<Result<T, ApiError>> {
|
|
60
|
+
const { requireFirestore } = require("./collectionUtils");
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const db = requireFirestore();
|
|
64
|
+
return await executeAsResult(() => fn(db));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return mapErrorToResult<T>(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAsyncState Hook
|
|
3
|
+
* Shared hook for managing async operation states
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
|
|
8
|
+
export type AsyncStatus = "idle" | "loading" | "success" | "error";
|
|
9
|
+
|
|
10
|
+
export interface AsyncState<T> {
|
|
11
|
+
data: T | null;
|
|
12
|
+
status: AsyncStatus;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseAsyncStateOptions<T> {
|
|
17
|
+
initialData?: T | null;
|
|
18
|
+
onSuccess?: (data: T) => void;
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseAsyncStateReturn<T> {
|
|
23
|
+
data: T | null;
|
|
24
|
+
status: AsyncStatus;
|
|
25
|
+
error: Error | null;
|
|
26
|
+
isLoading: boolean;
|
|
27
|
+
isSuccess: boolean;
|
|
28
|
+
isError: boolean;
|
|
29
|
+
isIdle: boolean;
|
|
30
|
+
setData: (data: T | null) => void;
|
|
31
|
+
setError: (error: Error | null) => void;
|
|
32
|
+
reset: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useAsyncState<T>(
|
|
36
|
+
options: UseAsyncStateOptions<T> = {}
|
|
37
|
+
): UseAsyncStateReturn<T> {
|
|
38
|
+
const { initialData = null, onSuccess, onError } = options;
|
|
39
|
+
|
|
40
|
+
const [state, setState] = useState<AsyncState<T>>({
|
|
41
|
+
data: initialData,
|
|
42
|
+
status: initialData ? "success" : "idle",
|
|
43
|
+
error: null,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const setData = useCallback((data: T | null) => {
|
|
47
|
+
setState({ data, status: data ? "success" : "idle", error: null });
|
|
48
|
+
if (data) onSuccess?.(data);
|
|
49
|
+
}, [onSuccess]);
|
|
50
|
+
|
|
51
|
+
const setError = useCallback((error: Error | null) => {
|
|
52
|
+
setState((prev) => ({ ...prev, status: "error", error }));
|
|
53
|
+
if (error) onError?.(error);
|
|
54
|
+
}, [onError]);
|
|
55
|
+
|
|
56
|
+
const reset = useCallback(() => {
|
|
57
|
+
setState({ data: initialData, status: initialData ? "success" : "idle", error: null });
|
|
58
|
+
}, [initialData]);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
data: state.data,
|
|
62
|
+
status: state.status,
|
|
63
|
+
error: state.error,
|
|
64
|
+
isLoading: state.status === "loading",
|
|
65
|
+
isSuccess: state.status === "success",
|
|
66
|
+
isError: state.status === "error",
|
|
67
|
+
isIdle: state.status === "idle",
|
|
68
|
+
setData,
|
|
69
|
+
setError,
|
|
70
|
+
reset,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useServiceCall Hook
|
|
3
|
+
* Shared hook for handling service calls with loading, error, and success states
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
|
|
8
|
+
export interface ServiceCallState<T> {
|
|
9
|
+
data: T | null;
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface UseServiceCallOptions<T> {
|
|
15
|
+
onSuccess?: (data: T) => void;
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
onComplete?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ServiceCallResult<T> {
|
|
21
|
+
data: T | null;
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
execute: () => Promise<void>;
|
|
25
|
+
reset: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useServiceCall<T>(
|
|
29
|
+
serviceFn: () => Promise<T>,
|
|
30
|
+
options: UseServiceCallOptions<T> = {}
|
|
31
|
+
): ServiceCallResult<T> {
|
|
32
|
+
const { onSuccess, onError, onComplete } = options;
|
|
33
|
+
const [state, setState] = useState<ServiceCallState<T>>({
|
|
34
|
+
data: null,
|
|
35
|
+
isLoading: false,
|
|
36
|
+
error: null,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const execute = useCallback(async () => {
|
|
40
|
+
setState({ data: null, isLoading: true, error: null });
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const data = await serviceFn();
|
|
44
|
+
setState({ data, isLoading: false, error: null });
|
|
45
|
+
onSuccess?.(data);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const errorObj = error instanceof Error ? error : new Error("Service call failed");
|
|
48
|
+
setState({ data: null, isLoading: false, error: errorObj });
|
|
49
|
+
onError?.(errorObj);
|
|
50
|
+
} finally {
|
|
51
|
+
onComplete?.();
|
|
52
|
+
}
|
|
53
|
+
}, [serviceFn, onSuccess, onError, onComplete]);
|
|
54
|
+
|
|
55
|
+
const reset = useCallback(() => {
|
|
56
|
+
setState({ data: null, isLoading: false, error: null });
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
data: state.data,
|
|
61
|
+
isLoading: state.isLoading,
|
|
62
|
+
error: state.error,
|
|
63
|
+
execute,
|
|
64
|
+
reset,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Shared types used across multiple domains
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { Platform as SubscriptionPlatform } from "../../domains/subscription/core/SubscriptionConstants";
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Purchase result from any purchase operation
|
|
8
10
|
*/
|
|
@@ -57,9 +59,12 @@ export interface TransactionMetadata {
|
|
|
57
59
|
/**
|
|
58
60
|
* Platform information
|
|
59
61
|
*/
|
|
60
|
-
export type Platform =
|
|
62
|
+
export type Platform = SubscriptionPlatform;
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* Purchase source tracking
|
|
64
66
|
*/
|
|
65
67
|
export type PurchaseSource = 'settings' | 'paywall' | 'upgrade_prompt' | 'auto-execution' | 'manual';
|
|
68
|
+
|
|
69
|
+
// Re-export from SubscriptionConstants to maintain compatibility
|
|
70
|
+
export { PLATFORM, PURCHASE_SOURCE, type Platform as SubscriptionPlatformType, type PurchaseSource as PurchaseSourceType } from "../../domains/subscription/core/SubscriptionConstants";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared React Types
|
|
3
|
+
* Common type definitions for React components and hooks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type VoidCallback = () => void;
|
|
7
|
+
export type AsyncCallback = () => Promise<void> | Promise<void>;
|
|
8
|
+
|
|
9
|
+
export type EventHandler<T = void> = (event: T) => void;
|
|
10
|
+
export type AsyncEventHandler<T = void> = (event: T) => Promise<void>;
|
|
11
|
+
|
|
12
|
+
export interface LoadingState {
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
isRefreshing: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ErrorState {
|
|
18
|
+
error: Error | null;
|
|
19
|
+
hasError: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PaginationState {
|
|
23
|
+
page: number;
|
|
24
|
+
perPage: number;
|
|
25
|
+
total: number;
|
|
26
|
+
hasMore: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ScreenProps<T = unknown> {
|
|
30
|
+
route: {
|
|
31
|
+
params?: T;
|
|
32
|
+
};
|
|
33
|
+
navigation: any;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ButtonProps = {
|
|
37
|
+
onPress: EventHandler | AsyncEventHandler;
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
loading?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface SelectableItem {
|
|
43
|
+
id: string;
|
|
44
|
+
selected: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface FormFieldProps<T> {
|
|
48
|
+
value: T;
|
|
49
|
+
onChange: (value: T) => void;
|
|
50
|
+
error?: string;
|
|
51
|
+
label?: string;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ListItemProps<T> {
|
|
57
|
+
item: T;
|
|
58
|
+
index: number;
|
|
59
|
+
onPress?: (item: T, index: number) => void;
|
|
60
|
+
onLongPress?: (item: T, index: number) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SearchState {
|
|
64
|
+
query: string;
|
|
65
|
+
results: unknown[];
|
|
66
|
+
isSearching: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TabItem {
|
|
70
|
+
id: string;
|
|
71
|
+
title: string;
|
|
72
|
+
icon?: string;
|
|
73
|
+
badge?: number | string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ModalProps {
|
|
77
|
+
visible: boolean;
|
|
78
|
+
onClose: () => void;
|
|
79
|
+
title?: string;
|
|
80
|
+
}
|