@umituz/react-native-subscription 2.14.10 → 2.14.12
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 +1 -1
- package/src/domain/errors/InsufficientCreditsError.ts +32 -0
- package/src/domains/wallet/domain/entities/CreditCost.ts +45 -0
- package/src/domains/wallet/domain/errors/WalletError.ts +169 -0
- package/src/domains/wallet/domain/types/credit-cost.types.ts +86 -0
- package/src/domains/wallet/domain/types/index.ts +33 -0
- package/src/domains/wallet/domain/types/transaction.types.ts +49 -0
- package/src/domains/wallet/domain/types/wallet.types.ts +50 -0
- package/src/domains/wallet/index.ts +116 -0
- package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +166 -0
- package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +131 -0
- package/src/domains/wallet/presentation/components/BalanceCard.tsx +119 -0
- package/src/domains/wallet/presentation/components/TransactionItem.tsx +150 -0
- package/src/domains/wallet/presentation/components/TransactionList.tsx +127 -0
- package/src/domains/wallet/presentation/components/index.ts +23 -0
- package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +88 -0
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +87 -0
- package/src/domains/wallet/presentation/hooks/useWallet.ts +93 -0
- package/src/domains/wallet/presentation/screens/WalletScreen.tsx +147 -0
- package/src/index.ts +18 -1
- package/src/presentation/hooks/useCredits.ts +10 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useProductMetadata Hook
|
|
3
|
+
*
|
|
4
|
+
* TanStack Query hook for fetching product metadata.
|
|
5
|
+
* Generic and reusable - uses config from ProductMetadataService.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useQuery } from "@tanstack/react-query";
|
|
9
|
+
import type {
|
|
10
|
+
ProductMetadata,
|
|
11
|
+
ProductMetadataConfig,
|
|
12
|
+
ProductType,
|
|
13
|
+
} from "../../domain/types/wallet.types";
|
|
14
|
+
import { ProductMetadataService } from "../../infrastructure/services/ProductMetadataService";
|
|
15
|
+
|
|
16
|
+
const CACHE_CONFIG = {
|
|
17
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
18
|
+
gcTime: 30 * 60 * 1000, // 30 minutes
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const productMetadataQueryKeys = {
|
|
22
|
+
all: ["productMetadata"] as const,
|
|
23
|
+
byType: (type: ProductType) => ["productMetadata", type] as const,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface UseProductMetadataParams {
|
|
27
|
+
config: ProductMetadataConfig;
|
|
28
|
+
type?: ProductType;
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseProductMetadataResult {
|
|
33
|
+
products: ProductMetadata[];
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
error: Error | null;
|
|
36
|
+
refetch: () => void;
|
|
37
|
+
creditsPackages: ProductMetadata[];
|
|
38
|
+
subscriptionPackages: ProductMetadata[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useProductMetadata({
|
|
42
|
+
config,
|
|
43
|
+
type,
|
|
44
|
+
enabled = true,
|
|
45
|
+
}: UseProductMetadataParams): UseProductMetadataResult {
|
|
46
|
+
const service = new ProductMetadataService(config);
|
|
47
|
+
|
|
48
|
+
const queryKey = type
|
|
49
|
+
? productMetadataQueryKeys.byType(type)
|
|
50
|
+
: productMetadataQueryKeys.all;
|
|
51
|
+
|
|
52
|
+
const { data, isLoading, error, refetch } = useQuery({
|
|
53
|
+
queryKey,
|
|
54
|
+
queryFn: async () => {
|
|
55
|
+
if (type) {
|
|
56
|
+
return service.getByType(type);
|
|
57
|
+
}
|
|
58
|
+
return service.getAll();
|
|
59
|
+
},
|
|
60
|
+
enabled,
|
|
61
|
+
staleTime: CACHE_CONFIG.staleTime,
|
|
62
|
+
gcTime: CACHE_CONFIG.gcTime,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const products = data ?? [];
|
|
66
|
+
|
|
67
|
+
const creditsPackages = products.filter((p) => p.type === "credits");
|
|
68
|
+
const subscriptionPackages = products.filter((p) => p.type === "subscription");
|
|
69
|
+
|
|
70
|
+
if (__DEV__) {
|
|
71
|
+
console.log("[useProductMetadata] State", {
|
|
72
|
+
enabled,
|
|
73
|
+
isLoading,
|
|
74
|
+
count: products.length,
|
|
75
|
+
credits: creditsPackages.length,
|
|
76
|
+
subscriptions: subscriptionPackages.length,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
products,
|
|
82
|
+
isLoading,
|
|
83
|
+
error: error as Error | null,
|
|
84
|
+
refetch,
|
|
85
|
+
creditsPackages,
|
|
86
|
+
subscriptionPackages,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useTransactionHistory Hook
|
|
3
|
+
*
|
|
4
|
+
* TanStack Query hook for fetching credit transaction history.
|
|
5
|
+
* Generic and reusable - uses config from TransactionRepository.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useQuery } from "@tanstack/react-query";
|
|
9
|
+
import type {
|
|
10
|
+
CreditLog,
|
|
11
|
+
TransactionRepositoryConfig,
|
|
12
|
+
} from "../../domain/types/transaction.types";
|
|
13
|
+
import { TransactionRepository } from "../../infrastructure/repositories/TransactionRepository";
|
|
14
|
+
|
|
15
|
+
const CACHE_CONFIG = {
|
|
16
|
+
staleTime: 60 * 1000, // 1 minute
|
|
17
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const transactionQueryKeys = {
|
|
21
|
+
all: ["transactions"] as const,
|
|
22
|
+
user: (userId: string) => ["transactions", userId] as const,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface UseTransactionHistoryParams {
|
|
26
|
+
userId: string | undefined;
|
|
27
|
+
config: TransactionRepositoryConfig;
|
|
28
|
+
limit?: number;
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseTransactionHistoryResult {
|
|
33
|
+
transactions: CreditLog[];
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
error: Error | null;
|
|
36
|
+
refetch: () => void;
|
|
37
|
+
isEmpty: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useTransactionHistory({
|
|
41
|
+
userId,
|
|
42
|
+
config,
|
|
43
|
+
limit = 50,
|
|
44
|
+
enabled = true,
|
|
45
|
+
}: UseTransactionHistoryParams): UseTransactionHistoryResult {
|
|
46
|
+
const repository = new TransactionRepository(config);
|
|
47
|
+
|
|
48
|
+
const { data, isLoading, error, refetch } = useQuery({
|
|
49
|
+
queryKey: [...transactionQueryKeys.user(userId ?? ""), limit],
|
|
50
|
+
queryFn: async () => {
|
|
51
|
+
if (!userId) return [];
|
|
52
|
+
|
|
53
|
+
const result = await repository.getTransactions({
|
|
54
|
+
userId,
|
|
55
|
+
limit,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!result.success) {
|
|
59
|
+
throw new Error(result.error?.message || "Failed to fetch history");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result.data ?? [];
|
|
63
|
+
},
|
|
64
|
+
enabled: enabled && !!userId,
|
|
65
|
+
staleTime: CACHE_CONFIG.staleTime,
|
|
66
|
+
gcTime: CACHE_CONFIG.gcTime,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const transactions = data ?? [];
|
|
70
|
+
|
|
71
|
+
if (__DEV__) {
|
|
72
|
+
console.log("[useTransactionHistory] State", {
|
|
73
|
+
userId,
|
|
74
|
+
enabled,
|
|
75
|
+
isLoading,
|
|
76
|
+
count: transactions.length,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
transactions,
|
|
82
|
+
isLoading,
|
|
83
|
+
error: error as Error | null,
|
|
84
|
+
refetch,
|
|
85
|
+
isEmpty: transactions.length === 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useWallet Hook
|
|
3
|
+
*
|
|
4
|
+
* Orchestration hook for wallet functionality.
|
|
5
|
+
* Combines balance, transactions, and purchase state.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useMemo } from "react";
|
|
9
|
+
import {
|
|
10
|
+
useCredits,
|
|
11
|
+
type UseCreditsParams,
|
|
12
|
+
} from "../../../../presentation/hooks/useCredits";
|
|
13
|
+
import {
|
|
14
|
+
useTransactionHistory,
|
|
15
|
+
type UseTransactionHistoryParams,
|
|
16
|
+
} from "./useTransactionHistory";
|
|
17
|
+
import type { CreditLog } from "../../domain/types/transaction.types";
|
|
18
|
+
|
|
19
|
+
export interface UseWalletParams {
|
|
20
|
+
userId: string | undefined;
|
|
21
|
+
transactionConfig: UseTransactionHistoryParams["config"];
|
|
22
|
+
transactionLimit?: number;
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseWalletResult {
|
|
27
|
+
balance: number;
|
|
28
|
+
textCredits: number;
|
|
29
|
+
imageCredits: number;
|
|
30
|
+
balanceLoading: boolean;
|
|
31
|
+
transactions: CreditLog[];
|
|
32
|
+
transactionsLoading: boolean;
|
|
33
|
+
hasCredits: boolean;
|
|
34
|
+
refetchBalance: () => void;
|
|
35
|
+
refetchTransactions: () => void;
|
|
36
|
+
refetchAll: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useWallet({
|
|
40
|
+
userId,
|
|
41
|
+
transactionConfig,
|
|
42
|
+
transactionLimit = 50,
|
|
43
|
+
enabled = true,
|
|
44
|
+
}: UseWalletParams): UseWalletResult {
|
|
45
|
+
const creditsParams: UseCreditsParams = {
|
|
46
|
+
userId,
|
|
47
|
+
enabled,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const transactionParams: UseTransactionHistoryParams = {
|
|
51
|
+
userId,
|
|
52
|
+
config: transactionConfig,
|
|
53
|
+
limit: transactionLimit,
|
|
54
|
+
enabled,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
credits,
|
|
59
|
+
isLoading: balanceLoading,
|
|
60
|
+
refetch: refetchBalance,
|
|
61
|
+
hasTextCredits,
|
|
62
|
+
hasImageCredits,
|
|
63
|
+
} = useCredits(creditsParams);
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
transactions,
|
|
67
|
+
isLoading: transactionsLoading,
|
|
68
|
+
refetch: refetchTransactions,
|
|
69
|
+
} = useTransactionHistory(transactionParams);
|
|
70
|
+
|
|
71
|
+
const balance = useMemo(() => {
|
|
72
|
+
if (!credits) return 0;
|
|
73
|
+
return credits.textCredits + credits.imageCredits;
|
|
74
|
+
}, [credits]);
|
|
75
|
+
|
|
76
|
+
const refetchAll = useCallback(() => {
|
|
77
|
+
refetchBalance();
|
|
78
|
+
refetchTransactions();
|
|
79
|
+
}, [refetchBalance, refetchTransactions]);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
balance,
|
|
83
|
+
textCredits: credits?.textCredits ?? 0,
|
|
84
|
+
imageCredits: credits?.imageCredits ?? 0,
|
|
85
|
+
balanceLoading,
|
|
86
|
+
transactions,
|
|
87
|
+
transactionsLoading,
|
|
88
|
+
hasCredits: hasTextCredits || hasImageCredits,
|
|
89
|
+
refetchBalance,
|
|
90
|
+
refetchTransactions,
|
|
91
|
+
refetchAll,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Screen
|
|
3
|
+
*
|
|
4
|
+
* Generic wallet screen composition.
|
|
5
|
+
* Props-driven for full customization across apps.
|
|
6
|
+
* No business logic - pure presentation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { View, StyleSheet, ActivityIndicator, TouchableOpacity } from "react-native";
|
|
11
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
|
+
import {
|
|
13
|
+
useAppDesignTokens,
|
|
14
|
+
AtomicText,
|
|
15
|
+
AtomicIcon,
|
|
16
|
+
ScreenLayout,
|
|
17
|
+
} from "@umituz/react-native-design-system";
|
|
18
|
+
import {
|
|
19
|
+
BalanceCard,
|
|
20
|
+
type BalanceCardTranslations,
|
|
21
|
+
} from "../components/BalanceCard";
|
|
22
|
+
import {
|
|
23
|
+
TransactionList,
|
|
24
|
+
type TransactionListTranslations,
|
|
25
|
+
} from "../components/TransactionList";
|
|
26
|
+
import type { CreditLog } from "../../domain/types/transaction.types";
|
|
27
|
+
|
|
28
|
+
export interface WalletScreenTranslations
|
|
29
|
+
extends BalanceCardTranslations,
|
|
30
|
+
TransactionListTranslations {
|
|
31
|
+
screenTitle: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WalletScreenConfig {
|
|
35
|
+
balance: number;
|
|
36
|
+
balanceLoading: boolean;
|
|
37
|
+
transactions: CreditLog[];
|
|
38
|
+
transactionsLoading: boolean;
|
|
39
|
+
translations: WalletScreenTranslations;
|
|
40
|
+
onBack?: () => void;
|
|
41
|
+
dateFormatter?: (timestamp: number) => string;
|
|
42
|
+
maxTransactionHeight?: number;
|
|
43
|
+
balanceIconName?: string;
|
|
44
|
+
footer?: React.ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface WalletScreenProps {
|
|
48
|
+
config: WalletScreenConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const WalletScreen: React.FC<WalletScreenProps> = ({ config }) => {
|
|
52
|
+
const tokens = useAppDesignTokens();
|
|
53
|
+
const insets = useSafeAreaInsets();
|
|
54
|
+
|
|
55
|
+
const renderHeader = () => (
|
|
56
|
+
<View style={[styles.header, { paddingTop: insets.top + 12 }]}>
|
|
57
|
+
{config.onBack && (
|
|
58
|
+
<TouchableOpacity
|
|
59
|
+
onPress={config.onBack}
|
|
60
|
+
style={styles.backButton}
|
|
61
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
62
|
+
>
|
|
63
|
+
<AtomicIcon
|
|
64
|
+
name="ArrowLeft"
|
|
65
|
+
size="lg"
|
|
66
|
+
customColor={tokens.colors.textPrimary}
|
|
67
|
+
/>
|
|
68
|
+
</TouchableOpacity>
|
|
69
|
+
)}
|
|
70
|
+
<AtomicText
|
|
71
|
+
type="titleLarge"
|
|
72
|
+
style={{ color: tokens.colors.textPrimary, fontWeight: "700" }}
|
|
73
|
+
>
|
|
74
|
+
{config.translations.screenTitle}
|
|
75
|
+
</AtomicText>
|
|
76
|
+
</View>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const renderBalance = () => {
|
|
80
|
+
if (config.balanceLoading) {
|
|
81
|
+
return (
|
|
82
|
+
<View style={styles.loadingContainer}>
|
|
83
|
+
<ActivityIndicator size="large" color={tokens.colors.primary} />
|
|
84
|
+
<AtomicText
|
|
85
|
+
type="bodyMedium"
|
|
86
|
+
style={[styles.loadingText, { color: tokens.colors.textSecondary }]}
|
|
87
|
+
>
|
|
88
|
+
{config.translations.loading}
|
|
89
|
+
</AtomicText>
|
|
90
|
+
</View>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<BalanceCard
|
|
96
|
+
balance={config.balance}
|
|
97
|
+
translations={config.translations}
|
|
98
|
+
iconName={config.balanceIconName}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<ScreenLayout
|
|
105
|
+
scrollable={true}
|
|
106
|
+
edges={["bottom"]}
|
|
107
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
108
|
+
contentContainerStyle={styles.content}
|
|
109
|
+
footer={config.footer}
|
|
110
|
+
>
|
|
111
|
+
{renderHeader()}
|
|
112
|
+
{renderBalance()}
|
|
113
|
+
<TransactionList
|
|
114
|
+
transactions={config.transactions}
|
|
115
|
+
loading={config.transactionsLoading}
|
|
116
|
+
translations={config.translations}
|
|
117
|
+
maxHeight={config.maxTransactionHeight}
|
|
118
|
+
dateFormatter={config.dateFormatter}
|
|
119
|
+
/>
|
|
120
|
+
</ScreenLayout>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const styles = StyleSheet.create({
|
|
125
|
+
content: {
|
|
126
|
+
paddingBottom: 24,
|
|
127
|
+
},
|
|
128
|
+
header: {
|
|
129
|
+
flexDirection: "row",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
paddingHorizontal: 16,
|
|
132
|
+
paddingBottom: 12,
|
|
133
|
+
},
|
|
134
|
+
backButton: {
|
|
135
|
+
marginRight: 16,
|
|
136
|
+
},
|
|
137
|
+
loadingContainer: {
|
|
138
|
+
padding: 40,
|
|
139
|
+
alignItems: "center",
|
|
140
|
+
justifyContent: "center",
|
|
141
|
+
gap: 12,
|
|
142
|
+
},
|
|
143
|
+
loadingText: {
|
|
144
|
+
fontSize: 14,
|
|
145
|
+
fontWeight: "500",
|
|
146
|
+
},
|
|
147
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
|
9
|
-
// DOMAIN
|
|
9
|
+
// WALLET DOMAIN (Complete)
|
|
10
10
|
// =============================================================================
|
|
11
11
|
|
|
12
|
+
export * from "./domains/wallet";
|
|
13
|
+
|
|
12
14
|
// =============================================================================
|
|
13
15
|
// DOMAIN LAYER - Subscription Status
|
|
14
16
|
// =============================================================================
|
|
@@ -231,6 +233,15 @@ export type {
|
|
|
231
233
|
|
|
232
234
|
export { DEFAULT_CREDITS_CONFIG } from "./domain/entities/Credits";
|
|
233
235
|
|
|
236
|
+
// =============================================================================
|
|
237
|
+
// CREDITS SYSTEM - Errors
|
|
238
|
+
// =============================================================================
|
|
239
|
+
|
|
240
|
+
export { InsufficientCreditsError } from "./domain/errors/InsufficientCreditsError";
|
|
241
|
+
|
|
242
|
+
// CreditCost, Transaction types, Wallet types, Credit-cost types
|
|
243
|
+
// are now exported from "./domains/wallet"
|
|
244
|
+
|
|
234
245
|
// =============================================================================
|
|
235
246
|
// CREDITS SYSTEM - Repository
|
|
236
247
|
// =============================================================================
|
|
@@ -240,6 +251,9 @@ export {
|
|
|
240
251
|
createCreditsRepository,
|
|
241
252
|
} from "./infrastructure/repositories/CreditsRepository";
|
|
242
253
|
|
|
254
|
+
// TransactionRepository and ProductMetadataService
|
|
255
|
+
// are now exported from "./domains/wallet"
|
|
256
|
+
|
|
243
257
|
// =============================================================================
|
|
244
258
|
// CREDITS SYSTEM - Configuration (Module-Level Provider)
|
|
245
259
|
// =============================================================================
|
|
@@ -316,6 +330,9 @@ export {
|
|
|
316
330
|
|
|
317
331
|
export { useDevTestCallbacks } from "./presentation/hooks/useDevTestCallbacks";
|
|
318
332
|
|
|
333
|
+
// Wallet hooks, components, and screens
|
|
334
|
+
// are now exported from "./domains/wallet"
|
|
335
|
+
|
|
319
336
|
// =============================================================================
|
|
320
337
|
// CREDITS SYSTEM - Utilities
|
|
321
338
|
// =============================================================================
|
|
@@ -38,6 +38,8 @@ export interface UseCreditsResult {
|
|
|
38
38
|
textCreditsPercent: number;
|
|
39
39
|
imageCreditsPercent: number;
|
|
40
40
|
refetch: () => void;
|
|
41
|
+
/** Check if user can afford a specific credit cost */
|
|
42
|
+
canAfford: (cost: number, type?: CreditType) => boolean;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export const useCredits = ({
|
|
@@ -86,6 +88,13 @@ export const useCredits = ({
|
|
|
86
88
|
? Math.round((credits.imageCredits / config.imageCreditLimit) * 100)
|
|
87
89
|
: 0;
|
|
88
90
|
|
|
91
|
+
const canAfford = (cost: number, type: CreditType = "text"): boolean => {
|
|
92
|
+
if (!credits) return false;
|
|
93
|
+
return type === "text"
|
|
94
|
+
? credits.textCredits >= cost
|
|
95
|
+
: credits.imageCredits >= cost;
|
|
96
|
+
};
|
|
97
|
+
|
|
89
98
|
return {
|
|
90
99
|
credits,
|
|
91
100
|
isLoading,
|
|
@@ -95,6 +104,7 @@ export const useCredits = ({
|
|
|
95
104
|
textCreditsPercent,
|
|
96
105
|
imageCreditsPercent,
|
|
97
106
|
refetch,
|
|
107
|
+
canAfford,
|
|
98
108
|
};
|
|
99
109
|
};
|
|
100
110
|
|