@umituz/react-native-ai-generation-content 1.57.4 → 1.58.0
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/domains/access-control/hooks/useAIFeatureGate.ts +17 -5
- package/src/domains/access-control/types/access-control.types.ts +10 -0
- package/src/domains/generation/wizard/presentation/components/WizardFlow.types.ts +0 -14
- package/src/features/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +10 -46
- package/src/features/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +10 -16
- package/src/features/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +10 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.0",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { useCallback, useMemo } from "react";
|
|
22
|
+
import { useOffline } from "@umituz/react-native-design-system";
|
|
22
23
|
import { useAuth, useAuthModalStore } from "@umituz/react-native-auth";
|
|
23
24
|
import {
|
|
24
25
|
usePremium,
|
|
@@ -41,7 +42,10 @@ import type {
|
|
|
41
42
|
export function useAIFeatureGate(
|
|
42
43
|
options: AIFeatureGateOptions,
|
|
43
44
|
): AIFeatureGateReturn {
|
|
44
|
-
const { creditCost, onSuccess, onError } = options;
|
|
45
|
+
const { creditCost, onNetworkError, onSuccess, onError } = options;
|
|
46
|
+
|
|
47
|
+
// Network state
|
|
48
|
+
const { isOffline } = useOffline();
|
|
45
49
|
|
|
46
50
|
// Auth state
|
|
47
51
|
const { isAuthenticated: rawIsAuthenticated, isAnonymous } = useAuth();
|
|
@@ -69,16 +73,23 @@ export function useAIFeatureGate(
|
|
|
69
73
|
isCreditsLoaded,
|
|
70
74
|
});
|
|
71
75
|
|
|
72
|
-
// Can access if: authenticated AND (premium OR has credits)
|
|
76
|
+
// Can access if: online AND authenticated AND (premium OR has credits)
|
|
73
77
|
const canAccess = useMemo(() => {
|
|
78
|
+
if (isOffline) return false;
|
|
74
79
|
if (!isAuthenticated) return false;
|
|
75
80
|
if (isPremium) return true;
|
|
76
81
|
return hasCredits;
|
|
77
|
-
}, [isAuthenticated, isPremium, hasCredits]);
|
|
82
|
+
}, [isOffline, isAuthenticated, isPremium, hasCredits]);
|
|
78
83
|
|
|
79
|
-
// Wrapped requireFeature with optional callbacks
|
|
84
|
+
// Wrapped requireFeature with offline check and optional callbacks
|
|
80
85
|
const requireFeature = useCallback(
|
|
81
86
|
(action: () => void | Promise<void>): void => {
|
|
87
|
+
// Network check first - must be online
|
|
88
|
+
if (isOffline) {
|
|
89
|
+
onNetworkError?.();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Then auth/credit checks via subscription package
|
|
82
93
|
requireFeatureFromPackage(() => {
|
|
83
94
|
const result = action();
|
|
84
95
|
if (result instanceof Promise) {
|
|
@@ -94,7 +105,7 @@ export function useAIFeatureGate(
|
|
|
94
105
|
}
|
|
95
106
|
});
|
|
96
107
|
},
|
|
97
|
-
[requireFeatureFromPackage, onSuccess, onError],
|
|
108
|
+
[isOffline, onNetworkError, requireFeatureFromPackage, onSuccess, onError],
|
|
98
109
|
);
|
|
99
110
|
|
|
100
111
|
return {
|
|
@@ -105,5 +116,6 @@ export function useAIFeatureGate(
|
|
|
105
116
|
isAuthenticated,
|
|
106
117
|
isPremium,
|
|
107
118
|
creditBalance,
|
|
119
|
+
isOffline,
|
|
108
120
|
};
|
|
109
121
|
}
|
|
@@ -14,6 +14,11 @@ export interface AIFeatureGateOptions {
|
|
|
14
14
|
*/
|
|
15
15
|
featureName?: string;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Callback fired when network is unavailable
|
|
19
|
+
*/
|
|
20
|
+
onNetworkError?: () => void;
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Callback fired when feature is successfully accessed and executed
|
|
19
24
|
*/
|
|
@@ -61,6 +66,11 @@ export interface AIFeatureGateReturn {
|
|
|
61
66
|
* Current credit balance
|
|
62
67
|
*/
|
|
63
68
|
creditBalance: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether device is offline
|
|
72
|
+
*/
|
|
73
|
+
isOffline: boolean;
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
/**
|
|
@@ -9,22 +9,8 @@ export interface BaseWizardFlowProps {
|
|
|
9
9
|
readonly model: string;
|
|
10
10
|
/** User ID for saving creations */
|
|
11
11
|
readonly userId?: string;
|
|
12
|
-
/** Is user authenticated (registered, not anonymous) */
|
|
13
|
-
readonly isAuthenticated: boolean;
|
|
14
|
-
/** Does user have premium subscription */
|
|
15
|
-
readonly hasPremium: boolean;
|
|
16
|
-
/** User's credit balance */
|
|
17
|
-
readonly creditBalance: number;
|
|
18
|
-
/** Are credits loaded */
|
|
19
|
-
readonly isCreditsLoaded: boolean;
|
|
20
12
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
21
13
|
readonly creditCost: number;
|
|
22
|
-
/** Is device offline - prevents generation when true */
|
|
23
|
-
readonly isOffline?: boolean;
|
|
24
|
-
/** Show auth modal with callback */
|
|
25
|
-
readonly onShowAuthModal: (callback: () => void) => void;
|
|
26
|
-
/** Show paywall */
|
|
27
|
-
readonly onShowPaywall: () => void;
|
|
28
14
|
/** Called when network is unavailable and generation is blocked */
|
|
29
15
|
readonly onNetworkError?: () => void;
|
|
30
16
|
/** Called when generation completes */
|
|
@@ -8,6 +8,7 @@ import { View, StyleSheet } from "react-native";
|
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { GenericWizardFlow } from "../../../../domains/generation/wizard/presentation/components";
|
|
10
10
|
import { IMAGE_TO_VIDEO_WIZARD_CONFIG } from "../../../../domains/generation/wizard/configs";
|
|
11
|
+
import { useAIFeatureGate } from "../../../../domains/access-control";
|
|
11
12
|
import type { WizardScenarioData } from "../../../../domains/generation/wizard/presentation/hooks/useWizardGeneration";
|
|
12
13
|
import type { BaseWizardFlowProps } from "../../../../domains/generation/wizard/presentation/components/WizardFlow.types";
|
|
13
14
|
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
@@ -29,14 +30,7 @@ export const ImageToVideoWizardFlow: React.FC<ImageToVideoWizardFlowProps> = (pr
|
|
|
29
30
|
const {
|
|
30
31
|
model,
|
|
31
32
|
userId,
|
|
32
|
-
isAuthenticated,
|
|
33
|
-
hasPremium,
|
|
34
|
-
creditBalance,
|
|
35
|
-
isCreditsLoaded,
|
|
36
33
|
creditCost,
|
|
37
|
-
isOffline,
|
|
38
|
-
onShowAuthModal,
|
|
39
|
-
onShowPaywall,
|
|
40
34
|
onNetworkError,
|
|
41
35
|
onGenerationComplete,
|
|
42
36
|
onGenerationError,
|
|
@@ -47,6 +41,12 @@ export const ImageToVideoWizardFlow: React.FC<ImageToVideoWizardFlowProps> = (pr
|
|
|
47
41
|
|
|
48
42
|
const tokens = useAppDesignTokens();
|
|
49
43
|
|
|
44
|
+
// Centralized access control - handles offline, auth, credits, paywall
|
|
45
|
+
const { requireFeature } = useAIFeatureGate({
|
|
46
|
+
creditCost,
|
|
47
|
+
onNetworkError,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
50
|
const scenario: WizardScenarioData = useMemo(
|
|
51
51
|
() => ({
|
|
52
52
|
id: "image-to-video",
|
|
@@ -71,45 +71,10 @@ export const ImageToVideoWizardFlow: React.FC<ImageToVideoWizardFlowProps> = (pr
|
|
|
71
71
|
|
|
72
72
|
const handleGenerationStart = useCallback(
|
|
73
73
|
(_data: Record<string, unknown>, proceed: () => void) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
isOffline,
|
|
77
|
-
isAuthenticated,
|
|
78
|
-
hasPremium,
|
|
79
|
-
creditBalance,
|
|
80
|
-
creditCost,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
// Network check - must be online to generate
|
|
84
|
-
if (isOffline) {
|
|
85
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
86
|
-
console.log("[ImageToVideoWizardFlow] Blocked: offline");
|
|
87
|
-
}
|
|
88
|
-
onNetworkError?.();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
// Auth check - must be authenticated
|
|
92
|
-
if (!isAuthenticated) {
|
|
93
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
94
|
-
console.log("[ImageToVideoWizardFlow] Blocked: not authenticated, showing auth modal");
|
|
95
|
-
}
|
|
96
|
-
onShowAuthModal(proceed);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
// Credit check - must have enough credits
|
|
100
|
-
if (!hasPremium && isCreditsLoaded && creditBalance < creditCost) {
|
|
101
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
console.log("[ImageToVideoWizardFlow] Blocked: insufficient credits, showing paywall");
|
|
103
|
-
}
|
|
104
|
-
onShowPaywall();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
108
|
-
console.log("[ImageToVideoWizardFlow] All checks passed, proceeding");
|
|
109
|
-
}
|
|
110
|
-
proceed();
|
|
74
|
+
// Use centralized access control - checks offline, auth, credits
|
|
75
|
+
requireFeature(proceed);
|
|
111
76
|
},
|
|
112
|
-
[
|
|
77
|
+
[requireFeature],
|
|
113
78
|
);
|
|
114
79
|
|
|
115
80
|
const handleGenerationComplete = useCallback(() => {
|
|
@@ -129,7 +94,6 @@ export const ImageToVideoWizardFlow: React.FC<ImageToVideoWizardFlowProps> = (pr
|
|
|
129
94
|
onGenerationStart={handleGenerationStart}
|
|
130
95
|
onGenerationComplete={handleGenerationComplete}
|
|
131
96
|
onGenerationError={onGenerationError}
|
|
132
|
-
onCreditsExhausted={onShowPaywall}
|
|
133
97
|
onBack={onBack}
|
|
134
98
|
onTryAgain={onBack}
|
|
135
99
|
t={t}
|
|
@@ -8,6 +8,7 @@ import { View, StyleSheet } from "react-native";
|
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { GenericWizardFlow } from "../../../../domains/generation/wizard/presentation/components";
|
|
10
10
|
import { TEXT_TO_IMAGE_WIZARD_CONFIG } from "../../../../domains/generation/wizard/configs";
|
|
11
|
+
import { useAIFeatureGate } from "../../../../domains/access-control";
|
|
11
12
|
import type { WizardScenarioData } from "../../../../domains/generation/wizard/presentation/hooks/useWizardGeneration";
|
|
12
13
|
import type { BaseWizardFlowProps } from "../../../../domains/generation/wizard/presentation/components/WizardFlow.types";
|
|
13
14
|
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
@@ -29,14 +30,7 @@ export const TextToImageWizardFlow: React.FC<TextToImageWizardFlowProps> = (prop
|
|
|
29
30
|
const {
|
|
30
31
|
model,
|
|
31
32
|
userId,
|
|
32
|
-
isAuthenticated,
|
|
33
|
-
hasPremium,
|
|
34
|
-
creditBalance,
|
|
35
|
-
isCreditsLoaded,
|
|
36
33
|
creditCost,
|
|
37
|
-
isOffline,
|
|
38
|
-
onShowAuthModal,
|
|
39
|
-
onShowPaywall,
|
|
40
34
|
onNetworkError,
|
|
41
35
|
onGenerationComplete,
|
|
42
36
|
onGenerationError,
|
|
@@ -47,6 +41,12 @@ export const TextToImageWizardFlow: React.FC<TextToImageWizardFlowProps> = (prop
|
|
|
47
41
|
|
|
48
42
|
const tokens = useAppDesignTokens();
|
|
49
43
|
|
|
44
|
+
// Centralized access control - handles offline, auth, credits, paywall
|
|
45
|
+
const { requireFeature } = useAIFeatureGate({
|
|
46
|
+
creditCost,
|
|
47
|
+
onNetworkError,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
50
|
const scenario: WizardScenarioData = useMemo(
|
|
51
51
|
() => ({
|
|
52
52
|
id: "text-to-image",
|
|
@@ -71,15 +71,10 @@ export const TextToImageWizardFlow: React.FC<TextToImageWizardFlowProps> = (prop
|
|
|
71
71
|
|
|
72
72
|
const handleGenerationStart = useCallback(
|
|
73
73
|
(_data: Record<string, unknown>, proceed: () => void) => {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
// Auth check - must be authenticated
|
|
77
|
-
if (!isAuthenticated) { onShowAuthModal(proceed); return; }
|
|
78
|
-
// Credit check - must have enough credits
|
|
79
|
-
if (!hasPremium && isCreditsLoaded && creditBalance < creditCost) { onShowPaywall(); return; }
|
|
80
|
-
proceed();
|
|
74
|
+
// Use centralized access control - checks offline, auth, credits
|
|
75
|
+
requireFeature(proceed);
|
|
81
76
|
},
|
|
82
|
-
[
|
|
77
|
+
[requireFeature],
|
|
83
78
|
);
|
|
84
79
|
|
|
85
80
|
const handleGenerationComplete = useCallback(() => {
|
|
@@ -99,7 +94,6 @@ export const TextToImageWizardFlow: React.FC<TextToImageWizardFlowProps> = (prop
|
|
|
99
94
|
onGenerationStart={handleGenerationStart}
|
|
100
95
|
onGenerationComplete={handleGenerationComplete}
|
|
101
96
|
onGenerationError={onGenerationError}
|
|
102
|
-
onCreditsExhausted={onShowPaywall}
|
|
103
97
|
onBack={onBack}
|
|
104
98
|
onTryAgain={onBack}
|
|
105
99
|
t={t}
|
|
@@ -8,6 +8,7 @@ import { View, StyleSheet } from "react-native";
|
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { GenericWizardFlow } from "../../../../domains/generation/wizard/presentation/components";
|
|
10
10
|
import { TEXT_TO_VIDEO_WIZARD_CONFIG } from "../../../../domains/generation/wizard/configs";
|
|
11
|
+
import { useAIFeatureGate } from "../../../../domains/access-control";
|
|
11
12
|
import type { WizardScenarioData } from "../../../../domains/generation/wizard/presentation/hooks/useWizardGeneration";
|
|
12
13
|
import type { BaseWizardFlowProps } from "../../../../domains/generation/wizard/presentation/components/WizardFlow.types";
|
|
13
14
|
import type { AlertMessages } from "../../../../presentation/hooks/generation/types";
|
|
@@ -29,14 +30,7 @@ export const TextToVideoWizardFlow: React.FC<TextToVideoWizardFlowProps> = (prop
|
|
|
29
30
|
const {
|
|
30
31
|
model,
|
|
31
32
|
userId,
|
|
32
|
-
isAuthenticated,
|
|
33
|
-
hasPremium,
|
|
34
|
-
creditBalance,
|
|
35
|
-
isCreditsLoaded,
|
|
36
33
|
creditCost,
|
|
37
|
-
isOffline,
|
|
38
|
-
onShowAuthModal,
|
|
39
|
-
onShowPaywall,
|
|
40
34
|
onNetworkError,
|
|
41
35
|
onGenerationComplete,
|
|
42
36
|
onGenerationError,
|
|
@@ -47,6 +41,12 @@ export const TextToVideoWizardFlow: React.FC<TextToVideoWizardFlowProps> = (prop
|
|
|
47
41
|
|
|
48
42
|
const tokens = useAppDesignTokens();
|
|
49
43
|
|
|
44
|
+
// Centralized access control - handles offline, auth, credits, paywall
|
|
45
|
+
const { requireFeature } = useAIFeatureGate({
|
|
46
|
+
creditCost,
|
|
47
|
+
onNetworkError,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
50
|
const scenario: WizardScenarioData = useMemo(
|
|
51
51
|
() => ({
|
|
52
52
|
id: "text-to-video",
|
|
@@ -71,15 +71,10 @@ export const TextToVideoWizardFlow: React.FC<TextToVideoWizardFlowProps> = (prop
|
|
|
71
71
|
|
|
72
72
|
const handleGenerationStart = useCallback(
|
|
73
73
|
(_data: Record<string, unknown>, proceed: () => void) => {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
// Auth check - must be authenticated
|
|
77
|
-
if (!isAuthenticated) { onShowAuthModal(proceed); return; }
|
|
78
|
-
// Credit check - must have enough credits
|
|
79
|
-
if (!hasPremium && isCreditsLoaded && creditBalance < creditCost) { onShowPaywall(); return; }
|
|
80
|
-
proceed();
|
|
74
|
+
// Use centralized access control - checks offline, auth, credits
|
|
75
|
+
requireFeature(proceed);
|
|
81
76
|
},
|
|
82
|
-
[
|
|
77
|
+
[requireFeature],
|
|
83
78
|
);
|
|
84
79
|
|
|
85
80
|
const handleGenerationComplete = useCallback(() => {
|
|
@@ -99,7 +94,6 @@ export const TextToVideoWizardFlow: React.FC<TextToVideoWizardFlowProps> = (prop
|
|
|
99
94
|
onGenerationStart={handleGenerationStart}
|
|
100
95
|
onGenerationComplete={handleGenerationComplete}
|
|
101
96
|
onGenerationError={onGenerationError}
|
|
102
|
-
onCreditsExhausted={onShowPaywall}
|
|
103
97
|
onBack={onBack}
|
|
104
98
|
onTryAgain={onBack}
|
|
105
99
|
t={t}
|