@kyro-cms/admin 0.2.10 → 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.
Files changed (67) hide show
  1. package/README.md +46 -272
  2. package/package.json +32 -7
  3. package/src/blocks/examples/sample-block-2.tsx +27 -0
  4. package/src/blocks/examples/sample-block.tsx +26 -0
  5. package/src/blocks/index.ts +14 -0
  6. package/src/blocks/registry.ts +38 -0
  7. package/src/blocks/types.ts +23 -0
  8. package/src/components/Admin.tsx +1 -1
  9. package/src/components/ApiKeysManager.tsx +1 -1
  10. package/src/components/AuditLogsPage.tsx +1 -1
  11. package/src/components/AutoForm.tsx +2 -2
  12. package/src/components/BrandingHub.tsx +1 -1
  13. package/src/components/CreateView.tsx +1 -1
  14. package/src/components/DetailView.tsx +1 -1
  15. package/src/components/DeveloperCenter.tsx +1 -1
  16. package/src/components/EnhancedListView.tsx +1 -1
  17. package/src/components/ListView.tsx +1 -1
  18. package/src/components/LoginPage.tsx +1 -1
  19. package/src/components/MediaGallery.tsx +1 -1
  20. package/src/components/UserManagement.tsx +1 -1
  21. package/src/components/WebhookManager.tsx +2 -2
  22. package/src/components/fields/RelationshipBlockField.tsx +1 -1
  23. package/src/components/fields/RelationshipField.tsx +1 -1
  24. package/src/components/fields/UploadField.tsx +1 -6
  25. package/src/components/ui/CommandPalette.tsx +1 -1
  26. package/src/fields/examples/sample-field-2.tsx +30 -0
  27. package/src/fields/examples/sample-field.tsx +30 -0
  28. package/src/fields/index.ts +33 -0
  29. package/src/fields/registry.tsx +46 -0
  30. package/src/fields/types.ts +24 -0
  31. package/src/hooks/data.ts +116 -0
  32. package/src/hooks/examples/sample-hook-2.ts +13 -0
  33. package/src/hooks/examples/sample-hook.ts +12 -0
  34. package/src/hooks/index.ts +19 -0
  35. package/src/hooks/lifecycle.ts +81 -0
  36. package/src/hooks/types.ts +40 -0
  37. package/src/index.ts +78 -0
  38. package/src/integration.ts +52 -0
  39. package/src/pages/api/[collection]/[id]/publish.ts +2 -2
  40. package/src/pages/api/[collection]/[id]/unpublish.ts +2 -2
  41. package/src/pages/api/[collection]/[id]/versions.ts +1 -1
  42. package/src/pages/api/[collection]/[id].ts +2 -2
  43. package/src/pages/api/[collection]/index.ts +2 -2
  44. package/src/pages/api/collections.ts +1 -1
  45. package/src/pages/api/globals/[slug].ts +2 -2
  46. package/src/pages/api/graphql.ts +3 -3
  47. package/src/pages/api/media/folders.ts +1 -1
  48. package/src/pages/api/media/index.ts +1 -1
  49. package/src/pages/api/media/resize.ts +1 -1
  50. package/src/pages/api/slug-availability.ts +2 -2
  51. package/src/pages/api/storage-config.ts +1 -1
  52. package/src/pages/api/storage-status.ts +1 -1
  53. package/src/pages/api/upload.ts +1 -1
  54. package/src/plugins/examples/sample-plugin-2.ts +21 -0
  55. package/src/plugins/examples/sample-plugin.ts +21 -0
  56. package/src/plugins/index.ts +10 -0
  57. package/src/plugins/registry.ts +36 -0
  58. package/src/plugins/types.ts +22 -0
  59. package/src/theme/ThemeProvider.tsx +238 -0
  60. package/src/theme/index.ts +20 -0
  61. package/src/theme/tokens.ts +222 -0
  62. package/src/components/Modal.tsx +0 -206
  63. package/src/components/index.ts +0 -29
  64. package/src/env.ts +0 -20
  65. package/src/lib/i18n.tsx +0 -353
  66. package/src/lib/validation.ts +0 -250
  67. package/src/pages/api/globals/[slug]/test.ts +0 -171
