@kyro-cms/admin 0.2.4 → 0.3.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 +46 -272
- package/package.json +37 -10
- package/src/blocks/examples/sample-block-2.tsx +27 -0
- package/src/blocks/examples/sample-block.tsx +26 -0
- package/src/blocks/index.ts +14 -0
- package/src/blocks/registry.ts +38 -0
- package/src/blocks/types.ts +23 -0
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +1 -1
- package/src/components/AuditLogsPage.tsx +1 -1
- package/src/components/AutoForm.tsx +2 -2
- package/src/components/BrandingHub.tsx +1 -1
- package/src/components/CreateView.tsx +1 -1
- package/src/components/DetailView.tsx +1 -1
- package/src/components/DeveloperCenter.tsx +1 -1
- package/src/components/EnhancedListView.tsx +1 -1
- package/src/components/ListView.tsx +1 -1
- package/src/components/LoginPage.tsx +1 -1
- package/src/components/MediaGallery.tsx +1 -1
- package/src/components/UserManagement.tsx +1 -1
- package/src/components/WebhookManager.tsx +2 -2
- package/src/components/fields/RelationshipBlockField.tsx +1 -1
- package/src/components/fields/RelationshipField.tsx +1 -1
- package/src/components/fields/UploadField.tsx +1 -6
- package/src/components/ui/CommandPalette.tsx +1 -1
- package/src/fields/examples/sample-field-2.tsx +30 -0
- package/src/fields/examples/sample-field.tsx +30 -0
- package/src/fields/index.ts +33 -0
- package/src/fields/registry.tsx +46 -0
- package/src/fields/types.ts +24 -0
- package/src/hooks/data.ts +116 -0
- package/src/hooks/examples/sample-hook-2.ts +13 -0
- package/src/hooks/examples/sample-hook.ts +12 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/lifecycle.ts +81 -0
- package/src/hooks/types.ts +40 -0
- package/src/index.ts +78 -0
- package/src/integration.ts +52 -0
- package/src/pages/api/[collection]/[id]/publish.ts +2 -2
- package/src/pages/api/[collection]/[id]/unpublish.ts +2 -2
- package/src/pages/api/[collection]/[id]/versions.ts +1 -1
- package/src/pages/api/[collection]/[id].ts +2 -2
- package/src/pages/api/[collection]/index.ts +2 -2
- package/src/pages/api/collections.ts +1 -1
- package/src/pages/api/globals/[slug].ts +2 -2
- package/src/pages/api/graphql.ts +3 -3
- package/src/pages/api/media/folders.ts +1 -1
- package/src/pages/api/media/index.ts +1 -1
- package/src/pages/api/media/resize.ts +1 -1
- package/src/pages/api/slug-availability.ts +2 -2
- package/src/pages/api/storage-config.ts +1 -1
- package/src/pages/api/storage-status.ts +1 -1
- package/src/pages/api/upload.ts +1 -1
- package/src/plugins/examples/sample-plugin-2.ts +21 -0
- package/src/plugins/examples/sample-plugin.ts +21 -0
- package/src/plugins/index.ts +10 -0
- package/src/plugins/registry.ts +36 -0
- package/src/plugins/types.ts +22 -0
- package/src/styles/main.css +2 -41
- package/src/theme/ThemeProvider.tsx +238 -0
- package/src/theme/index.ts +20 -0
- package/src/theme/tokens.ts +222 -0
- package/src/components/Modal.tsx +0 -206
- package/src/components/index.ts +0 -29
- package/src/env.ts +0 -20
- package/src/lib/i18n.tsx +0 -353
- package/src/lib/validation.ts +0 -250
- package/src/pages/api/globals/[slug]/test.ts +0 -171
package/src/lib/i18n.tsx
DELETED
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
import { create } from "zustand";
|
|
2
|
-
|
|
3
|
-
export const en = {
|
|
4
|
-
common: {
|
|
5
|
-
save: "Save",
|
|
6
|
-
cancel: "Cancel",
|
|
7
|
-
delete: "Delete",
|
|
8
|
-
edit: "Edit",
|
|
9
|
-
create: "Create",
|
|
10
|
-
add: "Add",
|
|
11
|
-
update: "Update",
|
|
12
|
-
confirm: "Confirm",
|
|
13
|
-
clear: "Clear",
|
|
14
|
-
close: "Close",
|
|
15
|
-
back: "Back",
|
|
16
|
-
next: "Next",
|
|
17
|
-
submit: "Submit",
|
|
18
|
-
remove: "Remove",
|
|
19
|
-
loading: "Loading...",
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
status: {
|
|
23
|
-
draft: "Draft",
|
|
24
|
-
published: "Published",
|
|
25
|
-
error: "Error",
|
|
26
|
-
success: "Success",
|
|
27
|
-
failed: "Failed",
|
|
28
|
-
archived: "Archived",
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
form: {
|
|
32
|
-
email: "Email",
|
|
33
|
-
password: "Password",
|
|
34
|
-
confirmPassword: "Confirm Password",
|
|
35
|
-
name: "Name",
|
|
36
|
-
label: "Label",
|
|
37
|
-
title: "Title",
|
|
38
|
-
description: "Description",
|
|
39
|
-
role: "Role",
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
placeholder: {
|
|
43
|
-
search: "Search...",
|
|
44
|
-
searchByNameOrEmail: "Search by name or email...",
|
|
45
|
-
searchFiles: "Search files...",
|
|
46
|
-
searchMedia: "Search media...",
|
|
47
|
-
searchTypes: "Search types...",
|
|
48
|
-
enterHeading: "Enter heading text...",
|
|
49
|
-
enterParagraph: "Enter paragraph text...",
|
|
50
|
-
enterMarkdown: "Enter markdown content...",
|
|
51
|
-
linkText: "Link text...",
|
|
52
|
-
url: "https://...",
|
|
53
|
-
value: "Value...",
|
|
54
|
-
videoUrl: "MP4 URL, YouTube, or Vimeo link...",
|
|
55
|
-
jsonPlaceholder: '{"key": "value"}',
|
|
56
|
-
folderName: "Folder name",
|
|
57
|
-
apiKey: "API Key",
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
tooltip: {
|
|
61
|
-
moveUp: "Move up",
|
|
62
|
-
moveDown: "Move down",
|
|
63
|
-
remove: "Remove",
|
|
64
|
-
edit: "Edit",
|
|
65
|
-
delete: "Delete",
|
|
66
|
-
livePreview: "Live Preview",
|
|
67
|
-
toggleSidebar: "Toggle Sidebar",
|
|
68
|
-
fullscreen: "Fullscreen",
|
|
69
|
-
exitFullscreen: "Exit fullscreen",
|
|
70
|
-
undo: "Undo",
|
|
71
|
-
redo: "Redo",
|
|
72
|
-
bold: "Bold",
|
|
73
|
-
italic: "Italic",
|
|
74
|
-
underline: "Underline",
|
|
75
|
-
strikethrough: "Strikethrough",
|
|
76
|
-
code: "Code",
|
|
77
|
-
link: "Link",
|
|
78
|
-
bulletList: "Bullet List",
|
|
79
|
-
numberedList: "Numbered List",
|
|
80
|
-
generateNewKey: "Generate new key",
|
|
81
|
-
copyToClipboard: "Copy to clipboard",
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
media: {
|
|
85
|
-
all: "All",
|
|
86
|
-
images: "Images",
|
|
87
|
-
videos: "Videos",
|
|
88
|
-
audio: "Audio",
|
|
89
|
-
documents: "Documents",
|
|
90
|
-
archives: "Archives",
|
|
91
|
-
noMediaFound: "No media files found",
|
|
92
|
-
deleteMedia: "Delete Media",
|
|
93
|
-
createFolder: "Create New Folder",
|
|
94
|
-
deleteFolder: "Delete Folder",
|
|
95
|
-
deleteSelected: "Delete Selected",
|
|
96
|
-
downloadCollection: "Download Collection",
|
|
97
|
-
editMetadata: "Edit metadata",
|
|
98
|
-
download: "Download",
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
user: {
|
|
102
|
-
teamManagement: "Team Management",
|
|
103
|
-
inviteMember: "Invite Member",
|
|
104
|
-
allUsers: "All Users",
|
|
105
|
-
admins: "Admins",
|
|
106
|
-
restricted: "Restricted",
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
auth: {
|
|
110
|
-
signIn: "Sign In",
|
|
111
|
-
createAccount: "Create Account",
|
|
112
|
-
signingIn: "Signing in...",
|
|
113
|
-
creatingAccount: "Creating account...",
|
|
114
|
-
dontHaveAccount: "Don't have an account?",
|
|
115
|
-
signUp: "Sign up",
|
|
116
|
-
alreadyHaveAccount: "Already have an account?",
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
confirm: {
|
|
120
|
-
deleteEntry: "Delete Entry",
|
|
121
|
-
deleteConfirm: "Delete {item}?",
|
|
122
|
-
actionCannotBeUndone: "This action cannot be undone.",
|
|
123
|
-
deleteDocuments: "Delete Documents",
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
audit: {
|
|
127
|
-
eventId: "Event ID",
|
|
128
|
-
timestamp: "Timestamp",
|
|
129
|
-
userEmail: "User Email",
|
|
130
|
-
userId: "User ID",
|
|
131
|
-
role: "Role",
|
|
132
|
-
resource: "Resource",
|
|
133
|
-
resourceId: "Resource ID",
|
|
134
|
-
ipAddress: "IP Address",
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
collection: {
|
|
138
|
-
pages: "Pages",
|
|
139
|
-
posts: "Posts",
|
|
140
|
-
categories: "Categories",
|
|
141
|
-
media: "Media",
|
|
142
|
-
settings: "Settings",
|
|
143
|
-
navigation: "Navigation",
|
|
144
|
-
users: "Users",
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
item: {
|
|
148
|
-
item: "Item",
|
|
149
|
-
itemCount: "Item {n}",
|
|
150
|
-
items: "Items",
|
|
151
|
-
noItems: 'No items. Click "Add Item" to create one.',
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
empty: {
|
|
155
|
-
noResults: "No results found",
|
|
156
|
-
loading: "Loading...",
|
|
157
|
-
noData: "No data available",
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
validation: {
|
|
161
|
-
required: "This field is required",
|
|
162
|
-
email: "Please enter a valid email address",
|
|
163
|
-
url: "Please enter a valid URL",
|
|
164
|
-
minLength: "Minimum {min} characters required",
|
|
165
|
-
maxLength: "Maximum {max} characters allowed",
|
|
166
|
-
number: "Please enter a valid number",
|
|
167
|
-
integer: "Please enter a whole number",
|
|
168
|
-
range: "Please enter a value between {min} and {max}",
|
|
169
|
-
json: "Please enter valid JSON",
|
|
170
|
-
hexColor: "Please enter a valid hex color",
|
|
171
|
-
phone: "Please enter a valid phone number",
|
|
172
|
-
postalCode: "Please enter a valid postal code",
|
|
173
|
-
matches: "Values do not match",
|
|
174
|
-
pattern: "Invalid format",
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
errors: {
|
|
178
|
-
generic: "Something went wrong. Please try again.",
|
|
179
|
-
network: "Network error. Please check your connection.",
|
|
180
|
-
unauthorized: "You are not authorized to perform this action.",
|
|
181
|
-
notFound: "The requested resource was not found.",
|
|
182
|
-
serverError: "Server error. Please try again later.",
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
actions: {
|
|
186
|
-
save: "Save",
|
|
187
|
-
cancel: "Cancel",
|
|
188
|
-
delete: "Delete",
|
|
189
|
-
edit: "Edit",
|
|
190
|
-
create: "Create",
|
|
191
|
-
add: "Add",
|
|
192
|
-
update: "Update",
|
|
193
|
-
confirm: "Confirm",
|
|
194
|
-
clear: "Clear",
|
|
195
|
-
close: "Close",
|
|
196
|
-
back: "Back",
|
|
197
|
-
next: "Next",
|
|
198
|
-
submit: "Submit",
|
|
199
|
-
remove: "Remove",
|
|
200
|
-
search: "Search",
|
|
201
|
-
filter: "Filter",
|
|
202
|
-
sort: "Sort",
|
|
203
|
-
refresh: "Refresh",
|
|
204
|
-
reload: "Reload",
|
|
205
|
-
export: "Export",
|
|
206
|
-
import: "Import",
|
|
207
|
-
upload: "Upload",
|
|
208
|
-
download: "Download",
|
|
209
|
-
copy: "Copy",
|
|
210
|
-
select: "Select",
|
|
211
|
-
selectAll: "Select All",
|
|
212
|
-
deselectAll: "Deselect All",
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
pagination: {
|
|
216
|
-
page: "Page",
|
|
217
|
-
of: "of",
|
|
218
|
-
first: "First",
|
|
219
|
-
last: "Last",
|
|
220
|
-
previous: "Previous",
|
|
221
|
-
next: "Next",
|
|
222
|
-
showing: "Showing",
|
|
223
|
-
to: "to",
|
|
224
|
-
from: "from",
|
|
225
|
-
results: "results",
|
|
226
|
-
perPage: "Per page",
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
filters: {
|
|
230
|
-
all: "All",
|
|
231
|
-
active: "Active",
|
|
232
|
-
inactive: "Inactive",
|
|
233
|
-
published: "Published",
|
|
234
|
-
draft: "Draft",
|
|
235
|
-
archived: "Archived",
|
|
236
|
-
search: "Search",
|
|
237
|
-
dateRange: "Date Range",
|
|
238
|
-
clearFilters: "Clear Filters",
|
|
239
|
-
applyFilters: "Apply Filters",
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
sort: {
|
|
243
|
-
ascending: "Ascending",
|
|
244
|
-
descending: "Descending",
|
|
245
|
-
sortBy: "Sort by",
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
table: {
|
|
249
|
-
noColumns: "No columns selected",
|
|
250
|
-
toggleColumns: "Toggle Columns",
|
|
251
|
-
rowsPerPage: "Rows per page",
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
blocks: {
|
|
255
|
-
addBlock: "Add Block",
|
|
256
|
-
removeBlock: "Remove Block",
|
|
257
|
-
moveUp: "Move Up",
|
|
258
|
-
moveDown: "Move Down",
|
|
259
|
-
},
|
|
260
|
-
|
|
261
|
-
richText: {
|
|
262
|
-
bold: "Bold",
|
|
263
|
-
italic: "Italic",
|
|
264
|
-
underline: "Underline",
|
|
265
|
-
strikethrough: "Strikethrough",
|
|
266
|
-
bulletList: "Bullet List",
|
|
267
|
-
numberedList: "Numbered List",
|
|
268
|
-
heading: "Heading",
|
|
269
|
-
link: "Link",
|
|
270
|
-
code: "Code",
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
upload: {
|
|
274
|
-
dragDrop: "Drag and drop files here",
|
|
275
|
-
orBrowse: "or browse",
|
|
276
|
-
uploading: "Uploading...",
|
|
277
|
-
uploadComplete: "Upload complete",
|
|
278
|
-
uploadFailed: "Upload failed",
|
|
279
|
-
fileTooLarge: "File is too large",
|
|
280
|
-
invalidType: "Invalid file type",
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
version: {
|
|
284
|
-
versionHistory: "Version History",
|
|
285
|
-
currentVersion: "Current Version",
|
|
286
|
-
restoreVersion: "Restore Version",
|
|
287
|
-
previewVersion: "Preview Version",
|
|
288
|
-
compareVersion: "Compare Versions",
|
|
289
|
-
autoSaved: "Auto-saved",
|
|
290
|
-
manuallySaved: "Manually saved",
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
type Translations = typeof en;
|
|
295
|
-
|
|
296
|
-
interface I18nState {
|
|
297
|
-
locale: string;
|
|
298
|
-
translations: Translations;
|
|
299
|
-
setLocale: (locale: string) => void;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export const useI18n = create<I18nState>((set) => ({
|
|
303
|
-
locale: "en",
|
|
304
|
-
translations: en,
|
|
305
|
-
setLocale: (locale) => set({ locale }),
|
|
306
|
-
}));
|
|
307
|
-
|
|
308
|
-
export function getT(
|
|
309
|
-
key: string,
|
|
310
|
-
replacements?: Record<string, string>,
|
|
311
|
-
): string {
|
|
312
|
-
const { translations } = useI18n.getState();
|
|
313
|
-
const keys = key.split(".");
|
|
314
|
-
let result: any = translations;
|
|
315
|
-
|
|
316
|
-
for (const k of keys) {
|
|
317
|
-
result = result?.[k];
|
|
318
|
-
if (result === undefined) return key;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (typeof result !== "string") return key;
|
|
322
|
-
|
|
323
|
-
if (replacements) {
|
|
324
|
-
return Object.entries(replacements).reduce(
|
|
325
|
-
(str, [k, v]) => str.replace(new RegExp(`\\{${k}}`, "g"), v),
|
|
326
|
-
result,
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export function useTranslation() {
|
|
334
|
-
const translations = useI18n((state) => state.translations);
|
|
335
|
-
return {
|
|
336
|
-
t: (key: string, replacements?: Record<string, string>) => {
|
|
337
|
-
const keys = key.split(".");
|
|
338
|
-
let result: any = translations;
|
|
339
|
-
for (const k of keys) {
|
|
340
|
-
result = result?.[k];
|
|
341
|
-
if (result === undefined) return key;
|
|
342
|
-
}
|
|
343
|
-
if (typeof result !== "string") return key;
|
|
344
|
-
if (replacements) {
|
|
345
|
-
return Object.entries(replacements).reduce(
|
|
346
|
-
(str, [k, v]) => str.replace(new RegExp(`\\{${k}}`, "g"), v),
|
|
347
|
-
result,
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
return result;
|
|
351
|
-
},
|
|
352
|
-
};
|
|
353
|
-
}
|
package/src/lib/validation.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
export function isEmail(value: string): boolean {
|
|
2
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3
|
-
return emailRegex.test(value);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function isUrl(value: string): boolean {
|
|
7
|
-
try {
|
|
8
|
-
new URL(value);
|
|
9
|
-
return true;
|
|
10
|
-
} catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function isSlug(value: string): boolean {
|
|
16
|
-
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
17
|
-
return slugRegex.test(value);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function isRequired(value: any): boolean {
|
|
21
|
-
if (value === null || value === undefined) return false;
|
|
22
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
23
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function minLength(value: string, min: number): boolean {
|
|
28
|
-
return typeof value === "string" && value.length >= min;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function maxLength(value: string, max: number): boolean {
|
|
32
|
-
return typeof value === "string" && value.length <= max;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function isNumber(value: any): boolean {
|
|
36
|
-
return !isNaN(parseFloat(value)) && isFinite(value);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function isInteger(value: any): boolean {
|
|
40
|
-
return Number.isInteger(Number(value));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function inRange(value: number, min: number, max: number): boolean {
|
|
44
|
-
const num = Number(value);
|
|
45
|
-
return num >= min && num <= max;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function isJson(value: string): boolean {
|
|
49
|
-
try {
|
|
50
|
-
JSON.parse(value);
|
|
51
|
-
return true;
|
|
52
|
-
} catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function isHexColor(value: string): boolean {
|
|
58
|
-
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
59
|
-
return hexRegex.test(value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function isAlpha(value: string): boolean {
|
|
63
|
-
const alphaRegex = /^[a-zA-Z]+$/;
|
|
64
|
-
return alphaRegex.test(value);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function isAlphaNumeric(value: string): boolean {
|
|
68
|
-
const alphaNumRegex = /^[a-zA-Z0-9]+$/;
|
|
69
|
-
return alphaNumRegex.test(value);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function isPhone(value: string): boolean {
|
|
73
|
-
const phoneRegex = /^[\d\s\-+()]+$/;
|
|
74
|
-
return phoneRegex.test(value) && value.replace(/\D/g, "").length >= 10;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function isPostalCode(value: string, country: string = "US"): boolean {
|
|
78
|
-
const codes: Record<string, RegExp> = {
|
|
79
|
-
US: /^\d{5}(-\d{4})?$/,
|
|
80
|
-
UK: /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i,
|
|
81
|
-
CA: /^[A-Z]\d[A-Z] ?\d[A-Z]\d$/i,
|
|
82
|
-
};
|
|
83
|
-
return codes[country]?.test(value) || false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function matches(value: string, other: string): boolean {
|
|
87
|
-
return value === other;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function notMatches(value: string, other: string): boolean {
|
|
91
|
-
return value !== other;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface ValidationRule {
|
|
95
|
-
validate: (value: any, ...args: any[]) => boolean;
|
|
96
|
-
message: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function required(message = "This field is required"): ValidationRule {
|
|
100
|
-
return {
|
|
101
|
-
validate: isRequired,
|
|
102
|
-
message,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function email(message = "Invalid email address"): ValidationRule {
|
|
107
|
-
return {
|
|
108
|
-
validate: isEmail,
|
|
109
|
-
message,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function url(message = "Invalid URL"): ValidationRule {
|
|
114
|
-
return {
|
|
115
|
-
validate: isUrl,
|
|
116
|
-
message,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function minLengthRule(min: number, message?: string): ValidationRule {
|
|
121
|
-
return {
|
|
122
|
-
validate: (value: string) => minLength(value, min),
|
|
123
|
-
message: message || `Minimum ${min} characters required`,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function maxLengthRule(max: number, message?: string): ValidationRule {
|
|
128
|
-
return {
|
|
129
|
-
validate: (value: string) => maxLength(value, max),
|
|
130
|
-
message: message || `Maximum ${max} characters allowed`,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function pattern(
|
|
135
|
-
regex: RegExp,
|
|
136
|
-
message = "Invalid format",
|
|
137
|
-
): ValidationRule {
|
|
138
|
-
return {
|
|
139
|
-
validate: (value: string) => regex.test(value || ""),
|
|
140
|
-
message,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function matchesRule(
|
|
145
|
-
otherField: string,
|
|
146
|
-
message = "Values do not match",
|
|
147
|
-
): ValidationRule {
|
|
148
|
-
return {
|
|
149
|
-
validate: (value: string, data: Record<string, any>) =>
|
|
150
|
-
matches(value, data[otherField]),
|
|
151
|
-
message,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function notMatchesRule(
|
|
156
|
-
otherField: string,
|
|
157
|
-
message = "Value must be different",
|
|
158
|
-
): ValidationRule {
|
|
159
|
-
return {
|
|
160
|
-
validate: (value: string, data: Record<string, any>) =>
|
|
161
|
-
notMatches(value, data[otherField]),
|
|
162
|
-
message,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function phone(message = "Invalid phone number"): ValidationRule {
|
|
167
|
-
return {
|
|
168
|
-
validate: isPhone,
|
|
169
|
-
message,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function postalCodeRule(
|
|
174
|
-
country: string = "US",
|
|
175
|
-
message?: string,
|
|
176
|
-
): ValidationRule {
|
|
177
|
-
return {
|
|
178
|
-
validate: (value: string) => isPostalCode(value, country),
|
|
179
|
-
message: message || `Invalid postal code for ${country}`,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function numberRule(message = "Must be a valid number"): ValidationRule {
|
|
184
|
-
return {
|
|
185
|
-
validate: isNumber,
|
|
186
|
-
message,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function integerRule(
|
|
191
|
-
message = "Must be a whole number",
|
|
192
|
-
): ValidationRule {
|
|
193
|
-
return {
|
|
194
|
-
validate: isInteger,
|
|
195
|
-
message,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function rangeRule(
|
|
200
|
-
min: number,
|
|
201
|
-
max: number,
|
|
202
|
-
message?: string,
|
|
203
|
-
): ValidationRule {
|
|
204
|
-
return {
|
|
205
|
-
validate: (value: number) => inRange(Number(value), min, max),
|
|
206
|
-
message: message || `Must be between ${min} and ${max}`,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function jsonRule(message = "Must be valid JSON"): ValidationRule {
|
|
211
|
-
return {
|
|
212
|
-
validate: isJson,
|
|
213
|
-
message,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export function hexColorRule(
|
|
218
|
-
message = "Must be a valid hex color",
|
|
219
|
-
): ValidationRule {
|
|
220
|
-
return {
|
|
221
|
-
validate: isHexColor,
|
|
222
|
-
message,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export interface ValidationResult {
|
|
227
|
-
valid: boolean;
|
|
228
|
-
errors: Record<string, string>;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function validate(
|
|
232
|
-
data: Record<string, any>,
|
|
233
|
-
rules: Record<string, ValidationRule[]>,
|
|
234
|
-
): ValidationResult {
|
|
235
|
-
const errors: Record<string, string> = {};
|
|
236
|
-
|
|
237
|
-
for (const [field, fieldRules] of Object.entries(rules)) {
|
|
238
|
-
for (const rule of fieldRules) {
|
|
239
|
-
if (!rule.validate(data[field], data)) {
|
|
240
|
-
errors[field] = rule.message;
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
valid: Object.keys(errors).length === 0,
|
|
248
|
-
errors,
|
|
249
|
-
};
|
|
250
|
-
}
|