@umituz/react-native-validation 1.4.7 → 1.4.9
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/index.ts +6 -11
- package/src/infrastructure/utils/advanced-validators.ts +114 -0
- package/src/infrastructure/utils/date-validators.ts +28 -0
- package/src/infrastructure/utils/numeric-validators.ts +54 -0
- package/src/infrastructure/utils/text-validators.ts +133 -0
- package/src/infrastructure/utils/validators.ts +22 -461
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -23,20 +23,15 @@ export {
|
|
|
23
23
|
validatePattern,
|
|
24
24
|
validateDateOfBirth,
|
|
25
25
|
validateAge,
|
|
26
|
-
batchValidate,
|
|
27
26
|
} from "./infrastructure/utils/validators";
|
|
28
27
|
|
|
29
|
-
//
|
|
28
|
+
// Infrastructure Layer - Advanced Validators
|
|
30
29
|
export {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
validateDreamTags,
|
|
37
|
-
validateDreamPrivacy,
|
|
38
|
-
validateDreamForm,
|
|
39
|
-
} from "./infrastructure/utils/validators";
|
|
30
|
+
validateEnum,
|
|
31
|
+
validateTags,
|
|
32
|
+
validateDateRange,
|
|
33
|
+
batchValidate,
|
|
34
|
+
} from "./infrastructure/utils/advanced-validators";
|
|
40
35
|
|
|
41
36
|
// Infrastructure Layer - Sanitization
|
|
42
37
|
export {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Validation Utilities
|
|
3
|
+
* Complex validation functions for React Native forms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from "../../domain/entities/ValidationResult";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Custom enum validation
|
|
10
|
+
* Generic validator for enum-like values with custom options
|
|
11
|
+
*/
|
|
12
|
+
export const validateEnum = (
|
|
13
|
+
value: string,
|
|
14
|
+
validOptions: readonly string[],
|
|
15
|
+
fieldName: string = "Field",
|
|
16
|
+
): ValidationResult => {
|
|
17
|
+
if (!value || value.trim() === "") {
|
|
18
|
+
return { isValid: false, error: `${fieldName} is required` };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!validOptions.includes(value.toLowerCase())) {
|
|
22
|
+
return {
|
|
23
|
+
isValid: false,
|
|
24
|
+
error: `Please select a valid ${fieldName.toLowerCase()}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { isValid: true };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tags validation
|
|
33
|
+
* Generic validator for array of strings with constraints
|
|
34
|
+
*/
|
|
35
|
+
export const validateTags = (
|
|
36
|
+
tags: string[],
|
|
37
|
+
maxTags: number = 10,
|
|
38
|
+
maxTagLength: number = 20,
|
|
39
|
+
): ValidationResult => {
|
|
40
|
+
if (!Array.isArray(tags)) {
|
|
41
|
+
return { isValid: false, error: "Tags must be an array" };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (tags.length > maxTags) {
|
|
45
|
+
return { isValid: false, error: `Maximum ${maxTags} tags allowed` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const tag of tags) {
|
|
49
|
+
if (typeof tag !== "string" || tag.trim().length === 0) {
|
|
50
|
+
return { isValid: false, error: "All tags must be non-empty strings" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (tag.trim().length > maxTagLength) {
|
|
54
|
+
return {
|
|
55
|
+
isValid: false,
|
|
56
|
+
error: `Each tag must be at most ${maxTagLength} characters`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { isValid: true };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Date range validation
|
|
66
|
+
* Validates date is within a specific range
|
|
67
|
+
*/
|
|
68
|
+
export const validateDateRange = (
|
|
69
|
+
date: Date,
|
|
70
|
+
minDate?: Date,
|
|
71
|
+
maxDate?: Date,
|
|
72
|
+
fieldName: string = "Date",
|
|
73
|
+
): ValidationResult => {
|
|
74
|
+
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
75
|
+
return { isValid: false, error: `Please enter a valid ${fieldName.toLowerCase()}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (minDate && date < minDate) {
|
|
79
|
+
return {
|
|
80
|
+
isValid: false,
|
|
81
|
+
error: `${fieldName} cannot be before ${minDate.toLocaleDateString()}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (maxDate && date > maxDate) {
|
|
86
|
+
return {
|
|
87
|
+
isValid: false,
|
|
88
|
+
error: `${fieldName} cannot be after ${maxDate.toLocaleDateString()}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { isValid: true };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Batch validation
|
|
97
|
+
* Validates multiple fields and returns all errors
|
|
98
|
+
*/
|
|
99
|
+
export const batchValidate = (
|
|
100
|
+
validations: Array<{ field: string; validator: () => ValidationResult }>,
|
|
101
|
+
): { isValid: boolean; errors: Record<string, string> } => {
|
|
102
|
+
const errors: Record<string, string> = {};
|
|
103
|
+
let isValid = true;
|
|
104
|
+
|
|
105
|
+
validations.forEach(({ field, validator }) => {
|
|
106
|
+
const result = validator();
|
|
107
|
+
if (!result.isValid && result.error) {
|
|
108
|
+
errors[field] = result.error;
|
|
109
|
+
isValid = false;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { isValid, errors };
|
|
114
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Validation Utilities
|
|
3
|
+
* Validation functions for date fields
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from "../../../domain/entities/ValidationResult";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate date of birth
|
|
10
|
+
*/
|
|
11
|
+
export const validateDateOfBirth = (date: Date): ValidationResult => {
|
|
12
|
+
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
13
|
+
return { isValid: false, error: "Please enter a valid date" };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const today = new Date();
|
|
17
|
+
const age = today.getFullYear() - date.getFullYear();
|
|
18
|
+
|
|
19
|
+
if (age < 13) {
|
|
20
|
+
return { isValid: false, error: "You must be at least 13 years old" };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (age > 120) {
|
|
24
|
+
return { isValid: false, error: "Please enter a valid date of birth" };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { isValid: true };
|
|
28
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numeric Validation Utilities
|
|
3
|
+
* Validation functions for numeric values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from "../../../domain/entities/ValidationResult";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate number range
|
|
10
|
+
*/
|
|
11
|
+
export const validateNumberRange = (
|
|
12
|
+
value: number,
|
|
13
|
+
min: number,
|
|
14
|
+
max: number,
|
|
15
|
+
fieldName: string = "Value",
|
|
16
|
+
): ValidationResult => {
|
|
17
|
+
if (isNaN(value)) {
|
|
18
|
+
return { isValid: false, error: `${fieldName} must be a number` };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (value < min || value > max) {
|
|
22
|
+
return {
|
|
23
|
+
isValid: false,
|
|
24
|
+
error: `${fieldName} must be between ${min} and ${max}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { isValid: true };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate positive number
|
|
33
|
+
*/
|
|
34
|
+
export const validatePositiveNumber = (
|
|
35
|
+
value: number,
|
|
36
|
+
fieldName: string = "Value",
|
|
37
|
+
): ValidationResult => {
|
|
38
|
+
if (isNaN(value)) {
|
|
39
|
+
return { isValid: false, error: `${fieldName} must be a number` };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (value <= 0) {
|
|
43
|
+
return { isValid: false, error: `${fieldName} must be greater than 0` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { isValid: true };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate age
|
|
51
|
+
*/
|
|
52
|
+
export const validateAge = (age: number): ValidationResult => {
|
|
53
|
+
return validateNumberRange(age, 13, 120, "Age");
|
|
54
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Validation Utilities
|
|
3
|
+
* Validation functions for text-based fields
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ValidationResult } from "../../../domain/entities/ValidationResult";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate email format
|
|
10
|
+
*/
|
|
11
|
+
export const validateEmail = (email: string): ValidationResult => {
|
|
12
|
+
if (!email || email.trim() === "") {
|
|
13
|
+
return { isValid: false, error: "Email is required" };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17
|
+
if (!emailRegex.test(email)) {
|
|
18
|
+
return { isValid: false, error: "Please enter a valid email address" };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { isValid: true };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate required field
|
|
26
|
+
*/
|
|
27
|
+
export const validateRequired = (
|
|
28
|
+
value: string,
|
|
29
|
+
fieldName: string = "This field",
|
|
30
|
+
): ValidationResult => {
|
|
31
|
+
if (!value || value.trim() === "") {
|
|
32
|
+
return { isValid: false, error: `${fieldName} is required` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { isValid: true };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate name
|
|
40
|
+
*/
|
|
41
|
+
export const validateName = (
|
|
42
|
+
name: string,
|
|
43
|
+
fieldName: string = "Name",
|
|
44
|
+
minLength: number = 2,
|
|
45
|
+
): ValidationResult => {
|
|
46
|
+
if (!name || name.trim() === "") {
|
|
47
|
+
return { isValid: false, error: `${fieldName} is required` };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (name.trim().length < minLength) {
|
|
51
|
+
return {
|
|
52
|
+
isValid: false,
|
|
53
|
+
error: `${fieldName} must be at least ${minLength} characters`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { isValid: true };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate phone number (E.164 format)
|
|
62
|
+
*/
|
|
63
|
+
export const validatePhone = (phone: string): ValidationResult => {
|
|
64
|
+
if (!phone || phone.trim() === "") {
|
|
65
|
+
return { isValid: false, error: "Phone number is required" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const phoneRegex = /^\+[1-9]\d{1,14}$/;
|
|
69
|
+
if (!phoneRegex.test(phone)) {
|
|
70
|
+
return { isValid: false, error: "Please enter a valid phone number" };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { isValid: true };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate min length
|
|
78
|
+
*/
|
|
79
|
+
export const validateMinLength = (
|
|
80
|
+
value: string,
|
|
81
|
+
minLength: number,
|
|
82
|
+
fieldName: string = "Field",
|
|
83
|
+
): ValidationResult => {
|
|
84
|
+
if (!value || value.trim().length < minLength) {
|
|
85
|
+
return {
|
|
86
|
+
isValid: false,
|
|
87
|
+
error: `${fieldName} must be at least ${minLength} characters`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { isValid: true };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate max length
|
|
96
|
+
*/
|
|
97
|
+
export const validateMaxLength = (
|
|
98
|
+
value: string,
|
|
99
|
+
maxLength: number,
|
|
100
|
+
fieldName: string = "Field",
|
|
101
|
+
): ValidationResult => {
|
|
102
|
+
if (value && value.trim().length > maxLength) {
|
|
103
|
+
return {
|
|
104
|
+
isValid: false,
|
|
105
|
+
error: `${fieldName} must be at most ${maxLength} characters`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { isValid: true };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate pattern (regex)
|
|
114
|
+
*/
|
|
115
|
+
export const validatePattern = (
|
|
116
|
+
value: string,
|
|
117
|
+
pattern: RegExp,
|
|
118
|
+
fieldName: string = "Field",
|
|
119
|
+
errorMessage?: string,
|
|
120
|
+
): ValidationResult => {
|
|
121
|
+
if (!value) {
|
|
122
|
+
return { isValid: false, error: `${fieldName} is required` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!pattern.test(value)) {
|
|
126
|
+
return {
|
|
127
|
+
isValid: false,
|
|
128
|
+
error: errorMessage || `${fieldName} format is invalid`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { isValid: true };
|
|
133
|
+
};
|
|
@@ -3,464 +3,25 @@
|
|
|
3
3
|
* Comprehensive validation functions for React Native forms
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
value: string,
|
|
29
|
-
fieldName: string = "This field",
|
|
30
|
-
): ValidationResult => {
|
|
31
|
-
if (!value || value.trim() === "") {
|
|
32
|
-
return { isValid: false, error: `${fieldName} is required` };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return { isValid: true };
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Validate name
|
|
40
|
-
*/
|
|
41
|
-
export const validateName = (
|
|
42
|
-
name: string,
|
|
43
|
-
fieldName: string = "Name",
|
|
44
|
-
minLength: number = 2,
|
|
45
|
-
): ValidationResult => {
|
|
46
|
-
if (!name || name.trim() === "") {
|
|
47
|
-
return { isValid: false, error: `${fieldName} is required` };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (name.trim().length < minLength) {
|
|
51
|
-
return {
|
|
52
|
-
isValid: false,
|
|
53
|
-
error: `${fieldName} must be at least ${minLength} characters`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return { isValid: true };
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Validate phone number (E.164 format)
|
|
62
|
-
*/
|
|
63
|
-
export const validatePhone = (phone: string): ValidationResult => {
|
|
64
|
-
if (!phone || phone.trim() === "") {
|
|
65
|
-
return { isValid: false, error: "Phone number is required" };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const phoneRegex = /^\+[1-9]\d{1,14}$/;
|
|
69
|
-
if (!phoneRegex.test(phone)) {
|
|
70
|
-
return { isValid: false, error: "Please enter a valid phone number" };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { isValid: true };
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Validate number range
|
|
78
|
-
*/
|
|
79
|
-
export const validateNumberRange = (
|
|
80
|
-
value: number,
|
|
81
|
-
min: number,
|
|
82
|
-
max: number,
|
|
83
|
-
fieldName: string = "Value",
|
|
84
|
-
): ValidationResult => {
|
|
85
|
-
if (isNaN(value)) {
|
|
86
|
-
return { isValid: false, error: `${fieldName} must be a number` };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (value < min || value > max) {
|
|
90
|
-
return {
|
|
91
|
-
isValid: false,
|
|
92
|
-
error: `${fieldName} must be between ${min} and ${max}`,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return { isValid: true };
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Validate positive number
|
|
101
|
-
*/
|
|
102
|
-
export const validatePositiveNumber = (
|
|
103
|
-
value: number,
|
|
104
|
-
fieldName: string = "Value",
|
|
105
|
-
): ValidationResult => {
|
|
106
|
-
if (isNaN(value)) {
|
|
107
|
-
return { isValid: false, error: `${fieldName} must be a number` };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (value <= 0) {
|
|
111
|
-
return { isValid: false, error: `${fieldName} must be greater than 0` };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return { isValid: true };
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Validate min length
|
|
119
|
-
*/
|
|
120
|
-
export const validateMinLength = (
|
|
121
|
-
value: string,
|
|
122
|
-
minLength: number,
|
|
123
|
-
fieldName: string = "Field",
|
|
124
|
-
): ValidationResult => {
|
|
125
|
-
if (!value || value.trim().length < minLength) {
|
|
126
|
-
return {
|
|
127
|
-
isValid: false,
|
|
128
|
-
error: `${fieldName} must be at least ${minLength} characters`,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return { isValid: true };
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Validate max length
|
|
137
|
-
*/
|
|
138
|
-
export const validateMaxLength = (
|
|
139
|
-
value: string,
|
|
140
|
-
maxLength: number,
|
|
141
|
-
fieldName: string = "Field",
|
|
142
|
-
): ValidationResult => {
|
|
143
|
-
if (value && value.trim().length > maxLength) {
|
|
144
|
-
return {
|
|
145
|
-
isValid: false,
|
|
146
|
-
error: `${fieldName} must be at most ${maxLength} characters`,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return { isValid: true };
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Validate pattern (regex)
|
|
155
|
-
*/
|
|
156
|
-
export const validatePattern = (
|
|
157
|
-
value: string,
|
|
158
|
-
pattern: RegExp,
|
|
159
|
-
fieldName: string = "Field",
|
|
160
|
-
errorMessage?: string,
|
|
161
|
-
): ValidationResult => {
|
|
162
|
-
if (!value) {
|
|
163
|
-
return { isValid: false, error: `${fieldName} is required` };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!pattern.test(value)) {
|
|
167
|
-
return {
|
|
168
|
-
isValid: false,
|
|
169
|
-
error: errorMessage || `${fieldName} format is invalid`,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { isValid: true };
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Validate date of birth
|
|
178
|
-
*/
|
|
179
|
-
export const validateDateOfBirth = (date: Date): ValidationResult => {
|
|
180
|
-
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
181
|
-
return { isValid: false, error: "Please enter a valid date" };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const today = new Date();
|
|
185
|
-
const age = today.getFullYear() - date.getFullYear();
|
|
186
|
-
|
|
187
|
-
if (age < 13) {
|
|
188
|
-
return { isValid: false, error: "You must be at least 13 years old" };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (age > 120) {
|
|
192
|
-
return { isValid: false, error: "Please enter a valid date of birth" };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return { isValid: true };
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Validate age
|
|
200
|
-
*/
|
|
201
|
-
export const validateAge = (age: number): ValidationResult => {
|
|
202
|
-
return validateNumberRange(age, 13, 120, "Age");
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Batch validation
|
|
207
|
-
* Validates multiple fields and returns all errors
|
|
208
|
-
*/
|
|
209
|
-
export const batchValidate = (
|
|
210
|
-
validations: Array<{ field: string; validator: () => ValidationResult }>,
|
|
211
|
-
): { isValid: boolean; errors: Record<string, string> } => {
|
|
212
|
-
const errors: Record<string, string> = {};
|
|
213
|
-
let isValid = true;
|
|
214
|
-
|
|
215
|
-
validations.forEach(({ field, validator }) => {
|
|
216
|
-
const result = validator();
|
|
217
|
-
if (!result.isValid && result.error) {
|
|
218
|
-
errors[field] = result.error;
|
|
219
|
-
isValid = false;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
return { isValid, errors };
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Dream-specific validation functions
|
|
228
|
-
*/
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Validate dream title
|
|
232
|
-
*/
|
|
233
|
-
export const validateDreamTitle = (
|
|
234
|
-
title: string,
|
|
235
|
-
minLength: number = 3,
|
|
236
|
-
maxLength: number = 100,
|
|
237
|
-
): ValidationResult => {
|
|
238
|
-
if (!title || title.trim() === "") {
|
|
239
|
-
return { isValid: false, error: "Dream title is required" };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (title.trim().length < minLength) {
|
|
243
|
-
return {
|
|
244
|
-
isValid: false,
|
|
245
|
-
error: `Dream title must be at least ${minLength} characters`,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (title.trim().length > maxLength) {
|
|
250
|
-
return {
|
|
251
|
-
isValid: false,
|
|
252
|
-
error: `Dream title must be at most ${maxLength} characters`,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return { isValid: true };
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Validate dream description
|
|
261
|
-
*/
|
|
262
|
-
export const validateDreamDescription = (
|
|
263
|
-
description: string,
|
|
264
|
-
minLength: number = 10,
|
|
265
|
-
maxLength: number = 2000,
|
|
266
|
-
): ValidationResult => {
|
|
267
|
-
if (!description || description.trim() === "") {
|
|
268
|
-
return { isValid: false, error: "Dream description is required" };
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (description.trim().length < minLength) {
|
|
272
|
-
return {
|
|
273
|
-
isValid: false,
|
|
274
|
-
error: `Dream description must be at least ${minLength} characters`,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (description.trim().length > maxLength) {
|
|
279
|
-
return {
|
|
280
|
-
isValid: false,
|
|
281
|
-
error: `Dream description must be at most ${maxLength} characters`,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return { isValid: true };
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Validate dream mood
|
|
290
|
-
*/
|
|
291
|
-
export const validateDreamMood = (mood: string): ValidationResult => {
|
|
292
|
-
if (!mood || mood.trim() === "") {
|
|
293
|
-
return { isValid: false, error: "Dream mood is required" };
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const validMoods = [
|
|
297
|
-
"happy",
|
|
298
|
-
"sad",
|
|
299
|
-
"scared",
|
|
300
|
-
"excited",
|
|
301
|
-
"peaceful",
|
|
302
|
-
"anxious",
|
|
303
|
-
"confused",
|
|
304
|
-
"angry",
|
|
305
|
-
"surprised",
|
|
306
|
-
"neutral",
|
|
307
|
-
];
|
|
308
|
-
if (!validMoods.includes(mood.toLowerCase())) {
|
|
309
|
-
return {
|
|
310
|
-
isValid: false,
|
|
311
|
-
error: "Please select a valid mood",
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return { isValid: true };
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Validate dream category
|
|
320
|
-
*/
|
|
321
|
-
export const validateDreamCategory = (category: string): ValidationResult => {
|
|
322
|
-
if (!category || category.trim() === "") {
|
|
323
|
-
return { isValid: false, error: "Dream category is required" };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const validCategories = [
|
|
327
|
-
"lucid",
|
|
328
|
-
"nightmare",
|
|
329
|
-
"recurring",
|
|
330
|
-
"prophetic",
|
|
331
|
-
"healing",
|
|
332
|
-
"creative",
|
|
333
|
-
"adventure",
|
|
334
|
-
"relationship",
|
|
335
|
-
"work",
|
|
336
|
-
"childhood",
|
|
337
|
-
"flying",
|
|
338
|
-
"falling",
|
|
339
|
-
"chase",
|
|
340
|
-
"exam",
|
|
341
|
-
"tooth",
|
|
342
|
-
"other",
|
|
343
|
-
];
|
|
344
|
-
if (!validCategories.includes(category.toLowerCase())) {
|
|
345
|
-
return {
|
|
346
|
-
isValid: false,
|
|
347
|
-
error: "Please select a valid category",
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return { isValid: true };
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Validate dream date
|
|
356
|
-
*/
|
|
357
|
-
export const validateDreamDate = (date: Date): ValidationResult => {
|
|
358
|
-
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
359
|
-
return { isValid: false, error: "Please enter a valid dream date" };
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const today = new Date();
|
|
363
|
-
if (date > today) {
|
|
364
|
-
return { isValid: false, error: "Dream date cannot be in the future" };
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Don't allow dreams older than 1 year
|
|
368
|
-
const oneYearAgo = new Date();
|
|
369
|
-
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
|
370
|
-
if (date < oneYearAgo) {
|
|
371
|
-
return {
|
|
372
|
-
isValid: false,
|
|
373
|
-
error: "Dream date cannot be more than 1 year old",
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return { isValid: true };
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Validate dream tags
|
|
382
|
-
*/
|
|
383
|
-
export const validateDreamTags = (tags: string[]): ValidationResult => {
|
|
384
|
-
if (!Array.isArray(tags)) {
|
|
385
|
-
return { isValid: false, error: "Dream tags must be an array" };
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (tags.length > 10) {
|
|
389
|
-
return { isValid: false, error: "Maximum 10 tags allowed" };
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
for (const tag of tags) {
|
|
393
|
-
if (typeof tag !== "string" || tag.trim().length === 0) {
|
|
394
|
-
return { isValid: false, error: "All tags must be non-empty strings" };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (tag.trim().length > 20) {
|
|
398
|
-
return {
|
|
399
|
-
isValid: false,
|
|
400
|
-
error: "Each tag must be at most 20 characters",
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return { isValid: true };
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Validate dream privacy setting
|
|
410
|
-
*/
|
|
411
|
-
export const validateDreamPrivacy = (privacy: string): ValidationResult => {
|
|
412
|
-
if (!privacy || privacy.trim() === "") {
|
|
413
|
-
return { isValid: false, error: "Privacy setting is required" };
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const validPrivacyOptions = ["private", "friends", "public"];
|
|
417
|
-
if (!validPrivacyOptions.includes(privacy.toLowerCase())) {
|
|
418
|
-
return {
|
|
419
|
-
isValid: false,
|
|
420
|
-
error: "Please select a valid privacy setting",
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return { isValid: true };
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Complete dream form validation
|
|
429
|
-
* Validates all dream fields at once
|
|
430
|
-
*/
|
|
431
|
-
export const validateDreamForm = (formData: {
|
|
432
|
-
title?: string;
|
|
433
|
-
description?: string;
|
|
434
|
-
mood?: string;
|
|
435
|
-
category?: string;
|
|
436
|
-
date?: Date;
|
|
437
|
-
tags?: string[];
|
|
438
|
-
privacy?: string;
|
|
439
|
-
}): { isValid: boolean; errors: Record<string, string> } => {
|
|
440
|
-
const validations = [
|
|
441
|
-
{
|
|
442
|
-
field: "title",
|
|
443
|
-
validator: () => validateDreamTitle(formData.title || ""),
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
field: "description",
|
|
447
|
-
validator: () => validateDreamDescription(formData.description || ""),
|
|
448
|
-
},
|
|
449
|
-
{ field: "mood", validator: () => validateDreamMood(formData.mood || "") },
|
|
450
|
-
{
|
|
451
|
-
field: "category",
|
|
452
|
-
validator: () => validateDreamCategory(formData.category || ""),
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
field: "date",
|
|
456
|
-
validator: () => validateDreamDate(formData.date || new Date()),
|
|
457
|
-
},
|
|
458
|
-
{ field: "tags", validator: () => validateDreamTags(formData.tags || []) },
|
|
459
|
-
{
|
|
460
|
-
field: "privacy",
|
|
461
|
-
validator: () => validateDreamPrivacy(formData.privacy || ""),
|
|
462
|
-
},
|
|
463
|
-
];
|
|
464
|
-
|
|
465
|
-
return batchValidate(validations);
|
|
466
|
-
};
|
|
6
|
+
// Text validators
|
|
7
|
+
export {
|
|
8
|
+
validateEmail,
|
|
9
|
+
validateRequired,
|
|
10
|
+
validateName,
|
|
11
|
+
validatePhone,
|
|
12
|
+
validateMinLength,
|
|
13
|
+
validateMaxLength,
|
|
14
|
+
validatePattern,
|
|
15
|
+
} from "./text-validators";
|
|
16
|
+
|
|
17
|
+
// Numeric validators
|
|
18
|
+
export {
|
|
19
|
+
validateNumberRange,
|
|
20
|
+
validatePositiveNumber,
|
|
21
|
+
validateAge,
|
|
22
|
+
} from "./numeric-validators";
|
|
23
|
+
|
|
24
|
+
// Date validators
|
|
25
|
+
export {
|
|
26
|
+
validateDateOfBirth,
|
|
27
|
+
} from "./date-validators";
|