@umituz/react-native-ai-generation-content 1.61.39 → 1.61.40

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 (28) hide show
  1. package/package.json +1 -1
  2. package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +16 -213
  3. package/src/domains/creations/infrastructure/repositories/creations-operations.ts +128 -0
  4. package/src/domains/creations/infrastructure/repositories/creations-state-operations.ts +86 -0
  5. package/src/domains/creations/presentation/components/GalleryScreenHeader.tsx +54 -0
  6. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +23 -56
  7. package/src/domains/creations/presentation/utils/filter-buttons.util.ts +75 -0
  8. package/src/domains/generation/wizard/presentation/screens/GeneratingScreen.tsx +36 -43
  9. package/src/domains/generation/wizard/presentation/screens/TextInputScreen.tsx +6 -39
  10. package/src/domains/scenarios/presentation/screens/ScenarioPreviewScreen.tsx +7 -25
  11. package/src/features/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +26 -46
  12. package/src/features/shared/index.ts +6 -0
  13. package/src/features/shared/presentation/components/AutoSkipPreview.tsx +24 -0
  14. package/src/features/shared/presentation/components/index.ts +6 -0
  15. package/src/features/shared/presentation/utils/index.ts +14 -0
  16. package/src/features/shared/presentation/utils/wizard-flow.utils.ts +84 -0
  17. package/src/features/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +26 -46
  18. package/src/features/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +26 -46
  19. package/src/infrastructure/logging/debug.util.ts +1 -1
  20. package/src/infrastructure/logging/index.ts +1 -1
  21. package/src/infrastructure/validation/advanced-validator.ts +97 -0
  22. package/src/infrastructure/validation/ai-validator.ts +77 -0
  23. package/src/infrastructure/validation/base-validator.ts +149 -0
  24. package/src/infrastructure/validation/entity-validator.ts +64 -0
  25. package/src/infrastructure/validation/input-validator.ts +37 -409
  26. package/src/infrastructure/validation/sanitizer.ts +43 -0
  27. package/src/presentation/components/buttons/ContinueButton.tsx +72 -0
  28. package/src/presentation/components/buttons/index.ts +1 -0