@@ -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
- }
@@ -1,171 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { dataStore } from "../../../../../lib/dataStore";
3
- import nodemailer, { createTransport } from "nodemailer";
4
-
5
- export const POST: APIRoute = async ({ params, request }) => {
6
- const slug = params.slug as string;
7
-
8
- let body;
9
- try {
10
- body = await request.json();
11
- } catch (e) {
12
- body = {};
13
- }
14
-
15
- try {
16
- const globalConfig = await dataStore.findGlobal(slug);
17
-
18
- const testEmail = body?.email || globalConfig?.testEmail;
19
-
20
- if (!testEmail) {
21
- return new Response(
22
- JSON.stringify({ error: "Please enter a test email address" }),
23
- { status: 400, headers: { "Content-Type": "application/json" } },
24
- );
25
- }
26
-
27
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
28
- if (!emailRegex.test(testEmail)) {
29
- return new Response(
30
- JSON.stringify({ error: "Invalid email address format" }),
31
- { status: 400, headers: { "Content-Type": "application/json" } },
32
- );
33
- }
34
-
35
- const emailSettings = globalConfig;
36
- const provider = emailSettings?.provider;
37
-
38
- if (!provider) {
39
- return new Response(
40
- JSON.stringify({
41
- error:
42
- "No email provider configured. Please configure your email settings first.",
43
- }),
44
- { status: 400, headers: { "Content-Type": "application/json" } },
45
- );
46
- }
47
-
48
- let transporter;
49
-
50
- const transporterOptions = {
51
- timeout: 10000, // 10 second timeout
52
- };
53
-
54
- if (provider === "smtp" && emailSettings?.smtp) {
55
- transporter = createTransport({
56
- ...transporterOptions,
57
- host: emailSettings.smtp.host,
58
- port: parseInt(emailSettings.smtp.port) || 587,
59
- secure: emailSettings.smtp.secure || false,
60
- auth: {
61
- user: emailSettings.smtp.username,
62
- pass: emailSettings.smtp.password,
63
- },
64
- });
65
- } else if (provider === "ses" && emailSettings?.ses) {
66
- transporter = createTransport({
67
- ...transporterOptions,
68
- host: `email-smtp.${emailSettings.ses.region}.amazonaws.com`,
69
- port: 587,
70
- secure: false,
71
- auth: {
72
- user: emailSettings.ses.accessKeyId,
73
- pass: emailSettings.ses.secretAccessKey,
74
- },
75
- });
76
- } else if (provider === "resend" && emailSettings?.resend?.apiKey) {
77
- transporter = createTransport({
78
- ...transporterOptions,
79
- host: "smtp.resend.com",
80
- port: 587,
81
- secure: false,
82
- auth: {
83
- user: "resend",
84
- pass: emailSettings.resend.apiKey,
85
- },
86
- });
87
- } else if (provider === "sendgrid" && emailSettings?.sendgrid?.apiKey) {
88
- transporter = createTransport({
89
- ...transporterOptions,
90
- host: "smtp.sendgrid.net",
91
- port: 587,
92
- secure: false,
93
- auth: {
94
- user: "apikey",
95
- pass: emailSettings.sendgrid.apiKey,
96
- },
97
- });
98
- } else {
99
- return new Response(
100
- JSON.stringify({
101
- error: `Provider ${provider} not supported or not configured`,
102
- }),
103
- { status: 400, headers: { "Content-Type": "application/json" } },
104
- );
105
- }
106
-
107
- const fromEmail =
108
- emailSettings?.fromEmail || emailSettings?.from || "noreply@example.com";
109
- const fromName = emailSettings?.fromName || "Kyro CMS";
110
-
111
- const info = await transporter.sendMail({
112
- from: `"${fromName}" <${fromEmail}>`,
113
- to: testEmail,
114
- subject: "Test Email - Kyro CMS",
115
- html: `
116
- <!DOCTYPE html>
117
- <html>
118
- <head>
119
- <meta charset="utf-8">
120
- <meta name="viewport" content="width=device-width, initial-scale=1">
121
- <title>Test Email</title>
122
- </head>
123
- <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333;">
124
- <div style="max-width:600px; margin:0 auto; padding:20px;">
125
- <h1 style="color:#0b1222;">Test Email Successful!</h1>
126
- <p>This is a test email from <strong>Kyro CMS</strong>.</p>
127
- <p>If you received this email, your email settings are configured correctly.</p>
128
- <hr style="border:none; border-top:1px solid #e2e8f0; margin:20px 0;">
129
- <p style="font-size:12px; color:#666;">
130
- Sent to: ${testEmail}<br>
131
- Provider: ${provider}
132
- </p>
133
- </div>
134
- </body>
135
- </html>
136
- `,
137
- text: `Test Email Successful!\n\nThis is a test email from Kyro CMS.\nIf you received this email, your email settings are configured correctly.\n\nSent to: ${testEmail}\nProvider: ${provider}`,
138
- });
139
-
140
- console.log(info);
141
- return new Response(
142
- JSON.stringify({
143
- success: true,
144
- message: `Test email sent to ${testEmail}`,
145
- messageId: info.messageId,
146
- }),
147
- { status: 200, headers: { "Content-Type": "application/json" } },
148
- );
149
- } catch (error: any) {
150
- console.log(error);
151
- let errorMessage = "Failed to send test email";
152
-
153
- if (error.code === "ECONNREFUSED") {
154
- errorMessage = "Connection refused. Check your SMTP host and port.";
155
- } else if (error.code === "ETIMEDOUT") {
156
- errorMessage = "Connection timed out. Check your SMTP host and port.";
157
- } else if (error.code === "ENOTFOUND") {
158
- errorMessage = "Host not found. Check your SMTP hostname.";
159
- } else if (error.code === "EAUTH") {
160
- errorMessage = "Authentication failed. Check your username/password.";
161
- } else if (error.message) {
162
- errorMessage = error.message;
163
- }
164
-
165
- console.error("[test-email] Error:", errorMessage, error.stack);
166
- return new Response(JSON.stringify({ error: errorMessage }), {
167
- status: 500,
168
- headers: { "Content-Type": "application/json" },
169
- });
170
- }
171
- };