@umituz/react-native-subscription 2.27.115 → 2.27.116

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 (36) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/infrastructure/CreditsRepository.ts +16 -19
  3. package/src/domains/credits/utils/creditCalculations.ts +6 -11
  4. package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +17 -41
  5. package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +2 -6
  6. package/src/shared/infrastructure/firestore/collectionUtils.ts +67 -0
  7. package/src/shared/infrastructure/firestore/index.ts +6 -0
  8. package/src/shared/infrastructure/firestore/resultUtils.ts +68 -0
  9. package/src/shared/infrastructure/index.ts +6 -0
  10. package/src/shared/presentation/hooks/index.ts +6 -0
  11. package/src/shared/presentation/hooks/useAsyncState.ts +72 -0
  12. package/src/shared/presentation/hooks/useServiceCall.ts +66 -0
  13. package/src/shared/types/CommonTypes.ts +6 -1
  14. package/src/shared/types/ReactTypes.ts +80 -0
  15. package/src/shared/utils/arrayUtils.core.ts +81 -0
  16. package/src/shared/utils/arrayUtils.query.ts +118 -0
  17. package/src/shared/utils/arrayUtils.transforms.ts +116 -0
  18. package/src/shared/utils/arrayUtils.ts +19 -0
  19. package/src/shared/utils/index.ts +14 -0
  20. package/src/shared/utils/numberUtils.aggregate.ts +35 -0
  21. package/src/shared/utils/numberUtils.core.ts +73 -0
  22. package/src/shared/utils/numberUtils.format.ts +42 -0
  23. package/src/shared/utils/numberUtils.math.ts +48 -0
  24. package/src/shared/utils/numberUtils.ts +9 -0
  25. package/src/shared/utils/stringUtils.case.ts +64 -0
  26. package/src/shared/utils/stringUtils.check.ts +65 -0
  27. package/src/shared/utils/stringUtils.format.ts +84 -0
  28. package/src/shared/utils/stringUtils.generate.ts +47 -0
  29. package/src/shared/utils/stringUtils.modify.ts +67 -0
  30. package/src/shared/utils/stringUtils.ts +10 -0
  31. package/src/shared/utils/validators.ts +187 -0
  32. package/src/utils/dateUtils.compare.ts +65 -0
  33. package/src/utils/dateUtils.core.ts +67 -0
  34. package/src/utils/dateUtils.format.ts +138 -0
  35. package/src/utils/dateUtils.math.ts +112 -0
  36. 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.115",
3
+ "version": "2.27.116",
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",
@@ -3,8 +3,8 @@
3
3
  * Optimized to use Design Patterns: Command, Observer, and Strategy.
4
4
  */
5
5
 
6
- import { doc, getDoc, type Firestore } from "firebase/firestore";
7
- import { BaseRepository, getFirestore } from "@umituz/react-native-firebase";
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 { setDoc } from "firebase/firestore";
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
- return this.config.useUserSubcollection
28
- ? doc(db, "users", userId, "credits", "balance")
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 = getFirestore();
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 = getFirestore();
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 = getFirestore();
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
- if (currentCredits === null || currentCredits === undefined || creditLimit <= 0) {
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
- if (currentCredits === null || currentCredits === undefined) {
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 Math.max(0, currentCredits - cost);
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, getFirestore } from "@umituz/react-native-firebase";
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 getCollectionRef(db: Firestore, userId: string) {
39
- if (this.config.useUserSubcollection) {
40
- return collection(db, "users", userId, this.config.collectionName);
41
- }
42
- return collection(db, this.config.collectionName);
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 { getFirestore } from "@umituz/react-native-firebase";
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 = getFirestore();
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,6 @@
1
+ /**
2
+ * Firestore Infrastructure Utilities
3
+ */
4
+
5
+ export * from "./collectionUtils";
6
+ export * from "./resultUtils";
@@ -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,6 @@
1
+ /**
2
+ * Shared Infrastructure
3
+ */
4
+
5
+ export * from "./SubscriptionEventBus";
6
+ export * from "./firestore";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared Presentation Hooks
3
+ */
4
+
5
+ export * from "./useAsyncState";
6
+ export * from "./useServiceCall";
@@ -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 = 'ios' | 'android';
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
+ }