@adlas/create-app 1.0.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/README.md +476 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/figma.d.ts +16 -0
- package/dist/commands/figma.d.ts.map +1 -0
- package/dist/commands/figma.js +172 -0
- package/dist/commands/figma.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +5 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1471 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/swagger.d.ts +16 -0
- package/dist/commands/swagger.d.ts.map +1 -0
- package/dist/commands/swagger.js +404 -0
- package/dist/commands/swagger.js.map +1 -0
- package/dist/commands/update.d.ts +15 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/package.json +63 -0
- package/templates/.vscode/extensions.json +9 -0
- package/templates/.vscode/launch.json +26 -0
- package/templates/.vscode/settings.json +67 -0
- package/templates/.vscode/tasks.json +21 -0
- package/templates/boilerplate/config/fonts.ts +10 -0
- package/templates/boilerplate/config/navigationUrls.ts +47 -0
- package/templates/boilerplate/config/site.ts +96 -0
- package/templates/boilerplate/libs/I18n.ts +15 -0
- package/templates/boilerplate/libs/I18nNavigation.ts +5 -0
- package/templates/boilerplate/libs/I18nRouting.ts +9 -0
- package/templates/boilerplate/libs/env.ts +21 -0
- package/templates/boilerplate/libs/react-query/ReactQueryProvider.tsx +21 -0
- package/templates/boilerplate/libs/react-query/index.ts +2 -0
- package/templates/boilerplate/libs/react-query/queryClient.ts +62 -0
- package/templates/boilerplate/libs/react-query/queryKeys.ts +5 -0
- package/templates/boilerplate/public/images/index.ts +1 -0
- package/templates/boilerplate/reset.d.ts +2 -0
- package/templates/boilerplate/styles/globals.css +308 -0
- package/templates/boilerplate/types/i18n.ts +10 -0
- package/templates/boilerplate/types/locale.ts +8 -0
- package/templates/boilerplate/utils/file/fileConfig.ts +123 -0
- package/templates/boilerplate/utils/file/fileValidation.ts +78 -0
- package/templates/boilerplate/utils/file/imageCompression.ts +182 -0
- package/templates/boilerplate/utils/file/index.ts +3 -0
- package/templates/boilerplate/utils/helpers.ts +55 -0
- package/templates/boilerplate/validations/auth.validation.ts +92 -0
- package/templates/boilerplate/validations/commonValidations.ts +258 -0
- package/templates/boilerplate/validations/zodErrorMap.ts +101 -0
- package/templates/configs/.env.example +8 -0
- package/templates/configs/.prettierignore +23 -0
- package/templates/configs/.prettierrc.cjs +26 -0
- package/templates/configs/.prettierrc.icons.cjs +11 -0
- package/templates/configs/Dockerfile +6 -0
- package/templates/configs/commitlint.config.ts +8 -0
- package/templates/configs/eslint.config.mjs +119 -0
- package/templates/configs/knip.config.ts +32 -0
- package/templates/configs/lefthook.yml +42 -0
- package/templates/configs/lint-staged.config.js +8 -0
- package/templates/configs/next.config.template.ts +77 -0
- package/templates/configs/next.config.ts +43 -0
- package/templates/configs/package.json +75 -0
- package/templates/configs/postcss.config.mjs +15 -0
- package/templates/configs/svgr.config.mjs +129 -0
- package/templates/configs/tsconfig.json +75 -0
- package/templates/docs/AI_QUICK_REFERENCE.md +379 -0
- package/templates/docs/ARCHITECTURE_PATTERNS.md +927 -0
- package/templates/docs/DOCUMENTATION_INDEX.md +411 -0
- package/templates/docs/FIGMA_TO_CODE_GUIDE.md +768 -0
- package/templates/docs/IMPLEMENTATION_GUIDE.md +892 -0
- package/templates/docs/PROJECT_OVERVIEW.md +302 -0
- package/templates/docs/REFACTOR_PROGRESS.md +1113 -0
- package/templates/docs/SHADCN_TO_HEROUI_MIGRATION.md +1375 -0
- package/templates/docs/UI_COMPONENTS_GUIDE.md +893 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { siteConfig } from '@/config/site';
|
|
2
|
+
|
|
3
|
+
export const isBrowser = (): boolean => typeof window !== 'undefined';
|
|
4
|
+
|
|
5
|
+
export const isServer = (): boolean => typeof window === 'undefined';
|
|
6
|
+
|
|
7
|
+
export const formatPrice = (
|
|
8
|
+
price: string | number,
|
|
9
|
+
locale: string = siteConfig.locales[1] as string,
|
|
10
|
+
) => {
|
|
11
|
+
const priceNum = typeof price === 'string' ? Number.parseFloat(price) : price;
|
|
12
|
+
|
|
13
|
+
if (locale === siteConfig.locales[1]) {
|
|
14
|
+
const formatted = new Intl.NumberFormat('de-DE', {
|
|
15
|
+
style: 'currency',
|
|
16
|
+
currency: 'EUR',
|
|
17
|
+
minimumFractionDigits: 2,
|
|
18
|
+
maximumFractionDigits: 2,
|
|
19
|
+
}).format(priceNum);
|
|
20
|
+
return formatted;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const formatted = new Intl.NumberFormat('en-US', {
|
|
24
|
+
minimumFractionDigits: 2,
|
|
25
|
+
maximumFractionDigits: 2,
|
|
26
|
+
}).format(priceNum);
|
|
27
|
+
return `${formatted} €`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const formatLabel = (str: string) => {
|
|
31
|
+
return str
|
|
32
|
+
.split('-')
|
|
33
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
34
|
+
.join(' ');
|
|
35
|
+
};
|
|
36
|
+
// Format date
|
|
37
|
+
export const formatDate = (dateString: string, locale: string) => {
|
|
38
|
+
// Return empty string if dateString is empty or invalid
|
|
39
|
+
if (!dateString || dateString.trim() === '') {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const date = new Date(dateString);
|
|
44
|
+
|
|
45
|
+
// Check if date is invalid
|
|
46
|
+
if (Number.isNaN(date.getTime())) {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Intl.DateTimeFormat(locale === siteConfig.locales[0] ? 'en-US' : 'de-DE', {
|
|
51
|
+
year: 'numeric',
|
|
52
|
+
month: 'long',
|
|
53
|
+
day: 'numeric',
|
|
54
|
+
}).format(date);
|
|
55
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
confirmPasswordValidationI18n,
|
|
5
|
+
emailValidationI18n,
|
|
6
|
+
firstNameValidationI18n,
|
|
7
|
+
lastNameValidationI18n,
|
|
8
|
+
passwordValidationI18n,
|
|
9
|
+
} from './commonValidations';
|
|
10
|
+
|
|
11
|
+
// ==================== Authentication Validation Schemas with i18n ====================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Login validation schema with i18n support
|
|
15
|
+
*/
|
|
16
|
+
export const LoginValidationI18n = z.object({
|
|
17
|
+
email: emailValidationI18n,
|
|
18
|
+
password: passwordValidationI18n,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register person validation schema with i18n support
|
|
23
|
+
*/
|
|
24
|
+
const _RegisterPersonValidationI18n = z
|
|
25
|
+
.object({
|
|
26
|
+
firstName: firstNameValidationI18n,
|
|
27
|
+
lastName: lastNameValidationI18n,
|
|
28
|
+
email: emailValidationI18n,
|
|
29
|
+
password: passwordValidationI18n,
|
|
30
|
+
confirmPassword: confirmPasswordValidationI18n,
|
|
31
|
+
})
|
|
32
|
+
.refine(data => data.password === data.confirmPassword, {
|
|
33
|
+
path: ['confirmPassword'],
|
|
34
|
+
message: 'Validations.Auth.passwordsDoNotMatch',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Basic info validation schema with i18n support
|
|
39
|
+
* Used for the first step of registration
|
|
40
|
+
*/
|
|
41
|
+
export const PersonBasicInfoValidationI18n = z.object({
|
|
42
|
+
firstName: firstNameValidationI18n,
|
|
43
|
+
lastName: lastNameValidationI18n,
|
|
44
|
+
email: emailValidationI18n,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Password step validation schema with i18n support
|
|
49
|
+
* Used for the second step of registration
|
|
50
|
+
*/
|
|
51
|
+
export const PersonPasswordValidationI18n = z
|
|
52
|
+
.object({
|
|
53
|
+
password: passwordValidationI18n,
|
|
54
|
+
confirmPassword: confirmPasswordValidationI18n,
|
|
55
|
+
})
|
|
56
|
+
.refine(data => data.password === data.confirmPassword, {
|
|
57
|
+
path: ['confirmPassword'],
|
|
58
|
+
message: 'Validations.Auth.passwordsDoNotMatch',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Forgot password request validation schema with i18n support
|
|
63
|
+
*/
|
|
64
|
+
export const ForgotPasswordRequestValidationI18n = z.object({
|
|
65
|
+
email: emailValidationI18n,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Reset password validation schema with i18n support
|
|
70
|
+
*/
|
|
71
|
+
export const ResetPasswordValidationI18n = z
|
|
72
|
+
.object({
|
|
73
|
+
email: emailValidationI18n,
|
|
74
|
+
token: z.string().min(1, { message: 'Validations.Auth.tokenRequired' }),
|
|
75
|
+
password: passwordValidationI18n,
|
|
76
|
+
confirmPassword: confirmPasswordValidationI18n,
|
|
77
|
+
})
|
|
78
|
+
.refine(data => data.password === data.confirmPassword, {
|
|
79
|
+
path: ['confirmPassword'],
|
|
80
|
+
message: 'Validations.Auth.passwordsDoNotMatch',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ==================== Type Exports ====================
|
|
84
|
+
|
|
85
|
+
export type LoginValidationI18nType = z.infer<typeof LoginValidationI18n>;
|
|
86
|
+
export type RegisterPersonValidationI18nType = z.infer<typeof _RegisterPersonValidationI18n>;
|
|
87
|
+
export type PersonBasicInfoValidationI18nType = z.infer<typeof PersonBasicInfoValidationI18n>;
|
|
88
|
+
export type PersonPasswordValidationI18nType = z.infer<typeof PersonPasswordValidationI18n>;
|
|
89
|
+
export type ForgotPasswordRequestValidationI18nType = z.infer<
|
|
90
|
+
typeof ForgotPasswordRequestValidationI18n
|
|
91
|
+
>;
|
|
92
|
+
export type ResetPasswordValidationI18nType = z.infer<typeof ResetPasswordValidationI18n>;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { useTranslations } from 'next-intl';
|
|
2
|
+
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
import type { FileValidationError } from '@/components/ui';
|
|
6
|
+
|
|
7
|
+
// ==================== Types ====================
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type for field validation (used by Form component)
|
|
11
|
+
*/
|
|
12
|
+
export type FormFieldValidation = {
|
|
13
|
+
required?: boolean;
|
|
14
|
+
pattern?: RegExp;
|
|
15
|
+
min?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
minLength?: number;
|
|
18
|
+
maxLength?: number;
|
|
19
|
+
validate?: (value: unknown) => boolean | string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ==================== Common Validation Schemas with i18n ====================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Email validation with i18n support
|
|
26
|
+
*/
|
|
27
|
+
export const emailValidationI18n = z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1, { message: 'Validations.required' })
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.regex(/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/, {
|
|
32
|
+
message: 'Validations.email',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Password validation with i18n support
|
|
37
|
+
* Must match validatePasswordRequirements function
|
|
38
|
+
*/
|
|
39
|
+
export const passwordValidationI18n = z
|
|
40
|
+
.string()
|
|
41
|
+
.min(8, { message: 'Validations.Auth.passwordMinLength' })
|
|
42
|
+
.max(50, { message: 'Validations.Auth.passwordMaxLength' })
|
|
43
|
+
.regex(/[A-Z]/, { message: 'Validations.Auth.passwordUppercase' })
|
|
44
|
+
.regex(/[a-z]/, { message: 'Validations.Auth.passwordLowercase' })
|
|
45
|
+
.regex(/\d/, { message: 'Validations.Auth.passwordNumber' })
|
|
46
|
+
.regex(/[@#$%]/, { message: 'Validations.Auth.passwordSpecialChar' })
|
|
47
|
+
.regex(/^[a-z0-9@#$%]*$/i, { message: 'Validations.Auth.passwordEnglishOnly' });
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Confirm password validation with i18n support
|
|
51
|
+
*/
|
|
52
|
+
export const confirmPasswordValidationI18n = z
|
|
53
|
+
.string()
|
|
54
|
+
.min(1, { message: 'Validations.Auth.confirmPasswordRequired' });
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* First name validation with i18n support
|
|
58
|
+
*/
|
|
59
|
+
export const firstNameValidationI18n = z
|
|
60
|
+
.string()
|
|
61
|
+
.min(1, { message: 'Validations.required' })
|
|
62
|
+
.min(2, { message: 'Validations.minLength' })
|
|
63
|
+
.max(50, { message: 'Validations.maxLength' })
|
|
64
|
+
.regex(/^[a-z\s\-']+$/i, {
|
|
65
|
+
message: 'Validations.invalidFormat',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Last name validation with i18n support
|
|
70
|
+
*/
|
|
71
|
+
export const lastNameValidationI18n = z
|
|
72
|
+
.string()
|
|
73
|
+
.min(1, { message: 'Validations.required' })
|
|
74
|
+
.min(2, { message: 'Validations.minLength' })
|
|
75
|
+
.max(50, { message: 'Validations.maxLength' })
|
|
76
|
+
.regex(/^[a-z\s\-']+$/i, {
|
|
77
|
+
message: 'Validations.invalidFormat',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Phone number validation with i18n support
|
|
82
|
+
*/
|
|
83
|
+
export const phoneValidationI18n = z
|
|
84
|
+
.string()
|
|
85
|
+
.min(1, { message: 'Validations.required' })
|
|
86
|
+
.regex(/^\+?[\d\s\-()]+$/, {
|
|
87
|
+
message: 'Validations.invalidFormat',
|
|
88
|
+
})
|
|
89
|
+
.min(10, { message: 'Validations.minLength' })
|
|
90
|
+
.max(20, { message: 'Validations.maxLength' });
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Street address validation with i18n support
|
|
94
|
+
*/
|
|
95
|
+
export const streetAddressValidationI18n = z
|
|
96
|
+
.string()
|
|
97
|
+
.min(1, { message: 'Validations.required' })
|
|
98
|
+
.min(5, { message: 'Validations.minLength' })
|
|
99
|
+
.max(200, { message: 'Validations.maxLength' });
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* City validation with i18n support
|
|
103
|
+
*/
|
|
104
|
+
export const cityValidationI18n = z
|
|
105
|
+
.string()
|
|
106
|
+
.min(1, { message: 'Validations.required' })
|
|
107
|
+
.min(2, { message: 'Validations.minLength' })
|
|
108
|
+
.max(100, { message: 'Validations.maxLength' });
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Postal code validation with i18n support
|
|
112
|
+
*/
|
|
113
|
+
export const postalCodeValidationI18n = z
|
|
114
|
+
.string()
|
|
115
|
+
.min(1, { message: 'Validations.required' })
|
|
116
|
+
.regex(/^[\dA-Z\s\-]+$/i, {
|
|
117
|
+
message: 'Validations.invalidFormat',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Country code validation with i18n support
|
|
122
|
+
*/
|
|
123
|
+
export const countryCodeValidationI18n = z
|
|
124
|
+
.string()
|
|
125
|
+
.min(1, { message: 'Validations.required' })
|
|
126
|
+
.length(2, { message: 'Validations.invalidFormat' });
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* State/Province validation with i18n support
|
|
130
|
+
*/
|
|
131
|
+
export const stateProvinceValidationI18n = z
|
|
132
|
+
.string()
|
|
133
|
+
.max(100, { message: 'Validations.maxLength' })
|
|
134
|
+
.optional();
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gender validation with i18n support (optional)
|
|
138
|
+
*/
|
|
139
|
+
export const genderValidationI18n = z
|
|
140
|
+
.enum(['male', 'female', 'diverse'], {
|
|
141
|
+
message: 'Validations.required',
|
|
142
|
+
})
|
|
143
|
+
.optional();
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Birthday validation with i18n support (optional)
|
|
147
|
+
*/
|
|
148
|
+
export const birthdayValidationI18n = z
|
|
149
|
+
.string()
|
|
150
|
+
.refine(
|
|
151
|
+
val => {
|
|
152
|
+
if (!val) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const date = new Date(val);
|
|
156
|
+
if (Number.isNaN(date.getTime())) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const today = new Date();
|
|
161
|
+
const minDate = new Date();
|
|
162
|
+
minDate.setFullYear(today.getFullYear() - 120);
|
|
163
|
+
|
|
164
|
+
return date >= minDate && date <= today;
|
|
165
|
+
},
|
|
166
|
+
{ message: 'Validations.invalidDate' },
|
|
167
|
+
)
|
|
168
|
+
.optional();
|
|
169
|
+
|
|
170
|
+
// ==================== Utility Functions ====================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Makes any validation optional (allows empty string or undefined)
|
|
174
|
+
*/
|
|
175
|
+
export function optional<T extends z.ZodTypeAny>(schema: T) {
|
|
176
|
+
return schema.optional().or(z.literal(''));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Type guard for file validation errors
|
|
181
|
+
*/
|
|
182
|
+
const isFileValidationError = (value: unknown): value is FileValidationError => {
|
|
183
|
+
return (
|
|
184
|
+
typeof value === 'object' &&
|
|
185
|
+
value !== null &&
|
|
186
|
+
'isValidationError' in value &&
|
|
187
|
+
value.isValidationError === true
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Valid file input types for validation
|
|
193
|
+
*/
|
|
194
|
+
type FileInput = File | string | FileValidationError | null | undefined;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Creates a file validation schema with i18n support
|
|
198
|
+
* @param required - Whether the file is required
|
|
199
|
+
*/
|
|
200
|
+
export const createFileValidation = (required = false) =>
|
|
201
|
+
z.custom<FileInput>().superRefine((value, ctx) => {
|
|
202
|
+
// Handle validation error objects
|
|
203
|
+
if (isFileValidationError(value)) {
|
|
204
|
+
ctx.addIssue({
|
|
205
|
+
code: 'custom',
|
|
206
|
+
message: value.errorMessage,
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// If required, reject null/undefined/empty string
|
|
212
|
+
if (required && !value) {
|
|
213
|
+
ctx.addIssue({
|
|
214
|
+
code: 'custom',
|
|
215
|
+
message: 'Validations.File.required',
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// If value is a string (URL), it's valid - this happens after upload
|
|
221
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// If value exists but is not a File instance or valid string, reject it
|
|
226
|
+
if (value && !(value instanceof File) && typeof value !== 'string') {
|
|
227
|
+
ctx.addIssue({
|
|
228
|
+
code: 'custom',
|
|
229
|
+
message: 'Validations.File.invalidType',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Password requirement type for validation feedback
|
|
236
|
+
*/
|
|
237
|
+
type PasswordRequirement = {
|
|
238
|
+
text: string;
|
|
239
|
+
isValid: boolean;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Validates password requirements and returns validation results
|
|
244
|
+
* Used for real-time password strength feedback
|
|
245
|
+
*/
|
|
246
|
+
export const validatePasswordRequirements = (
|
|
247
|
+
password: string,
|
|
248
|
+
t: ReturnType<typeof useTranslations>,
|
|
249
|
+
): PasswordRequirement[] => {
|
|
250
|
+
return [
|
|
251
|
+
{ text: t('minLength'), isValid: password.length >= 8 },
|
|
252
|
+
{ text: t('maxLength'), isValid: password.length > 0 && password.length <= 50 },
|
|
253
|
+
{ text: t('uppercase'), isValid: /[A-Z]/.test(password) },
|
|
254
|
+
{ text: t('number'), isValid: /\d/.test(password) },
|
|
255
|
+
{ text: t('specialChar'), isValid: /[@#$%]/.test(password) },
|
|
256
|
+
{ text: t('englishOnly'), isValid: password.length > 0 && /^[a-z0-9@#$%]*$/i.test(password) },
|
|
257
|
+
];
|
|
258
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Translation function type - simplified to accept any params
|
|
5
|
+
*/
|
|
6
|
+
type TranslationFn = (key: string, params?: Record<string, unknown>) => string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Custom Zod error metadata for i18n
|
|
10
|
+
*/
|
|
11
|
+
export type ZodErrorMetadata = {
|
|
12
|
+
translationKey?: string;
|
|
13
|
+
params?: Record<string, string | number>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract parameter types from ZodErrorMap
|
|
18
|
+
*/
|
|
19
|
+
type ZodErrorMapParams = Parameters<z.ZodErrorMap>;
|
|
20
|
+
type ZodIssueType = ZodErrorMapParams[0];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extended issue type with all possible properties and custom metadata
|
|
24
|
+
*/
|
|
25
|
+
type ZodIssueWithMetadata = {
|
|
26
|
+
code: string;
|
|
27
|
+
path: (string | number)[];
|
|
28
|
+
message?: string;
|
|
29
|
+
received?: string;
|
|
30
|
+
expected?: string;
|
|
31
|
+
validation?: string;
|
|
32
|
+
minimum?: number;
|
|
33
|
+
maximum?: number;
|
|
34
|
+
type?: string;
|
|
35
|
+
params?: ZodErrorMetadata;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a custom Zod error map for i18n translation
|
|
40
|
+
* This replaces the pipe format with a clean, type-safe approach
|
|
41
|
+
*/
|
|
42
|
+
export const createZodErrorMap = (t: TranslationFn): z.ZodErrorMap => {
|
|
43
|
+
return ((issue: ZodIssueType) => {
|
|
44
|
+
const typedIssue = issue as ZodIssueWithMetadata;
|
|
45
|
+
// Get custom metadata if provided
|
|
46
|
+
const metadata = typedIssue.params;
|
|
47
|
+
const translationKey = metadata?.translationKey;
|
|
48
|
+
const params = metadata?.params;
|
|
49
|
+
|
|
50
|
+
// If custom translation key is provided, use it
|
|
51
|
+
if (translationKey) {
|
|
52
|
+
return {
|
|
53
|
+
message: params ? t(translationKey, params) : t(translationKey),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle different Zod issue types with default translations
|
|
58
|
+
switch (typedIssue.code) {
|
|
59
|
+
case 'invalid_type':
|
|
60
|
+
if (typedIssue.received === 'undefined' || typedIssue.received === 'null') {
|
|
61
|
+
return { message: t('Validations.required') };
|
|
62
|
+
}
|
|
63
|
+
return { message: t('Validations.invalidFormat') };
|
|
64
|
+
|
|
65
|
+
case 'invalid_string':
|
|
66
|
+
if (typedIssue.validation === 'email') {
|
|
67
|
+
return { message: t('Validations.email') };
|
|
68
|
+
}
|
|
69
|
+
if (typedIssue.validation === 'regex') {
|
|
70
|
+
return { message: t('Validations.invalidFormat') };
|
|
71
|
+
}
|
|
72
|
+
return { message: t('Validations.invalidFormat') };
|
|
73
|
+
|
|
74
|
+
case 'too_small':
|
|
75
|
+
if (typedIssue.type === 'string') {
|
|
76
|
+
if (typedIssue.minimum === 1) {
|
|
77
|
+
return { message: t('Validations.required') };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
message: t('Validations.minLength', { min: typedIssue.minimum as number }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { message: t('Validations.invalidFormat') };
|
|
84
|
+
|
|
85
|
+
case 'too_big':
|
|
86
|
+
if (typedIssue.type === 'string') {
|
|
87
|
+
return {
|
|
88
|
+
message: t('Validations.maxLength', { max: typedIssue.maximum as number }),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { message: t('Validations.invalidFormat') };
|
|
92
|
+
|
|
93
|
+
case 'custom':
|
|
94
|
+
// For custom validations, the message is already set
|
|
95
|
+
return { message: typedIssue.message ?? t('Validations.invalidFormat') };
|
|
96
|
+
|
|
97
|
+
default:
|
|
98
|
+
return { message: t('Validations.invalidFormat') };
|
|
99
|
+
}
|
|
100
|
+
}) as z.ZodErrorMap;
|
|
101
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
.pnp
|
|
4
|
+
.pnp.js
|
|
5
|
+
|
|
6
|
+
# Build outputs
|
|
7
|
+
.next
|
|
8
|
+
out
|
|
9
|
+
dist
|
|
10
|
+
build
|
|
11
|
+
|
|
12
|
+
# Logs
|
|
13
|
+
*.log
|
|
14
|
+
|
|
15
|
+
# Package manager
|
|
16
|
+
pnpm-lock.yaml
|
|
17
|
+
package-lock.json
|
|
18
|
+
yarn.lock
|
|
19
|
+
|
|
20
|
+
# Ignore markdown and YAML files (if you want ESLint to handle them)
|
|
21
|
+
*.md
|
|
22
|
+
*.yml
|
|
23
|
+
*.yaml
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
printWidth: 100,
|
|
3
|
+
singleQuote: true,
|
|
4
|
+
semi: true,
|
|
5
|
+
trailingComma: 'all',
|
|
6
|
+
tabWidth: 2,
|
|
7
|
+
useTabs: false,
|
|
8
|
+
arrowParens: 'avoid',
|
|
9
|
+
plugins: ['prettier-plugin-tailwindcss'],
|
|
10
|
+
overrides: [
|
|
11
|
+
{
|
|
12
|
+
files: 'src/components/icons/**/*.{ts,tsx}',
|
|
13
|
+
options: {
|
|
14
|
+
plugins: [],
|
|
15
|
+
printWidth: 100,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
// Disable Tailwind plugin for JS/TS/JSX/TSX (ESLint handles it)
|
|
20
|
+
files: '**/*.{js,jsx,ts,tsx}',
|
|
21
|
+
options: {
|
|
22
|
+
plugins: [],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UserConfig } from '@commitlint/types';
|
|
2
|
+
|
|
3
|
+
const Configuration: UserConfig = {
|
|
4
|
+
extends: ['@commitlint/config-conventional'],
|
|
5
|
+
ignores: [message => message.startsWith('chore: bump')], // Ignore dependabot commits
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default Configuration;
|