@@ -1,411 +1,39 @@
1
1
  /**
2
2
  * Input Validation Utilities
3
- * Provides comprehensive input validation for security and data integrity
4
- */
5
-
6
- import {
7
- MAX_PROMPT_LENGTH,
8
- MIN_PROMPT_LENGTH,
9
- MAX_USER_ID_LENGTH,
10
- MAX_CREATION_ID_LENGTH,
11
- } from "../constants/validation.constants";
12
-
13
- /**
14
- * Validation result type
15
- */
16
- export interface ValidationResult {
17
- readonly isValid: boolean;
18
- readonly errors: readonly string[];
19
- }
20
-
21
- /**
22
- * String validation options
23
- */
24
- export interface StringValidationOptions {
25
- readonly minLength?: number;
26
- readonly maxLength?: number;
27
- readonly pattern?: RegExp;
28
- readonly allowedCharacters?: RegExp;
29
- readonly trim?: boolean;
30
- }
31
-
32
- /**
33
- * Numeric validation options
34
- */
35
- export interface NumericValidationOptions {
36
- readonly min?: number;
37
- readonly max?: number;
38
- readonly integer?: boolean;
39
- }
40
-
41
- /**
42
- * Sanitizes user input to prevent XSS and injection attacks
43
- */
44
- export function sanitizeString(input: unknown): string {
45
- if (typeof input !== "string") {
46
- return "";
47
- }
48
-
49
- return input
50
- .trim()
51
- .replace(/[<>]/g, "") // Remove potential HTML tags
52
- .replace(/javascript:/gi, "") // Remove javascript: protocol
53
- .replace(/data:/gi, "") // Remove data: protocol
54
- .replace(/vbscript:/gi, "") // Remove vbscript: protocol
55
- .replace(/on\w+\s*=/gi, "") // Remove event handlers
56
- .replace(/--/g, "") // Remove SQL comment sequences
57
- .replace(/;\s*drop\s+/gi, "") // Remove SQL injection attempts
58
- .replace(/['"\\]/g, "") // Remove quotes and backslashes
59
- .slice(0, 10000); // Limit length
60
- }
61
-
62
- /**
63
- * Validates a string input against provided rules
64
- */
65
- export function validateString(
66
- input: unknown,
67
- options: StringValidationOptions = {}
68
- ): ValidationResult {
69
- const errors: string[] = [];
70
-
71
- // Check if input is a string
72
- if (typeof input !== "string") {
73
- return {
74
- isValid: false,
75
- errors: ["Input must be a string"],
76
- };
77
- }
78
-
79
- let value = options.trim !== false ? input.trim() : input;
80
-
81
- // Check min length
82
- if (options.minLength !== undefined && value.length < options.minLength) {
83
- errors.push(`Input must be at least ${options.minLength} characters`);
84
- }
85
-
86
- // Check max length
87
- if (options.maxLength !== undefined && value.length > options.maxLength) {
88
- errors.push(`Input must be at most ${options.maxLength} characters`);
89
- }
90
-
91
- // Check pattern
92
- if (options.pattern && !options.pattern.test(value)) {
93
- errors.push("Input format is invalid");
94
- }
95
-
96
- // Check allowed characters
97
- if (options.allowedCharacters && !options.allowedCharacters.test(value)) {
98
- errors.push("Input contains invalid characters");
99
- }
100
-
101
- return {
102
- isValid: errors.length === 0,
103
- errors,
104
- };
105
- }
106
-
107
- /**
108
- * Validates a numeric input
109
- */
110
- export function validateNumber(
111
- input: unknown,
112
- options: NumericValidationOptions = {}
113
- ): ValidationResult {
114
- const errors: string[] = [];
115
-
116
- // Check if input is a number
117
- if (typeof input !== "number" || isNaN(input)) {
118
- return {
119
- isValid: false,
120
- errors: ["Input must be a number"],
121
- };
122
- }
123
-
124
- // Check if integer
125
- if (options.integer && !Number.isInteger(input)) {
126
- errors.push("Input must be an integer");
127
- }
128
-
129
- // Check min value
130
- if (options.min !== undefined && input < options.min) {
131
- errors.push(`Input must be at least ${options.min}`);
132
- }
133
-
134
- // Check max value
135
- if (options.max !== undefined && input > options.max) {
136
- errors.push(`Input must be at most ${options.max}`);
137
- }
138
-
139
- return {
140
- isValid: errors.length === 0,
141
- errors,
142
- };
143
- }
144
-
145
- /**
146
- * Validates URL format
147
- */
148
- export function validateURL(input: unknown): ValidationResult {
149
- if (typeof input !== "string") {
150
- return {
151
- isValid: false,
152
- errors: ["URL must be a string"],
153
- };
154
- }
155
-
156
- try {
157
- const url = new URL(input);
158
-
159
- // Only allow http and https protocols
160
- if (!["http:", "https:"].includes(url.protocol)) {
161
- return {
162
- isValid: false,
163
- errors: ["Only HTTP and HTTPS protocols are allowed"],
164
- };
165
- }
166
-
167
- return { isValid: true, errors: [] };
168
- } catch {
169
- return {
170
- isValid: false,
171
- errors: ["Invalid URL format"],
172
- };
173
- }
174
- }
175
-
176
- /**
177
- * Validates email format
178
- */
179
- export function validateEmail(input: unknown): ValidationResult {
180
- if (typeof input !== "string") {
181
- return {
182
- isValid: false,
183
- errors: ["Email must be a string"],
184
- };
185
- }
186
-
187
- // Basic email validation regex
188
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
189
-
190
- if (!emailRegex.test(input)) {
191
- return {
192
- isValid: false,
193
- errors: ["Invalid email format"],
194
- };
195
- }
196
-
197
- return { isValid: true, errors: [] };
198
- }
199
-
200
- /**
201
- * Validates base64 string
202
- */
203
- export function validateBase64(input: unknown): ValidationResult {
204
- if (typeof input !== "string") {
205
- return {
206
- isValid: false,
207
- errors: ["Input must be a string"],
208
- };
209
- }
210
-
211
- // Check if it's a valid base64 string
212
- const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
213
-
214
- if (!base64Regex.test(input)) {
215
- return {
216
- isValid: false,
217
- errors: ["Invalid base64 format"],
218
- };
219
- }
220
-
221
- // Check if length is valid (must be multiple of 4)
222
- if (input.length % 4 !== 0) {
223
- return {
224
- isValid: false,
225
- errors: ["Base64 string length must be a multiple of 4"],
226
- };
227
- }
228
-
229
- return { isValid: true, errors: [] };
230
- }
231
-
232
- /**
233
- * Validates object structure
234
- */
235
- export function validateObject(
236
- input: unknown,
237
- requiredFields: readonly string[] = []
238
- ): ValidationResult {
239
- const errors: string[] = [];
240
-
241
- if (typeof input !== "object" || input === null) {
242
- return {
243
- isValid: false,
244
- errors: ["Input must be an object"],
245
- };
246
- }
247
-
248
- // Check required fields
249
- for (const field of requiredFields) {
250
- if (!(field in input)) {
251
- errors.push(`Missing required field: ${field}`);
252
- }
253
- }
254
-
255
- return {
256
- isValid: errors.length === 0,
257
- errors,
258
- };
259
- }
260
-
261
- /**
262
- * Validates array input
263
- */
264
- export function validateArray(
265
- input: unknown,
266
- options: {
267
- readonly minLength?: number;
268
- readonly maxLength?: number;
269
- readonly itemType?: "string" | "number" | "object";
270
- } = {}
271
- ): ValidationResult {
272
- const errors: string[] = [];
273
-
274
- if (!Array.isArray(input)) {
275
- return {
276
- isValid: false,
277
- errors: ["Input must be an array"],
278
- };
279
- }
280
-
281
- // Check min length
282
- if (options.minLength !== undefined && input.length < options.minLength) {
283
- errors.push(`Array must have at least ${options.minLength} items`);
284
- }
285
-
286
- // Check max length
287
- if (options.maxLength !== undefined && input.length > options.maxLength) {
288
- errors.push(`Array must have at most ${options.maxLength} items`);
289
- }
290
-
291
- // Check item types
292
- if (options.itemType) {
293
- for (let i = 0; i < input.length; i++) {
294
- const item = input[i];
295
- const isValidType =
296
- options.itemType === "string"
297
- ? typeof item === "string"
298
- : options.itemType === "number"
299
- ? typeof item === "number"
300
- : typeof item === "object" && item !== null;
301
-
302
- if (!isValidType) {
303
- errors.push(`Item at index ${i} is not a ${options.itemType}`);
304
- }
305
- }
306
- }
307
-
308
- return {
309
- isValid: errors.length === 0,
310
- errors,
311
- };
312
- }
313
-
314
- /**
315
- * Combines multiple validation results
316
- */
317
- export function combineValidationResults(
318
- results: readonly ValidationResult[]
319
- ): ValidationResult {
320
- const allErrors = results.flatMap((r) => r.errors);
321
-
322
- return {
323
- isValid: allErrors.length === 0,
324
- errors: allErrors,
325
- };
326
- }
327
-
328
- /**
329
- * Sanitizes and validates user input in one step
330
- */
331
- export function sanitizeAndValidate(
332
- input: unknown,
333
- options: StringValidationOptions = {}
334
- ): { readonly sanitized: string; readonly validation: ValidationResult } {
335
- const sanitized = sanitizeString(input);
336
- const validation = validateString(sanitized, options);
337
-
338
- return { sanitized, validation };
339
- }
340
-
341
- /**
342
- * Validates prompt/input text for AI generation
343
- */
344
- export function validateAIPrompt(input: unknown): ValidationResult {
345
- const options: StringValidationOptions = {
346
- minLength: MIN_PROMPT_LENGTH,
347
- maxLength: MAX_PROMPT_LENGTH,
348
- trim: true,
349
- };
350
-
351
- return validateString(input, options);
352
- }
353
-
354
- /**
355
- * Validates image data (base64 or URL)
356
- */
357
- export function validateImageData(input: unknown): ValidationResult {
358
- if (typeof input !== "string") {
359
- return {
360
- isValid: false,
361
- errors: ["Image data must be a string"],
362
- };
363
- }
364
-
365
- // Check if it's a URL
366
- if (input.startsWith("http://") || input.startsWith("https://")) {
367
- return validateURL(input);
368
- }
369
-
370
- // Check if it's base64
371
- if (input.startsWith("data:image/")) {
372
- const base64Part = input.split(",")[1];
373
- if (!base64Part) {
374
- return {
375
- isValid: false,
376
- errors: ["Invalid data URI format"],
377
- };
378
- }
379
- return validateBase64(base64Part);
380
- }
381
-
382
- return {
383
- isValid: false,
384
- errors: ["Image data must be a URL or base64 data URI"],
385
- };
386
- }
387
-
388
- /**
389
- * Validates user ID
390
- */
391
- export function validateUserId(input: unknown): ValidationResult {
392
- const options: StringValidationOptions = {
393
- minLength: 1,
394
- maxLength: MAX_USER_ID_LENGTH,
395
- pattern: /^[a-zA-Z0-9_-]+$/,
396
- };
397
-
398
- return validateString(input, options);
399
- }
400
-
401
- /**
402
- * Validates creation ID
403
- */
404
- export function validateCreationId(input: unknown): ValidationResult {
405
- const options: StringValidationOptions = {
406
- minLength: 1,
407
- maxLength: MAX_CREATION_ID_LENGTH,
408
- };
409
-
410
- return validateString(input, options);
411
- }
3
+ * Main export module for all validation functions
4
+ */
5
+
6
+ // Base validators
7
+ export {
8
+ validateString,
9
+ validateNumber,
10
+ validateURL,
11
+ validateEmail,
12
+ validateBase64,
13
+ type ValidationResult,
14
+ type StringValidationOptions,
15
+ type NumericValidationOptions,
16
+ } from "./base-validator";
17
+
18
+ // Sanitizers
19
+ export { sanitizeString, sanitizeObject } from "./sanitizer";
20
+
21
+ // Advanced validators
22
+ export {
23
+ validateObject,
24
+ validateArray,
25
+ combineValidationResults,
26
+ sanitizeAndValidate,
27
+ } from "./advanced-validator";
28
+
29
+ // AI validators
30
+ export { validateAIPrompt, validateImageData, validateVideoUrl } from "./ai-validator";
31
+
32
+ // Entity validators
33
+ export {
34
+ validateUserId,
35
+ validateCreationId,
36
+ validateScenarioId,
37
+ validateModelName,
38
+ validateProviderName,
39
+ } from "./entity-validator";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Input Sanitization Utilities
3
+ * Sanitizes user input to prevent XSS and injection attacks
4
+ */
5
+
6
+ /**
7
+ * Sanitizes user input to prevent XSS and injection attacks
8
+ */
9
+ export function sanitizeString(input: unknown): string {
10
+ if (typeof input !== "string") {
11
+ return "";
12
+ }
13
+
14
+ return input
15
+ .trim()
16
+ .replace(/[<>]/g, "")
17
+ .replace(/javascript:/gi, "")
18
+ .replace(/data:/gi, "")
19
+ .replace(/vbscript:/gi, "")
20
+ .replace(/on\w+\s*=/gi, "")
21
+ .replace(/--/g, "")
22
+ .replace(/;\s*drop\s+/gi, "")
23
+ .replace(/['"\\]/g, "")
24
+ .slice(0, 10000);
25
+ }
26
+
27
+ /**
28
+ * Sanitizes object by removing dangerous properties
29
+ */
30
+ export function sanitizeObject<T extends Record<string, unknown>>(input: T): T {
31
+ if (!input || typeof input !== "object") {
32
+ return input;
33
+ }
34
+
35
+ const sanitized = { ...input } as T;
36
+ const dangerousKeys = ["__proto__", "constructor", "prototype"];
37
+
38
+ for (const key of dangerousKeys) {
39
+ delete (sanitized as Record<string, unknown>)[key];
40
+ }
41
+
42
+ return sanitized;
43
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * ContinueButton Component
3
+ * Reusable continue button for wizard flows and forms
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { TouchableOpacity, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens, AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
9
+
10
+ export interface ContinueButtonProps {
11
+ readonly label: string;
12
+ readonly canContinue: boolean;
13
+ readonly onPress: () => void;
14
+ readonly iconName?: string;
15
+ readonly disabled?: boolean;
16
+ }
17
+
18
+ export const ContinueButton: React.FC<ContinueButtonProps> = ({
19
+ label,
20
+ canContinue,
21
+ onPress,
22
+ iconName = "arrow-forward",
23
+ disabled = false,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+ const isEnabled = canContinue && !disabled;
27
+
28
+ const buttonStyle = useMemo(() => [
29
+ styles.button,
30
+ {
31
+ backgroundColor: isEnabled ? tokens.colors.primary : tokens.colors.surfaceVariant,
32
+ opacity: isEnabled ? 1 : 0.5,
33
+ },
34
+ ], [isEnabled, tokens.colors.primary, tokens.colors.surfaceVariant]);
35
+
36
+ const textStyle = useMemo(() => [
37
+ styles.text,
38
+ { color: isEnabled ? tokens.colors.onPrimary : tokens.colors.textSecondary },
39
+ ], [isEnabled, tokens.colors.onPrimary, tokens.colors.textSecondary]);
40
+
41
+ return (
42
+ <TouchableOpacity
43
+ onPress={onPress}
44
+ activeOpacity={0.7}
45
+ disabled={!isEnabled}
46
+ style={buttonStyle}
47
+ >
48
+ <AtomicText type="bodyMedium" style={textStyle}>
49
+ {label}
50
+ </AtomicText>
51
+ <AtomicIcon
52
+ name={iconName}
53
+ size="sm"
54
+ color={isEnabled ? "onPrimary" : "textSecondary"}
55
+ />
56
+ </TouchableOpacity>
57
+ );
58
+ };
59
+
60
+ const styles = StyleSheet.create({
61
+ button: {
62
+ flexDirection: "row",
63
+ alignItems: "center",
64
+ paddingHorizontal: 16,
65
+ paddingVertical: 8,
66
+ borderRadius: 999,
67
+ },
68
+ text: {
69
+ fontWeight: "800",
70
+ marginRight: 4,
71
+ },
72
+ });
@@ -1 +1,2 @@
1
1
  export { GenerateButton, type GenerateButtonProps } from "./GenerateButton";
2
+ export { ContinueButton, type ContinueButtonProps } from "./ContinueButton";