@vocoweb/kernel 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/dist/react.mjs ADDED
@@ -0,0 +1,1845 @@
1
+ import { forwardRef, useState, useCallback, useEffect } from 'react';
2
+ import { createClient } from '@supabase/supabase-js';
3
+ import { z } from 'zod';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/auth/components/VocoAuth.tsx
7
+
8
+ // src/shared/errors.ts
9
+ var VocoError = class extends Error {
10
+ constructor(message, code, statusCode = 500, details) {
11
+ super(message);
12
+ this.code = code;
13
+ this.statusCode = statusCode;
14
+ this.details = details;
15
+ this.name = "VocoError";
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ toJSON() {
19
+ return {
20
+ name: this.name,
21
+ message: this.message,
22
+ code: this.code,
23
+ statusCode: this.statusCode,
24
+ details: this.details
25
+ };
26
+ }
27
+ };
28
+ var VocoAuthError = class _VocoAuthError extends VocoError {
29
+ constructor(message, code = "AUTH_ERROR", details) {
30
+ super(message, code, 401, details);
31
+ this.name = "VocoAuthError";
32
+ }
33
+ static invalidCredentials() {
34
+ return new _VocoAuthError("Invalid email or password", "INVALID_CREDENTIALS");
35
+ }
36
+ static sessionExpired() {
37
+ return new _VocoAuthError("Session has expired", "SESSION_EXPIRED");
38
+ }
39
+ static unauthorized(message = "Unauthorized access") {
40
+ return new _VocoAuthError(message, "UNAUTHORIZED");
41
+ }
42
+ static forbidden(message = "Access forbidden") {
43
+ const error = new _VocoAuthError(message, "FORBIDDEN");
44
+ error.statusCode = 403;
45
+ return error;
46
+ }
47
+ };
48
+ var VocoConfigError = class _VocoConfigError extends VocoError {
49
+ constructor(message, code = "CONFIG_ERROR", details) {
50
+ super(message, code, 500, details);
51
+ this.name = "VocoConfigError";
52
+ }
53
+ static missingEnvVar(varName) {
54
+ return new _VocoConfigError(
55
+ `Missing required environment variable: ${varName}`,
56
+ "MISSING_ENV_VAR",
57
+ { varName }
58
+ );
59
+ }
60
+ static invalidConfig(message) {
61
+ return new _VocoConfigError(`Invalid configuration: ${message}`, "INVALID_CONFIG");
62
+ }
63
+ };
64
+
65
+ // src/config/env.ts
66
+ var SupabaseConfigSchema = z.object({
67
+ url: z.string().url("SUPABASE_URL must be a valid URL"),
68
+ anonKey: z.string().min(1, "SUPABASE_ANON_KEY is required"),
69
+ serviceRoleKey: z.string().min(1, "SUPABASE_SERVICE_ROLE_KEY is required")
70
+ });
71
+ var StripeConfigSchema = z.object({
72
+ secretKey: z.string().startsWith("sk_", "STRIPE_SECRET_KEY must start with sk_"),
73
+ publishableKey: z.string().startsWith("pk_", "STRIPE_PUBLISHABLE_KEY must start with pk_"),
74
+ webhookSecret: z.string().optional()
75
+ });
76
+ var AppConfigSchema = z.object({
77
+ url: z.string().url("NEXT_PUBLIC_APP_URL must be a valid URL").optional().default("http://localhost:3000"),
78
+ name: z.string().optional().default("VocoWeb App"),
79
+ supportEmail: z.string().email("SUPPORT_EMAIL must be valid").optional().default("support@vocoweb.in"),
80
+ legalEmail: z.string().email("LEGAL_EMAIL must be valid").optional().default("legal@vocoweb.in")
81
+ });
82
+ var ResidencyConfigSchema = z.object({
83
+ enabled: z.boolean().optional().default(false),
84
+ region: z.enum(["eu", "us", "apac", "global"]).optional().default("global"),
85
+ strictMode: z.boolean().optional().default(false)
86
+ });
87
+ var AccessibilityConfigSchema = z.object({
88
+ enforceContrast: z.boolean().optional().default(true),
89
+ enforceAriaLabels: z.boolean().optional().default(true),
90
+ enforceKeyboardNav: z.boolean().optional().default(true),
91
+ contrastRatio: z.number().min(1).max(21).optional().default(4.5)
92
+ });
93
+ var KernelConfigSchema = z.object({
94
+ supabase: SupabaseConfigSchema,
95
+ stripe: StripeConfigSchema,
96
+ app: AppConfigSchema,
97
+ residency: ResidencyConfigSchema,
98
+ accessibility: AccessibilityConfigSchema
99
+ });
100
+ function getEnvVar(key) {
101
+ if (typeof window !== "undefined") {
102
+ return window?.env?.[key] || import.meta?.env?.[key] || "";
103
+ }
104
+ return process.env[key] || "";
105
+ }
106
+ function validateConfig(config) {
107
+ try {
108
+ return KernelConfigSchema.parse(config);
109
+ } catch (error) {
110
+ if (error instanceof z.ZodError) {
111
+ const errors = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
112
+ throw new VocoConfigError(`Configuration validation failed: ${errors}`, "CONFIG_VALIDATION_FAILED", {
113
+ errors: error.errors
114
+ });
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+ function loadConfigFromEnv() {
120
+ const rawConfig = {
121
+ supabase: {
122
+ url: getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || getEnvVar("SUPABASE_URL"),
123
+ anonKey: getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || getEnvVar("SUPABASE_ANON_KEY"),
124
+ serviceRoleKey: getEnvVar("SUPABASE_SERVICE_ROLE_KEY")
125
+ },
126
+ stripe: {
127
+ secretKey: getEnvVar("STRIPE_SECRET_KEY"),
128
+ publishableKey: getEnvVar("NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY") || getEnvVar("STRIPE_PUBLISHABLE_KEY"),
129
+ webhookSecret: getEnvVar("STRIPE_WEBHOOK_SECRET") || void 0
130
+ },
131
+ app: {
132
+ url: getEnvVar("NEXT_PUBLIC_APP_URL") || void 0,
133
+ name: getEnvVar("NEXT_PUBLIC_APP_NAME") || void 0,
134
+ supportEmail: getEnvVar("SUPPORT_EMAIL") || void 0,
135
+ legalEmail: getEnvVar("LEGAL_EMAIL") || void 0
136
+ },
137
+ residency: {
138
+ enabled: getEnvVar("VOCO_DATA_RESIDENCY_ENABLED") === "true",
139
+ region: getEnvVar("VOCO_DATA_REGION") || void 0,
140
+ strictMode: getEnvVar("VOCO_RESIDENCY_STRICT_MODE") === "true"
141
+ },
142
+ accessibility: {
143
+ enforceContrast: getEnvVar("VOCO_ENFORCE_CONTRAST") !== "false",
144
+ enforceAriaLabels: getEnvVar("VOCO_ENFORCE_ARIA") !== "false",
145
+ enforceKeyboardNav: getEnvVar("VOCO_ENFORCE_KEYBOARD") !== "false",
146
+ contrastRatio: getEnvVar("VOCO_CONTRAST_RATIO") ? parseFloat(getEnvVar("VOCO_CONTRAST_RATIO")) : void 0
147
+ }
148
+ };
149
+ return validateConfig(rawConfig);
150
+ }
151
+ var cachedConfig = null;
152
+ function getConfig() {
153
+ if (!cachedConfig) {
154
+ cachedConfig = loadConfigFromEnv();
155
+ }
156
+ return cachedConfig;
157
+ }
158
+ function getSupabaseConfig() {
159
+ return getConfig().supabase;
160
+ }
161
+
162
+ // src/auth/config.ts
163
+ var browserClient = null;
164
+ function getBrowserClient() {
165
+ if (browserClient) {
166
+ return browserClient;
167
+ }
168
+ if (typeof window === "undefined") {
169
+ throw new VocoConfigError(
170
+ "Browser client can only be used in browser environment",
171
+ "INVALID_CLIENT_TYPE"
172
+ );
173
+ }
174
+ const config = getSupabaseConfig();
175
+ browserClient = createClient(config.url, config.anonKey, {
176
+ auth: {
177
+ persistSession: true,
178
+ autoRefreshToken: true,
179
+ detectSessionInUrl: true,
180
+ storage: typeof window !== "undefined" ? window.localStorage : void 0,
181
+ storageKey: "vocoweb-auth-token",
182
+ flowType: "pkce"
183
+ }
184
+ });
185
+ return browserClient;
186
+ }
187
+
188
+ // src/auth/client.ts
189
+ function handleAuthError(error) {
190
+ if (!error) {
191
+ throw new VocoAuthError("Unknown authentication error", "UNKNOWN_ERROR");
192
+ }
193
+ const errorMap = {
194
+ InvalidCredentials: "INVALID_CREDENTIALS",
195
+ EmailNotConfirmed: "EMAIL_NOT_CONFIRMED",
196
+ UserNotFound: "USER_NOT_FOUND",
197
+ InvalidEmail: "INVALID_EMAIL",
198
+ WeakPassword: "WEAK_PASSWORD",
199
+ EmailAlreadyExists: "EMAIL_ALREADY_EXISTS",
200
+ SessionExpired: "SESSION_EXPIRED"
201
+ };
202
+ const code = errorMap[error.name] || "AUTH_ERROR";
203
+ throw new VocoAuthError(error.message || "Authentication failed", code, {
204
+ originalError: error.name
205
+ });
206
+ }
207
+ async function loginWithOAuth(options = {}) {
208
+ const {
209
+ provider = "google",
210
+ redirectTo,
211
+ scopes,
212
+ queryParams,
213
+ skipBrowserRedirect = false
214
+ } = options;
215
+ const supabase = getBrowserClient();
216
+ let redirectUrl = redirectTo;
217
+ if (!redirectUrl && typeof window !== "undefined") {
218
+ redirectUrl = `${window.location.origin}/auth/callback`;
219
+ }
220
+ const { error } = await supabase.auth.signInWithOAuth({
221
+ provider,
222
+ options: {
223
+ redirectTo: redirectUrl,
224
+ scopes,
225
+ queryParams,
226
+ skipBrowserRedirect
227
+ }
228
+ });
229
+ if (error) {
230
+ handleAuthError(error);
231
+ }
232
+ }
233
+ async function loginWithGoogle(redirectTo) {
234
+ await loginWithOAuth({ provider: "google", redirectTo });
235
+ }
236
+ async function loginWithGitHub(redirectTo) {
237
+ await loginWithOAuth({ provider: "github", redirectTo });
238
+ }
239
+ async function signInWithPassword(options) {
240
+ const supabase = getBrowserClient();
241
+ const { data, error } = await supabase.auth.signInWithPassword({
242
+ email: options.email,
243
+ password: options.password
244
+ });
245
+ if (error) {
246
+ handleAuthError(error);
247
+ }
248
+ return { data };
249
+ }
250
+ async function signUp(options) {
251
+ const supabase = getBrowserClient();
252
+ const { data, error } = await supabase.auth.signUp({
253
+ email: options.email,
254
+ password: options.password,
255
+ options: {
256
+ data: options.metadata
257
+ }
258
+ });
259
+ if (error) {
260
+ handleAuthError(error);
261
+ }
262
+ return { data };
263
+ }
264
+ function VocoAuth({
265
+ redirectUrl,
266
+ className,
267
+ mode = "both",
268
+ providers = ["google"],
269
+ showSignUp = false,
270
+ onSuccess,
271
+ onError
272
+ }) {
273
+ const [isLoading, setIsLoading] = useState(false);
274
+ const [error, setError] = useState(null);
275
+ const [authMode, setAuthMode] = useState("signIn");
276
+ const handleOAuthLogin = useCallback(
277
+ async (provider) => {
278
+ setIsLoading(true);
279
+ setError(null);
280
+ try {
281
+ if (provider === "google") {
282
+ await loginWithGoogle(redirectUrl);
283
+ } else {
284
+ await loginWithGitHub(redirectUrl);
285
+ }
286
+ } catch (err) {
287
+ const errorMessage = err instanceof Error ? err.message : "Authentication failed";
288
+ setError(errorMessage);
289
+ setIsLoading(false);
290
+ onError?.({ message: errorMessage, code: "OAUTH_ERROR" });
291
+ }
292
+ },
293
+ [redirectUrl, onError]
294
+ );
295
+ const handlePasswordAuth = useCallback(
296
+ async (e) => {
297
+ e.preventDefault();
298
+ setIsLoading(true);
299
+ setError(null);
300
+ const formData = new FormData(e.currentTarget);
301
+ const email = formData.get("email");
302
+ const password = formData.get("password");
303
+ try {
304
+ let result;
305
+ if (authMode === "signIn") {
306
+ result = await signInWithPassword({ email, password });
307
+ } else {
308
+ result = await signUp({
309
+ email,
310
+ password
311
+ });
312
+ }
313
+ if (result.data.user) {
314
+ onSuccess?.({
315
+ id: result.data.user.id,
316
+ email: result.data.user.email || ""
317
+ });
318
+ }
319
+ if (redirectUrl && typeof window !== "undefined") {
320
+ window.location.href = redirectUrl;
321
+ }
322
+ } catch (err) {
323
+ const errorMessage = err instanceof Error ? err.message : "Authentication failed";
324
+ setError(errorMessage);
325
+ setIsLoading(false);
326
+ onError?.({ message: errorMessage, code: "AUTH_ERROR" });
327
+ }
328
+ },
329
+ [authMode, redirectUrl, onSuccess, onError]
330
+ );
331
+ const toggleAuthMode = () => {
332
+ setAuthMode((prev) => prev === "signIn" ? "signUp" : "signIn");
333
+ setError(null);
334
+ };
335
+ return /* @__PURE__ */ jsx(
336
+ "div",
337
+ {
338
+ className,
339
+ style: {
340
+ display: "flex",
341
+ flexDirection: "column",
342
+ alignItems: "center",
343
+ justifyContent: "center",
344
+ minHeight: "400px",
345
+ padding: "2rem"
346
+ },
347
+ children: /* @__PURE__ */ jsxs(
348
+ "div",
349
+ {
350
+ style: {
351
+ width: "100%",
352
+ maxWidth: "400px",
353
+ backgroundColor: "white",
354
+ borderRadius: "12px",
355
+ padding: "2rem",
356
+ boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)"
357
+ },
358
+ children: [
359
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginBottom: "2rem" }, children: [
360
+ /* @__PURE__ */ jsx(
361
+ "h1",
362
+ {
363
+ style: {
364
+ fontSize: "1.5rem",
365
+ fontWeight: "700",
366
+ color: "#111827",
367
+ marginBottom: "0.5rem"
368
+ },
369
+ children: authMode === "signIn" ? "Sign in to your account" : "Create your account"
370
+ }
371
+ ),
372
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280" }, children: authMode === "signIn" ? "Welcome back!" : "Get started for free" })
373
+ ] }),
374
+ error && /* @__PURE__ */ jsx(
375
+ "div",
376
+ {
377
+ role: "alert",
378
+ style: {
379
+ padding: "0.75rem",
380
+ marginBottom: "1rem",
381
+ backgroundColor: "#fef2f2",
382
+ border: "1px solid #fecaca",
383
+ borderRadius: "0.5rem",
384
+ color: "#991b1b",
385
+ fontSize: "0.875rem"
386
+ },
387
+ children: error
388
+ }
389
+ ),
390
+ (mode === "oauth" || mode === "both") && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", marginBottom: "1.5rem" }, children: [
391
+ providers.includes("google") && /* @__PURE__ */ jsxs(
392
+ "button",
393
+ {
394
+ type: "button",
395
+ onClick: () => handleOAuthLogin("google"),
396
+ disabled: isLoading,
397
+ "aria-label": "Sign in with Google",
398
+ style: {
399
+ display: "flex",
400
+ alignItems: "center",
401
+ justifyContent: "center",
402
+ gap: "0.75rem",
403
+ width: "100%",
404
+ padding: "0.75rem 1rem",
405
+ backgroundColor: "white",
406
+ border: "1px solid #d1d5db",
407
+ borderRadius: "0.5rem",
408
+ fontSize: "0.875rem",
409
+ fontWeight: "500",
410
+ color: "#374151",
411
+ cursor: isLoading ? "not-allowed" : "pointer",
412
+ opacity: isLoading ? 0.5 : 1,
413
+ transition: "all 0.15s ease"
414
+ },
415
+ onMouseEnter: (e) => {
416
+ if (!isLoading) {
417
+ e.currentTarget.style.backgroundColor = "#f9fafb";
418
+ }
419
+ },
420
+ onMouseLeave: (e) => {
421
+ e.currentTarget.style.backgroundColor = "white";
422
+ },
423
+ children: [
424
+ /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", "aria-hidden": "true", children: [
425
+ /* @__PURE__ */ jsx(
426
+ "path",
427
+ {
428
+ d: "M19.6 10.23c0-.82-.1-1.42-.25-2.05H10v3.72h5.5c-.15.96-.74 2.31-2.04 3.22v2.45h3.16c1.89-1.73 2.98-4.3 2.98-7.34z",
429
+ fill: "#4285F4"
430
+ }
431
+ ),
432
+ /* @__PURE__ */ jsx(
433
+ "path",
434
+ {
435
+ d: "M10 20c2.7 0 4.96-.89 6.62-2.42l-3.16-2.45c-.86.58-1.97.92-3.46.92-2.65 0-4.92-1.8-5.73-4.22H2.98v2.52C4.56 17.64 7.06 20 10 20z",
436
+ fill: "#34A853"
437
+ }
438
+ ),
439
+ /* @__PURE__ */ jsx(
440
+ "path",
441
+ {
442
+ d: "M4.27 11.83c-.2-.58-.32-1.19-.32-1.83 0-.64.12-1.25.32-1.83V4.9H2.98C1.08 7.28 1.08 10.72 2.98 13.1l1.29-1.27z",
443
+ fill: "#FBBC05"
444
+ }
445
+ ),
446
+ /* @__PURE__ */ jsx(
447
+ "path",
448
+ {
449
+ d: "M10 3.98c1.52 0 2.87.52 3.94 1.54l2.9-2.9C15.06 1.19 12.7 0 10 0 7.06 0 4.56 2.36 3.38 5.9l1.29 1.27C5.38 4.98 7.65 3.98 10 3.98z",
450
+ fill: "#EA4335"
451
+ }
452
+ )
453
+ ] }),
454
+ "Continue with Google"
455
+ ]
456
+ }
457
+ ),
458
+ providers.includes("github") && /* @__PURE__ */ jsxs(
459
+ "button",
460
+ {
461
+ type: "button",
462
+ onClick: () => handleOAuthLogin("github"),
463
+ disabled: isLoading,
464
+ "aria-label": "Sign in with GitHub",
465
+ style: {
466
+ display: "flex",
467
+ alignItems: "center",
468
+ justifyContent: "center",
469
+ gap: "0.75rem",
470
+ width: "100%",
471
+ padding: "0.75rem 1rem",
472
+ backgroundColor: "white",
473
+ border: "1px solid #d1d5db",
474
+ borderRadius: "0.5rem",
475
+ fontSize: "0.875rem",
476
+ fontWeight: "500",
477
+ color: "#374151",
478
+ cursor: isLoading ? "not-allowed" : "pointer",
479
+ opacity: isLoading ? 0.5 : 1,
480
+ transition: "all 0.15s ease"
481
+ },
482
+ onMouseEnter: (e) => {
483
+ if (!isLoading) {
484
+ e.currentTarget.style.backgroundColor = "#f9fafb";
485
+ }
486
+ },
487
+ onMouseLeave: (e) => {
488
+ e.currentTarget.style.backgroundColor = "white";
489
+ },
490
+ children: [
491
+ /* @__PURE__ */ jsx("svg", { width: "20", height: "20", fill: "currentColor", viewBox: "0 0 20 20", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
492
+ "path",
493
+ {
494
+ fillRule: "evenodd",
495
+ d: "M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z",
496
+ clipRule: "evenodd"
497
+ }
498
+ ) }),
499
+ "Continue with GitHub"
500
+ ]
501
+ }
502
+ )
503
+ ] }),
504
+ (mode === "both" || mode === "password" && showSignUp) && /* @__PURE__ */ jsxs(
505
+ "div",
506
+ {
507
+ style: {
508
+ display: "flex",
509
+ alignItems: "center",
510
+ gap: "1rem",
511
+ marginBottom: "1.5rem"
512
+ },
513
+ children: [
514
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: "1px", backgroundColor: "#e5e7eb" } }),
515
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", color: "#6b7280" }, children: "or continue with email" }),
516
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: "1px", backgroundColor: "#e5e7eb" } })
517
+ ]
518
+ }
519
+ ),
520
+ (mode === "password" || mode === "both") && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordAuth, style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
521
+ /* @__PURE__ */ jsxs("div", { children: [
522
+ /* @__PURE__ */ jsx(
523
+ "label",
524
+ {
525
+ htmlFor: "email",
526
+ style: {
527
+ display: "block",
528
+ fontSize: "0.875rem",
529
+ fontWeight: "500",
530
+ color: "#374151",
531
+ marginBottom: "0.5rem"
532
+ },
533
+ children: "Email address"
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx(
537
+ "input",
538
+ {
539
+ id: "email",
540
+ name: "email",
541
+ type: "email",
542
+ autoComplete: "email",
543
+ required: true,
544
+ disabled: isLoading,
545
+ style: {
546
+ width: "100%",
547
+ padding: "0.75rem 1rem",
548
+ border: "1px solid #d1d5db",
549
+ borderRadius: "0.5rem",
550
+ fontSize: "0.875rem",
551
+ outline: "none",
552
+ cursor: isLoading ? "not-allowed" : "text"
553
+ },
554
+ onFocus: (e) => {
555
+ e.currentTarget.style.borderColor = "#3b82f6";
556
+ e.currentTarget.style.boxShadow = "0 0 0 3px rgba(59, 130, 246, 0.1)";
557
+ },
558
+ onBlur: (e) => {
559
+ e.currentTarget.style.borderColor = "#d1d5db";
560
+ e.currentTarget.style.boxShadow = "none";
561
+ }
562
+ }
563
+ )
564
+ ] }),
565
+ /* @__PURE__ */ jsxs("div", { children: [
566
+ /* @__PURE__ */ jsx(
567
+ "label",
568
+ {
569
+ htmlFor: "password",
570
+ style: {
571
+ display: "block",
572
+ fontSize: "0.875rem",
573
+ fontWeight: "500",
574
+ color: "#374151",
575
+ marginBottom: "0.5rem"
576
+ },
577
+ children: "Password"
578
+ }
579
+ ),
580
+ /* @__PURE__ */ jsx(
581
+ "input",
582
+ {
583
+ id: "password",
584
+ name: "password",
585
+ type: "password",
586
+ autoComplete: authMode === "signIn" ? "current-password" : "new-password",
587
+ required: true,
588
+ disabled: isLoading,
589
+ minLength: 6,
590
+ style: {
591
+ width: "100%",
592
+ padding: "0.75rem 1rem",
593
+ border: "1px solid #d1d5db",
594
+ borderRadius: "0.5rem",
595
+ fontSize: "0.875rem",
596
+ outline: "none",
597
+ cursor: isLoading ? "not-allowed" : "text"
598
+ },
599
+ onFocus: (e) => {
600
+ e.currentTarget.style.borderColor = "#3b82f6";
601
+ e.currentTarget.style.boxShadow = "0 0 0 3px rgba(59, 130, 246, 0.1)";
602
+ },
603
+ onBlur: (e) => {
604
+ e.currentTarget.style.borderColor = "#d1d5db";
605
+ e.currentTarget.style.boxShadow = "none";
606
+ }
607
+ }
608
+ )
609
+ ] }),
610
+ /* @__PURE__ */ jsx(
611
+ "button",
612
+ {
613
+ type: "submit",
614
+ disabled: isLoading,
615
+ style: {
616
+ width: "100%",
617
+ padding: "0.75rem 1rem",
618
+ backgroundColor: "#3b82f6",
619
+ border: "none",
620
+ borderRadius: "0.5rem",
621
+ fontSize: "0.875rem",
622
+ fontWeight: "500",
623
+ color: "white",
624
+ cursor: isLoading ? "not-allowed" : "pointer",
625
+ opacity: isLoading ? 0.5 : 1,
626
+ transition: "all 0.15s ease"
627
+ },
628
+ onMouseEnter: (e) => {
629
+ if (!isLoading) {
630
+ e.currentTarget.style.backgroundColor = "#2563eb";
631
+ }
632
+ },
633
+ onMouseLeave: (e) => {
634
+ e.currentTarget.style.backgroundColor = "#3b82f6";
635
+ },
636
+ children: isLoading ? "Loading..." : authMode === "signIn" ? "Sign in" : "Create account"
637
+ }
638
+ )
639
+ ] }),
640
+ showSignUp && mode !== "oauth" && /* @__PURE__ */ jsx("div", { style: { marginTop: "1.5rem", textAlign: "center" }, children: /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.875rem", color: "#6b7280" }, children: [
641
+ authMode === "signIn" ? "Don't have an account? " : "Already have an account? ",
642
+ /* @__PURE__ */ jsx(
643
+ "button",
644
+ {
645
+ type: "button",
646
+ onClick: toggleAuthMode,
647
+ disabled: isLoading,
648
+ style: {
649
+ background: "none",
650
+ border: "none",
651
+ color: "#3b82f6",
652
+ fontSize: "0.875rem",
653
+ fontWeight: "500",
654
+ cursor: isLoading ? "not-allowed" : "pointer",
655
+ textDecoration: "underline"
656
+ },
657
+ children: authMode === "signIn" ? "Sign up" : "Sign in"
658
+ }
659
+ )
660
+ ] }) }),
661
+ /* @__PURE__ */ jsx("p", { style: { marginTop: "1.5rem", fontSize: "0.75rem", color: "#9ca3af", textAlign: "center" }, children: "By continuing, you agree to our Terms of Service and Privacy Policy." })
662
+ ]
663
+ }
664
+ )
665
+ }
666
+ );
667
+ }
668
+ var DEFAULT_SECTIONS = {
669
+ dataCollection: {
670
+ title: "1. Information We Collect",
671
+ content: `We collect information you provide directly to us, including:
672
+ - Name, email address, and other contact information
673
+ - Account credentials (encrypted)
674
+ - Profile information and preferences
675
+ - Usage data and analytics
676
+ - Payment information (processed securely through third-party providers)`
677
+ },
678
+ dataUsage: {
679
+ title: "2. How We Use Your Information",
680
+ content: `We use the information we collect to:
681
+ - Provide, maintain, and improve our services
682
+ - Process transactions and send related information
683
+ - Send technical notices and support messages
684
+ - Respond to comments and questions
685
+ - Monitor and analyze trends, usage, and activities
686
+ - Detect, prevent, and address technical issues and security threats`
687
+ },
688
+ dataSharing: {
689
+ title: "3. Information Sharing",
690
+ content: `We do not sell your personal data. We may share your information with:
691
+ - Service providers who perform services on our behalf
692
+ - Payment processors (for transaction processing)
693
+ - Analytics providers (to understand service usage)
694
+ - Legal authorities when required by law
695
+ - Third parties with your explicit consent`
696
+ },
697
+ dataRetention: {
698
+ title: "4. Data Retention",
699
+ content: `We retain your personal data for as long as necessary to provide our services and fulfill the transactions you request. You may request deletion of your data at any time, subject to certain legal obligations.`
700
+ },
701
+ userRights: {
702
+ title: "5. Your Rights (GDPR)",
703
+ content: `Under GDPR, you have the right to:
704
+ - Access your personal data
705
+ - Correct inaccurate data
706
+ - Request deletion of your data ("right to be forgotten")
707
+ - Object to processing of your data
708
+ - Data portability
709
+ - Restrict processing
710
+ - Withdraw consent at any time`
711
+ },
712
+ cookies: {
713
+ title: "6. Cookies and Tracking",
714
+ content: `We use cookies and similar technologies to:
715
+ - Remember your preferences
716
+ - Understand how you use our services
717
+ - Provide analytics and improve performance
718
+ - Deliver personalized content`
719
+ },
720
+ security: {
721
+ title: "7. Security",
722
+ content: `We implement appropriate technical and organizational measures to protect your personal data against unauthorized access, alteration, disclosure, or destruction. However, no method of transmission over the internet is 100% secure.`
723
+ },
724
+ contact: {
725
+ title: "8. Contact Us",
726
+ content: `For questions about this privacy policy or your personal data, please contact us.`
727
+ }
728
+ };
729
+ function PrivacyPolicy({
730
+ companyName,
731
+ email,
732
+ updatedAt,
733
+ websiteUrl,
734
+ supportEmail,
735
+ retentionDays = 365,
736
+ dataCategories = ["personal information", "usage data", "payment information"],
737
+ sections = {},
738
+ className
739
+ }) {
740
+ const formatDate = (date) => {
741
+ if (typeof date === "string") {
742
+ return date;
743
+ }
744
+ const parts = date.toISOString().split("T");
745
+ return parts[0];
746
+ };
747
+ const allSections = { ...DEFAULT_SECTIONS, ...sections };
748
+ const lastUpdated = updatedAt ? formatDate(updatedAt) : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
749
+ return /* @__PURE__ */ jsx("div", { className, style: { fontFamily: "system-ui, -apple-system, sans-serif" }, children: /* @__PURE__ */ jsxs("div", { style: { maxWidth: "800px", margin: "0 auto", padding: "2rem 1rem" }, children: [
750
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "2rem", fontWeight: "700", marginBottom: "0.5rem" }, children: "Privacy Policy" }),
751
+ /* @__PURE__ */ jsxs("p", { style: { color: "#6b7280", marginBottom: "2rem" }, children: [
752
+ "Last Updated: ",
753
+ lastUpdated
754
+ ] }),
755
+ /* @__PURE__ */ jsx("section", { style: { marginBottom: "2rem" }, children: /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.6" }, children: [
756
+ "At ",
757
+ /* @__PURE__ */ jsx("strong", { children: companyName }),
758
+ ' ("we", "our", or "us"), we are committed to protecting your privacy and personal data. This privacy policy explains how we collect, use, share, and protect your information when you use our services.'
759
+ ] }) }),
760
+ Object.entries(allSections).map(([key, section]) => /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
761
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: section.title }),
762
+ /* @__PURE__ */ jsx("div", { style: { lineHeight: "1.7", whiteSpace: "pre-line", color: "#374151" }, children: section.content })
763
+ ] }, key)),
764
+ dataCategories && dataCategories.length > 0 && /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
765
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "Categories of Personal Data" }),
766
+ /* @__PURE__ */ jsx("ul", { style: { paddingLeft: "1.5rem", color: "#374151", lineHeight: "1.7" }, children: dataCategories.map((category, index) => /* @__PURE__ */ jsx("li", { children: category }, index)) })
767
+ ] }),
768
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
769
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "Data Retention Period" }),
770
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
771
+ "We retain your personal data for a period of ",
772
+ /* @__PURE__ */ jsxs("strong", { children: [
773
+ retentionDays,
774
+ " days"
775
+ ] }),
776
+ " after account closure, unless longer retention is required by law."
777
+ ] })
778
+ ] }),
779
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
780
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "Contact Information" }),
781
+ /* @__PURE__ */ jsxs("div", { style: { lineHeight: "1.7", color: "#374151" }, children: [
782
+ /* @__PURE__ */ jsxs("p", { children: [
783
+ "If you have any questions about this Privacy Policy, please contact us at:",
784
+ " ",
785
+ /* @__PURE__ */ jsx("a", { href: `mailto:${email}`, style: { color: "#3b82f6" }, children: email })
786
+ ] }),
787
+ supportEmail && supportEmail !== email && /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem" }, children: [
788
+ "For support inquiries:",
789
+ " ",
790
+ /* @__PURE__ */ jsx("a", { href: `mailto:${supportEmail}`, style: { color: "#3b82f6" }, children: supportEmail })
791
+ ] }),
792
+ websiteUrl && /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem" }, children: [
793
+ "Website: ",
794
+ " ",
795
+ /* @__PURE__ */ jsx("a", { href: websiteUrl, style: { color: "#3b82f6" }, children: websiteUrl })
796
+ ] })
797
+ ] })
798
+ ] }),
799
+ /* @__PURE__ */ jsx("footer", { style: { marginTop: "3rem", paddingTop: "2rem", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.875rem", color: "#6b7280", textAlign: "center" }, children: [
800
+ "\xA9 ",
801
+ (/* @__PURE__ */ new Date()).getFullYear(),
802
+ " ",
803
+ companyName,
804
+ ". All rights reserved."
805
+ ] }) })
806
+ ] }) });
807
+ }
808
+ function TermsOfService({
809
+ companyName,
810
+ email,
811
+ updatedAt,
812
+ websiteUrl,
813
+ jurisdiction = "The laws of the jurisdiction where the company is registered",
814
+ governingLaw,
815
+ limitationOfLiability = true,
816
+ intellectualProperty = true,
817
+ terminationClause = true,
818
+ className
819
+ }) {
820
+ const formatDate = (date) => {
821
+ if (typeof date === "string") {
822
+ return date;
823
+ }
824
+ const parts = date.toISOString().split("T");
825
+ return parts[0];
826
+ };
827
+ const lastUpdated = updatedAt ? formatDate(updatedAt) : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
828
+ return /* @__PURE__ */ jsx("div", { className, style: { fontFamily: "system-ui, -apple-system, sans-serif" }, children: /* @__PURE__ */ jsxs("div", { style: { maxWidth: "800px", margin: "0 auto", padding: "2rem 1rem" }, children: [
829
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "2rem", fontWeight: "700", marginBottom: "0.5rem" }, children: "Terms of Service" }),
830
+ /* @__PURE__ */ jsxs("p", { style: { color: "#6b7280", marginBottom: "2rem" }, children: [
831
+ "Last Updated: ",
832
+ lastUpdated
833
+ ] }),
834
+ /* @__PURE__ */ jsx("section", { style: { marginBottom: "2rem" }, children: /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.6" }, children: [
835
+ "Welcome to ",
836
+ /* @__PURE__ */ jsx("strong", { children: companyName }),
837
+ ". By using our services, you agree to these terms. Please read them carefully."
838
+ ] }) }),
839
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
840
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "1. Acceptance of Terms" }),
841
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
842
+ "By accessing or using ",
843
+ companyName,
844
+ "'s services, you agree to be bound by these Terms of Service. If you do not agree to these terms, please do not use our services."
845
+ ] })
846
+ ] }),
847
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
848
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "2. Description of Service" }),
849
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
850
+ companyName,
851
+ " provides software services and related features as described in our documentation. We reserve the right to modify, suspend, or discontinue any aspect of our services at any time."
852
+ ] })
853
+ ] }),
854
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
855
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "3. User Responsibilities" }),
856
+ /* @__PURE__ */ jsx("p", { style: { lineHeight: "1.7", color: "#374151" }, children: "As a user of our services, you agree to:" }),
857
+ /* @__PURE__ */ jsxs("ul", { style: { paddingLeft: "1.5rem", color: "#374151", lineHeight: "1.7" }, children: [
858
+ /* @__PURE__ */ jsx("li", { children: "Provide accurate and complete information" }),
859
+ /* @__PURE__ */ jsx("li", { children: "Maintain the security of your account credentials" }),
860
+ /* @__PURE__ */ jsx("li", { children: "Comply with all applicable laws and regulations" }),
861
+ /* @__PURE__ */ jsx("li", { children: "Not use our services for any unlawful purpose" }),
862
+ /* @__PURE__ */ jsx("li", { children: "Not interfere with or disrupt our services" }),
863
+ /* @__PURE__ */ jsx("li", { children: "Not attempt to gain unauthorized access to our systems" })
864
+ ] })
865
+ ] }),
866
+ intellectualProperty && /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
867
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: "4. Intellectual Property" }),
868
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
869
+ "All content, features, and functionality of our services are owned by ",
870
+ companyName,
871
+ " and are protected by copyright, trademark, and other intellectual property laws. You may not:"
872
+ ] }),
873
+ /* @__PURE__ */ jsxs("ul", { style: { paddingLeft: "1.5rem", color: "#374151", lineHeight: "1.7" }, children: [
874
+ /* @__PURE__ */ jsx("li", { children: "Reproduce, distribute, or create derivative works" }),
875
+ /* @__PURE__ */ jsx("li", { children: "Use our services for commercial purposes without authorization" }),
876
+ /* @__PURE__ */ jsx("li", { children: "Remove any proprietary notices from our services" })
877
+ ] })
878
+ ] }),
879
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
880
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: [
881
+ intellectualProperty ? "5" : "4",
882
+ ". User Content"
883
+ ] }),
884
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
885
+ "You retain ownership of any content you submit to our services. By submitting content, you grant",
886
+ companyName,
887
+ " a license to use, modify, and display your content for the purpose of providing our services. You represent that you have all necessary rights to grant such license."
888
+ ] })
889
+ ] }),
890
+ terminationClause && /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
891
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: [
892
+ intellectualProperty ? "6" : "5",
893
+ ". Termination"
894
+ ] }),
895
+ /* @__PURE__ */ jsx("p", { style: { lineHeight: "1.7", color: "#374151" }, children: "We may terminate or suspend your account and access to our services at any time, with or without cause, with or without notice. Upon termination, your right to use our services will immediately cease." })
896
+ ] }),
897
+ limitationOfLiability && /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
898
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: [
899
+ terminationClause ? "7" : intellectualProperty ? "6" : "5",
900
+ ". Limitation of Liability"
901
+ ] }),
902
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
903
+ "To the fullest extent permitted by law, ",
904
+ companyName,
905
+ " shall not be liable for any indirect, incidental, special, consequential, or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses."
906
+ ] })
907
+ ] }),
908
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
909
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: [
910
+ limitationOfLiability ? "8" : terminationClause ? "7" : intellectualProperty ? "6" : "5",
911
+ ". Governing Law"
912
+ ] }),
913
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
914
+ "These terms shall be governed by and construed in accordance with ",
915
+ governingLaw || jurisdiction,
916
+ "."
917
+ ] })
918
+ ] }),
919
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
920
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: [
921
+ limitationOfLiability ? "9" : terminationClause ? "8" : intellectualProperty ? "7" : "6",
922
+ ". Changes to Terms"
923
+ ] }),
924
+ /* @__PURE__ */ jsx("p", { style: { lineHeight: "1.7", color: "#374151" }, children: "We reserve the right to modify these terms at any time. We will notify users of any material changes via email or through our services. Your continued use of our services after such modifications constitutes your acceptance of the updated terms." })
925
+ ] }),
926
+ /* @__PURE__ */ jsxs("section", { style: { marginBottom: "2rem" }, children: [
927
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "1rem" }, children: [
928
+ limitationOfLiability ? "10" : terminationClause ? "9" : intellectualProperty ? "8" : "7",
929
+ ". Contact Us"
930
+ ] }),
931
+ /* @__PURE__ */ jsxs("p", { style: { lineHeight: "1.7", color: "#374151" }, children: [
932
+ "If you have any questions about these Terms of Service, please contact us at:",
933
+ " ",
934
+ /* @__PURE__ */ jsx("a", { href: `mailto:${email}`, style: { color: "#3b82f6" }, children: email })
935
+ ] }),
936
+ websiteUrl && /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem", lineHeight: "1.7", color: "#374151" }, children: [
937
+ "Website: ",
938
+ " ",
939
+ /* @__PURE__ */ jsx("a", { href: websiteUrl, style: { color: "#3b82f6" }, children: websiteUrl })
940
+ ] })
941
+ ] }),
942
+ /* @__PURE__ */ jsx("footer", { style: { marginTop: "3rem", paddingTop: "2rem", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.875rem", color: "#6b7280", textAlign: "center" }, children: [
943
+ "\xA9 ",
944
+ (/* @__PURE__ */ new Date()).getFullYear(),
945
+ " ",
946
+ companyName,
947
+ ". All rights reserved."
948
+ ] }) })
949
+ ] }) });
950
+ }
951
+ var STORAGE_KEY = "vocoweb-cookie-consent";
952
+ var CONSENT_EXPIRY_DAYS = 365;
953
+ var DEFAULT_CATEGORIES = [
954
+ {
955
+ id: "necessary",
956
+ name: "Strictly Necessary",
957
+ description: "These cookies are essential for the site to function properly.",
958
+ required: true
959
+ },
960
+ {
961
+ id: "analytics",
962
+ name: "Analytics",
963
+ description: "Help us improve our website by collecting anonymous usage data.",
964
+ required: false,
965
+ scripts: ["https://www.googletagmanager.com/gtag/js"]
966
+ },
967
+ {
968
+ id: "marketing",
969
+ name: "Marketing",
970
+ description: "Used to deliver advertisements that are relevant to you and your interests.",
971
+ required: false
972
+ },
973
+ {
974
+ id: "preferences",
975
+ name: "Preferences",
976
+ description: "Allows the site to remember your choices and preferences.",
977
+ required: false
978
+ }
979
+ ];
980
+ function CookieConsent({
981
+ onAccept,
982
+ onReject,
983
+ onCustomize,
984
+ position = "bottom",
985
+ style = "banner",
986
+ categories = DEFAULT_CATEGORIES,
987
+ privacyPolicyUrl,
988
+ acceptAllText = "Accept All",
989
+ rejectAllText = "Reject All",
990
+ customizeText = "Customize",
991
+ className
992
+ }) {
993
+ const [isVisible, setIsVisible] = useState(false);
994
+ const [showCustomize, setShowCustomize] = useState(false);
995
+ const [selectedCategories, setSelectedCategories] = useState({});
996
+ useEffect(() => {
997
+ try {
998
+ const stored = localStorage.getItem(STORAGE_KEY);
999
+ if (stored) {
1000
+ const { timestamp, consent } = JSON.parse(stored);
1001
+ const daysSince = (Date.now() - timestamp) / (1e3 * 60 * 60 * 24);
1002
+ if (daysSince > CONSENT_EXPIRY_DAYS) {
1003
+ setIsVisible(true);
1004
+ } else if (consent === "pending") {
1005
+ setIsVisible(true);
1006
+ }
1007
+ } else {
1008
+ setIsVisible(true);
1009
+ }
1010
+ } catch {
1011
+ setIsVisible(true);
1012
+ }
1013
+ }, []);
1014
+ useEffect(() => {
1015
+ const required = {};
1016
+ categories.forEach((cat) => {
1017
+ if (cat.required) {
1018
+ required[cat.id] = true;
1019
+ }
1020
+ });
1021
+ setSelectedCategories(required);
1022
+ }, [categories]);
1023
+ const saveConsent = useCallback((consent, categories2) => {
1024
+ const data = {
1025
+ timestamp: Date.now(),
1026
+ consent,
1027
+ categories: categories2
1028
+ };
1029
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
1030
+ }, []);
1031
+ const handleAccept = useCallback(() => {
1032
+ const allAccepted = {};
1033
+ categories.forEach((cat) => {
1034
+ allAccepted[cat.id] = true;
1035
+ });
1036
+ setSelectedCategories(allAccepted);
1037
+ saveConsent("accept", allAccepted);
1038
+ setIsVisible(false);
1039
+ onAccept?.();
1040
+ }, [categories, onAccept, saveConsent]);
1041
+ const handleReject = useCallback(() => {
1042
+ const requiredOnly = {};
1043
+ categories.forEach((cat) => {
1044
+ if (cat.required) {
1045
+ requiredOnly[cat.id] = true;
1046
+ }
1047
+ });
1048
+ setSelectedCategories(requiredOnly);
1049
+ saveConsent("reject", requiredOnly);
1050
+ setIsVisible(false);
1051
+ onReject?.();
1052
+ }, [categories, onReject, saveConsent]);
1053
+ const handleCustomize = useCallback(() => {
1054
+ setShowCustomize(true);
1055
+ }, []);
1056
+ const handleSaveCustom = useCallback(() => {
1057
+ saveConsent("partial", selectedCategories);
1058
+ setIsVisible(false);
1059
+ setShowCustomize(false);
1060
+ onCustomize?.(selectedCategories);
1061
+ }, [selectedCategories, saveConsent, onCustomize]);
1062
+ const toggleCategory = useCallback((categoryId) => {
1063
+ const category = categories.find((c) => c.id === categoryId);
1064
+ if (category?.required) {
1065
+ return;
1066
+ }
1067
+ setSelectedCategories((prev) => ({
1068
+ ...prev,
1069
+ [categoryId]: !prev[categoryId]
1070
+ }));
1071
+ }, [categories]);
1072
+ const handleCloseModal = useCallback(() => {
1073
+ setShowCustomize(false);
1074
+ }, []);
1075
+ useEffect(() => {
1076
+ if (Object.keys(selectedCategories).length === 0) {
1077
+ return;
1078
+ }
1079
+ categories.forEach((category) => {
1080
+ if (category.scripts && category.scripts.length > 0) {
1081
+ const shouldBlock = !selectedCategories[category.id];
1082
+ category.scripts.forEach((scriptSrc) => {
1083
+ const scripts = document.querySelectorAll(`script[src="${scriptSrc}"]`);
1084
+ scripts.forEach((script) => {
1085
+ if (shouldBlock) {
1086
+ script.remove();
1087
+ }
1088
+ });
1089
+ });
1090
+ }
1091
+ });
1092
+ }, [selectedCategories, categories]);
1093
+ if (!isVisible) {
1094
+ return null;
1095
+ }
1096
+ if (style === "banner" && !showCustomize) {
1097
+ const isBottom = position === "bottom";
1098
+ return /* @__PURE__ */ jsx(
1099
+ "div",
1100
+ {
1101
+ className,
1102
+ role: "dialog",
1103
+ "aria-labelledby": "cookie-consent-title",
1104
+ "aria-describedby": "cookie-consent-description",
1105
+ style: {
1106
+ position: "fixed",
1107
+ left: 0,
1108
+ right: 0,
1109
+ [isBottom ? "bottom" : "top"]: 0,
1110
+ backgroundColor: "rgba(17, 24, 39, 0.95)",
1111
+ backdropFilter: "blur(8px)",
1112
+ padding: "1rem",
1113
+ zIndex: 9999,
1114
+ boxShadow: isBottom ? "0 -4px 6px -1px rgb(0 0 0 / 0.1)" : "0 4px 6px -1px rgb(0 0 0 / 0.1)"
1115
+ },
1116
+ children: /* @__PURE__ */ jsxs(
1117
+ "div",
1118
+ {
1119
+ style: {
1120
+ maxWidth: "1200px",
1121
+ margin: "0 auto",
1122
+ display: "flex",
1123
+ alignItems: "center",
1124
+ justifyContent: "space-between",
1125
+ gap: "1rem",
1126
+ flexWrap: "wrap"
1127
+ },
1128
+ children: [
1129
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: "200px" }, children: [
1130
+ /* @__PURE__ */ jsx("h2", { id: "cookie-consent-title", style: { fontSize: "1rem", fontWeight: "600", color: "white", marginBottom: "0.25rem" }, children: "We use cookies" }),
1131
+ /* @__PURE__ */ jsxs("p", { id: "cookie-consent-description", style: { fontSize: "0.875rem", color: "#d1d5db", margin: 0 }, children: [
1132
+ "We use cookies to improve your experience and analyze usage. By continuing, you agree to our",
1133
+ " ",
1134
+ privacyPolicyUrl && /* @__PURE__ */ jsx(
1135
+ "a",
1136
+ {
1137
+ href: privacyPolicyUrl,
1138
+ style: { color: "#60a5fa", textDecoration: "underline" },
1139
+ children: "Privacy Policy"
1140
+ }
1141
+ )
1142
+ ] })
1143
+ ] }),
1144
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", flexWrap: "wrap" }, children: [
1145
+ /* @__PURE__ */ jsx(
1146
+ "button",
1147
+ {
1148
+ onClick: handleReject,
1149
+ style: {
1150
+ padding: "0.5rem 1rem",
1151
+ backgroundColor: "transparent",
1152
+ border: "1px solid #4b5563",
1153
+ borderRadius: "0.375rem",
1154
+ color: "#d1d5db",
1155
+ fontSize: "0.875rem",
1156
+ fontWeight: "500",
1157
+ cursor: "pointer",
1158
+ transition: "all 0.15s ease"
1159
+ },
1160
+ onMouseEnter: (e) => {
1161
+ e.currentTarget.style.backgroundColor = "#374151";
1162
+ },
1163
+ onMouseLeave: (e) => {
1164
+ e.currentTarget.style.backgroundColor = "transparent";
1165
+ },
1166
+ children: rejectAllText
1167
+ }
1168
+ ),
1169
+ /* @__PURE__ */ jsx(
1170
+ "button",
1171
+ {
1172
+ onClick: handleCustomize,
1173
+ style: {
1174
+ padding: "0.5rem 1rem",
1175
+ backgroundColor: "transparent",
1176
+ border: "1px solid #4b5563",
1177
+ borderRadius: "0.375rem",
1178
+ color: "#d1d5db",
1179
+ fontSize: "0.875rem",
1180
+ fontWeight: "500",
1181
+ cursor: "pointer",
1182
+ transition: "all 0.15s ease"
1183
+ },
1184
+ onMouseEnter: (e) => {
1185
+ e.currentTarget.style.backgroundColor = "#374151";
1186
+ },
1187
+ onMouseLeave: (e) => {
1188
+ e.currentTarget.style.backgroundColor = "transparent";
1189
+ },
1190
+ children: customizeText
1191
+ }
1192
+ ),
1193
+ /* @__PURE__ */ jsx(
1194
+ "button",
1195
+ {
1196
+ onClick: handleAccept,
1197
+ style: {
1198
+ padding: "0.5rem 1rem",
1199
+ backgroundColor: "#3b82f6",
1200
+ border: "none",
1201
+ borderRadius: "0.375rem",
1202
+ color: "white",
1203
+ fontSize: "0.875rem",
1204
+ fontWeight: "500",
1205
+ cursor: "pointer",
1206
+ transition: "all 0.15s ease"
1207
+ },
1208
+ onMouseEnter: (e) => {
1209
+ e.currentTarget.style.backgroundColor = "#2563eb";
1210
+ },
1211
+ onMouseLeave: (e) => {
1212
+ e.currentTarget.style.backgroundColor = "#3b82f6";
1213
+ },
1214
+ children: acceptAllText
1215
+ }
1216
+ )
1217
+ ] })
1218
+ ]
1219
+ }
1220
+ )
1221
+ }
1222
+ );
1223
+ }
1224
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1225
+ showCustomize && /* @__PURE__ */ jsx(
1226
+ "div",
1227
+ {
1228
+ onClick: handleCloseModal,
1229
+ style: {
1230
+ position: "fixed",
1231
+ inset: 0,
1232
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1233
+ zIndex: 9998
1234
+ }
1235
+ }
1236
+ ),
1237
+ /* @__PURE__ */ jsxs(
1238
+ "div",
1239
+ {
1240
+ className,
1241
+ role: "dialog",
1242
+ "aria-labelledby": "cookie-consent-title",
1243
+ "aria-modal": "true",
1244
+ style: {
1245
+ position: "fixed",
1246
+ left: "50%",
1247
+ top: "50%",
1248
+ transform: "translate(-50%, -50%)",
1249
+ backgroundColor: "white",
1250
+ borderRadius: "0.75rem",
1251
+ padding: "1.5rem",
1252
+ zIndex: 9999,
1253
+ maxWidth: "500px",
1254
+ width: "90%",
1255
+ maxHeight: "80vh",
1256
+ overflowY: "auto",
1257
+ boxShadow: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
1258
+ },
1259
+ children: [
1260
+ /* @__PURE__ */ jsx(
1261
+ "h2",
1262
+ {
1263
+ id: "cookie-consent-title",
1264
+ style: { fontSize: "1.25rem", fontWeight: "700", color: "#111827", marginBottom: "0.5rem" },
1265
+ children: "Customize Cookie Preferences"
1266
+ }
1267
+ ),
1268
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280", marginBottom: "1.5rem" }, children: "We use different types of cookies to provide you with the best experience. You can choose which cookies to accept." }),
1269
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: "1.5rem" }, children: categories.map((category) => /* @__PURE__ */ jsxs(
1270
+ "div",
1271
+ {
1272
+ style: {
1273
+ display: "flex",
1274
+ justifyContent: "space-between",
1275
+ alignItems: "flex-start",
1276
+ padding: "0.75rem 0",
1277
+ borderBottom: "1px solid #e5e7eb"
1278
+ },
1279
+ children: [
1280
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
1281
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1282
+ /* @__PURE__ */ jsx("h3", { style: { fontSize: "0.875rem", fontWeight: "600", color: "#111827", margin: 0 }, children: category.name }),
1283
+ category.required && /* @__PURE__ */ jsx(
1284
+ "span",
1285
+ {
1286
+ style: {
1287
+ fontSize: "0.75rem",
1288
+ backgroundColor: "#dbeafe",
1289
+ color: "#1e40af",
1290
+ padding: "0.125rem 0.375rem",
1291
+ borderRadius: "0.25rem",
1292
+ fontWeight: "500"
1293
+ },
1294
+ children: "Required"
1295
+ }
1296
+ )
1297
+ ] }),
1298
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0", marginTop: "0.25rem" }, children: category.description })
1299
+ ] }),
1300
+ !category.required && /* @__PURE__ */ jsx(
1301
+ "button",
1302
+ {
1303
+ onClick: () => toggleCategory(category.id),
1304
+ style: {
1305
+ position: "relative",
1306
+ width: "44px",
1307
+ height: "24px",
1308
+ backgroundColor: selectedCategories[category.id] ? "#3b82f6" : "#d1d5db",
1309
+ borderRadius: "12px",
1310
+ border: "none",
1311
+ cursor: "pointer",
1312
+ transition: "background-color 0.15s ease"
1313
+ },
1314
+ "aria-pressed": selectedCategories[category.id],
1315
+ children: /* @__PURE__ */ jsx(
1316
+ "span",
1317
+ {
1318
+ style: {
1319
+ position: "absolute",
1320
+ top: "2px",
1321
+ left: selectedCategories[category.id] ? "22px" : "2px",
1322
+ width: "20px",
1323
+ height: "20px",
1324
+ backgroundColor: "white",
1325
+ borderRadius: "50%",
1326
+ transition: "left 0.15s ease"
1327
+ }
1328
+ }
1329
+ )
1330
+ }
1331
+ )
1332
+ ]
1333
+ },
1334
+ category.id
1335
+ )) }),
1336
+ privacyPolicyUrl && /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.875rem", color: "#6b7280", marginBottom: "1rem" }, children: [
1337
+ "Read more in our",
1338
+ " ",
1339
+ /* @__PURE__ */ jsx("a", { href: privacyPolicyUrl, style: { color: "#3b82f6", textDecoration: "underline" }, children: "Privacy Policy" })
1340
+ ] }),
1341
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.75rem", justifyContent: "flex-end" }, children: [
1342
+ /* @__PURE__ */ jsx(
1343
+ "button",
1344
+ {
1345
+ onClick: handleCloseModal,
1346
+ style: {
1347
+ padding: "0.5rem 1rem",
1348
+ backgroundColor: "white",
1349
+ border: "1px solid #d1d5db",
1350
+ borderRadius: "0.375rem",
1351
+ color: "#374151",
1352
+ fontSize: "0.875rem",
1353
+ fontWeight: "500",
1354
+ cursor: "pointer",
1355
+ transition: "all 0.15s ease"
1356
+ },
1357
+ onMouseEnter: (e) => {
1358
+ e.currentTarget.style.backgroundColor = "#f9fafb";
1359
+ },
1360
+ onMouseLeave: (e) => {
1361
+ e.currentTarget.style.backgroundColor = "white";
1362
+ },
1363
+ children: "Cancel"
1364
+ }
1365
+ ),
1366
+ /* @__PURE__ */ jsx(
1367
+ "button",
1368
+ {
1369
+ onClick: handleSaveCustom,
1370
+ style: {
1371
+ padding: "0.5rem 1rem",
1372
+ backgroundColor: "#3b82f6",
1373
+ border: "none",
1374
+ borderRadius: "0.375rem",
1375
+ color: "white",
1376
+ fontSize: "0.875rem",
1377
+ fontWeight: "500",
1378
+ cursor: "pointer",
1379
+ transition: "all 0.15s ease"
1380
+ },
1381
+ onMouseEnter: (e) => {
1382
+ e.currentTarget.style.backgroundColor = "#2563eb";
1383
+ },
1384
+ onMouseLeave: (e) => {
1385
+ e.currentTarget.style.backgroundColor = "#3b82f6";
1386
+ },
1387
+ children: "Save Preferences"
1388
+ }
1389
+ )
1390
+ ] })
1391
+ ]
1392
+ }
1393
+ )
1394
+ ] });
1395
+ }
1396
+
1397
+ // src/accessibility/validators.ts
1398
+ function validateAriaLabel(ariaLabel, ariaLabelledby, elementType = "button") {
1399
+ const issues = [];
1400
+ const elementsRequiringLabel = ["button", "a", "input", "textarea", "select"];
1401
+ if (elementsRequiringLabel.includes(elementType)) {
1402
+ if (!ariaLabel && !ariaLabelledby) {
1403
+ issues.push({
1404
+ severity: "error",
1405
+ message: `${elementType} must have an accessible name via aria-label or aria-labelledby`,
1406
+ attribute: "aria-label"
1407
+ });
1408
+ }
1409
+ }
1410
+ if (ariaLabel === "") {
1411
+ issues.push({
1412
+ severity: "error",
1413
+ message: "aria-label cannot be empty",
1414
+ attribute: "aria-label"
1415
+ });
1416
+ }
1417
+ if (ariaLabel && ariaLabelledby) {
1418
+ issues.push({
1419
+ severity: "warning",
1420
+ message: "Use either aria-label or aria-labelledby, not both",
1421
+ attribute: "aria-label"
1422
+ });
1423
+ }
1424
+ return {
1425
+ valid: issues.filter((i) => i.severity === "error").length === 0,
1426
+ element: elementType,
1427
+ issues
1428
+ };
1429
+ }
1430
+ var variantStyles = {
1431
+ default: "bg-blue-600 text-white hover:bg-blue-700",
1432
+ destructive: "bg-red-600 text-white hover:bg-red-700",
1433
+ outline: "border-2 border-gray-300 text-gray-700 hover:bg-gray-50",
1434
+ secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
1435
+ ghost: "text-gray-700 hover:bg-gray-100",
1436
+ link: "text-blue-600 underline hover:text-blue-700"
1437
+ };
1438
+ var sizeStyles = {
1439
+ default: "px-4 py-2 text-sm",
1440
+ sm: "px-3 py-1 text-xs",
1441
+ lg: "px-6 py-3 text-base",
1442
+ icon: "p-2"
1443
+ };
1444
+ var VocoButton = forwardRef(
1445
+ ({
1446
+ children,
1447
+ variant = "default",
1448
+ size = "default",
1449
+ disabled = false,
1450
+ type = "button",
1451
+ "aria-label": ariaLabel,
1452
+ "aria-labelledby": ariaLabelledby,
1453
+ "aria-describedby": ariaDescribedby,
1454
+ role,
1455
+ className = "",
1456
+ ...props
1457
+ }, ref) => {
1458
+ if (process.env.NODE_ENV === "development") {
1459
+ const validation = validateAriaLabel(ariaLabel, ariaLabelledby, "button");
1460
+ if (!validation.valid && !children) {
1461
+ console.warn("VocoButton accessibility warning:", validation.issues);
1462
+ }
1463
+ }
1464
+ const baseStyles = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
1465
+ const hasAccessibleName = children || ariaLabel || ariaLabelledby;
1466
+ return /* @__PURE__ */ jsx(
1467
+ "button",
1468
+ {
1469
+ ref,
1470
+ type,
1471
+ disabled,
1472
+ "aria-label": ariaLabel,
1473
+ "aria-labelledby": ariaLabelledby,
1474
+ "aria-describedby": ariaDescribedby,
1475
+ role,
1476
+ className: `${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${className}`.trim(),
1477
+ ...hasAccessibleName ? {} : { "aria-label": "Button" },
1478
+ ...props,
1479
+ children
1480
+ }
1481
+ );
1482
+ }
1483
+ );
1484
+ VocoButton.displayName = "VocoButton";
1485
+ var VocoInput = forwardRef(
1486
+ ({
1487
+ id,
1488
+ label,
1489
+ error,
1490
+ hint,
1491
+ required = false,
1492
+ type = "text",
1493
+ disabled = false,
1494
+ placeholder,
1495
+ "aria-label": ariaLabel,
1496
+ "aria-labelledby": ariaLabelledby,
1497
+ "aria-describedby": ariaDescribedby,
1498
+ role,
1499
+ className = "",
1500
+ ...props
1501
+ }, ref) => {
1502
+ const inputId = id || `input-${Math.random().toString(36).substring(2, 9)}`;
1503
+ const errorId = error ? `${inputId}-error` : void 0;
1504
+ const hintId = hint ? `${inputId}-hint` : void 0;
1505
+ const describedByParts = [
1506
+ ariaDescribedby,
1507
+ error ? errorId : void 0,
1508
+ hint ? hintId : void 0
1509
+ ].filter(Boolean).join(" ");
1510
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1511
+ label && /* @__PURE__ */ jsxs(
1512
+ "label",
1513
+ {
1514
+ htmlFor: inputId,
1515
+ className: "text-sm font-medium text-gray-700",
1516
+ id: ariaLabelledby,
1517
+ children: [
1518
+ label,
1519
+ required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-1", "aria-label": "required", children: "*" })
1520
+ ]
1521
+ }
1522
+ ),
1523
+ /* @__PURE__ */ jsx(
1524
+ "input",
1525
+ {
1526
+ ref,
1527
+ id: inputId,
1528
+ type,
1529
+ disabled,
1530
+ placeholder,
1531
+ "aria-label": ariaLabel,
1532
+ "aria-labelledby": ariaLabelledby,
1533
+ "aria-describedby": describedByParts || void 0,
1534
+ "aria-invalid": !!error,
1535
+ "aria-required": required,
1536
+ required,
1537
+ role,
1538
+ className: `
1539
+ px-3 py-2 rounded-md border transition-colors
1540
+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
1541
+ disabled:opacity-50 disabled:cursor-not-allowed
1542
+ ${error ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500"}
1543
+ ${className}
1544
+ `.trim().replace(/\s+/g, " "),
1545
+ ...props
1546
+ }
1547
+ ),
1548
+ hint && !error && /* @__PURE__ */ jsx("span", { id: hintId, className: "text-xs text-gray-500", children: hint }),
1549
+ error && /* @__PURE__ */ jsx(
1550
+ "span",
1551
+ {
1552
+ id: errorId,
1553
+ className: "text-xs text-red-500",
1554
+ role: "alert",
1555
+ "aria-live": "polite",
1556
+ children: error
1557
+ }
1558
+ )
1559
+ ] });
1560
+ }
1561
+ );
1562
+ VocoInput.displayName = "VocoInput";
1563
+ var VocoForm = forwardRef(
1564
+ ({
1565
+ children,
1566
+ onSubmit,
1567
+ "aria-live": ariaLive = "polite",
1568
+ "aria-label": ariaLabel,
1569
+ "aria-labelledby": ariaLabelledby,
1570
+ "aria-describedby": ariaDescribedby,
1571
+ role,
1572
+ className = "",
1573
+ ...props
1574
+ }, ref) => {
1575
+ const [errors, setErrors] = useState({});
1576
+ const [isSubmitting, setIsSubmitting] = useState(false);
1577
+ const handleSubmit = useCallback(
1578
+ async (event) => {
1579
+ event.preventDefault();
1580
+ setIsSubmitting(true);
1581
+ const newErrors = {};
1582
+ const requiredElements = event.currentTarget.querySelectorAll("[required]");
1583
+ requiredElements.forEach((element) => {
1584
+ const input = element;
1585
+ if (!input.value.trim()) {
1586
+ const fieldName = input.getAttribute("aria-label") || input.getAttribute("name") || "Field";
1587
+ newErrors[input.name || input.id || Math.random().toString()] = `${fieldName} is required`;
1588
+ }
1589
+ });
1590
+ setErrors(newErrors);
1591
+ if (Object.keys(newErrors).length > 0) {
1592
+ setIsSubmitting(false);
1593
+ const firstErrorInput = event.currentTarget.querySelector('[aria-invalid="true"]');
1594
+ firstErrorInput?.focus();
1595
+ return;
1596
+ }
1597
+ try {
1598
+ await onSubmit?.(event);
1599
+ } finally {
1600
+ setIsSubmitting(false);
1601
+ }
1602
+ },
1603
+ [onSubmit]
1604
+ );
1605
+ const formStatusId = `form-status-${Math.random().toString(36).substring(2, 9)}`;
1606
+ const hasErrors = Object.keys(errors).length > 0;
1607
+ return /* @__PURE__ */ jsxs(
1608
+ "form",
1609
+ {
1610
+ ref,
1611
+ onSubmit: handleSubmit,
1612
+ "aria-live": ariaLive,
1613
+ "aria-label": ariaLabel,
1614
+ "aria-labelledby": ariaLabelledby,
1615
+ "aria-describedby": ariaDescribedby,
1616
+ role,
1617
+ "aria-busy": isSubmitting,
1618
+ noValidate: true,
1619
+ className,
1620
+ ...props,
1621
+ children: [
1622
+ children,
1623
+ /* @__PURE__ */ jsxs(
1624
+ "div",
1625
+ {
1626
+ id: formStatusId,
1627
+ role: "status",
1628
+ "aria-live": "polite",
1629
+ className: "sr-only",
1630
+ "aria-atomic": "true",
1631
+ children: [
1632
+ isSubmitting && "Submitting form...",
1633
+ hasErrors && "Form has errors. Please correct them and try again."
1634
+ ]
1635
+ }
1636
+ )
1637
+ ]
1638
+ }
1639
+ );
1640
+ }
1641
+ );
1642
+ VocoForm.displayName = "VocoForm";
1643
+ function VocoAuditLog({
1644
+ userId: _userId,
1645
+ filters = {},
1646
+ limit = 50,
1647
+ className = "",
1648
+ onExport,
1649
+ onLogClick,
1650
+ showFilters = true,
1651
+ showExport = true
1652
+ }) {
1653
+ const [logs, setLogs] = useState([]);
1654
+ const [total, setTotal] = useState(0);
1655
+ const [loading, setLoading] = useState(false);
1656
+ const [hasMore, setHasMore] = useState(false);
1657
+ const [offset, setOffset] = useState(0);
1658
+ const [currentFilters, setCurrentFilters] = useState(filters);
1659
+ useEffect(() => {
1660
+ fetchLogs();
1661
+ }, [currentFilters, offset]);
1662
+ async function fetchLogs() {
1663
+ setLoading(true);
1664
+ try {
1665
+ const result = {
1666
+ logs: [],
1667
+ total: 0,
1668
+ hasMore: false
1669
+ };
1670
+ setLogs(result.logs);
1671
+ setTotal(result.total);
1672
+ setHasMore(result.hasMore);
1673
+ } catch (error) {
1674
+ console.error("Failed to fetch audit logs:", error);
1675
+ } finally {
1676
+ setLoading(false);
1677
+ }
1678
+ }
1679
+ function handleFilterChange(key, value) {
1680
+ setCurrentFilters({ ...currentFilters, [key]: value });
1681
+ setOffset(0);
1682
+ }
1683
+ function handleLoadMore() {
1684
+ setOffset(offset + limit);
1685
+ }
1686
+ function handleExportClick(format) {
1687
+ onExport?.(format);
1688
+ }
1689
+ function getActionColor(action) {
1690
+ switch (action) {
1691
+ case "INSERT":
1692
+ return "text-green-600 bg-green-50";
1693
+ case "UPDATE":
1694
+ return "text-blue-600 bg-blue-50";
1695
+ case "DELETE":
1696
+ return "text-red-600 bg-red-50";
1697
+ default:
1698
+ return "text-gray-600 bg-gray-50";
1699
+ }
1700
+ }
1701
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1702
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1703
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900", children: "Audit Log" }),
1704
+ showExport && /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1705
+ /* @__PURE__ */ jsx(
1706
+ "button",
1707
+ {
1708
+ onClick: () => handleExportClick("csv"),
1709
+ className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700",
1710
+ children: "Export CSV"
1711
+ }
1712
+ ),
1713
+ /* @__PURE__ */ jsx(
1714
+ "button",
1715
+ {
1716
+ onClick: () => handleExportClick("json"),
1717
+ className: "px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700",
1718
+ children: "Export JSON"
1719
+ }
1720
+ )
1721
+ ] })
1722
+ ] }),
1723
+ showFilters && /* @__PURE__ */ jsx("div", { className: "p-4 bg-gray-50 rounded-md space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4", children: [
1724
+ /* @__PURE__ */ jsxs("div", { children: [
1725
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: "Action" }),
1726
+ /* @__PURE__ */ jsxs(
1727
+ "select",
1728
+ {
1729
+ value: currentFilters.action || "",
1730
+ onChange: (e) => handleFilterChange("action", e.target.value),
1731
+ className: "w-full px-3 py-2 border rounded-md",
1732
+ children: [
1733
+ /* @__PURE__ */ jsx("option", { value: "", children: "All Actions" }),
1734
+ /* @__PURE__ */ jsx("option", { value: "INSERT", children: "Insert" }),
1735
+ /* @__PURE__ */ jsx("option", { value: "UPDATE", children: "Update" }),
1736
+ /* @__PURE__ */ jsx("option", { value: "DELETE", children: "Delete" }),
1737
+ /* @__PURE__ */ jsx("option", { value: "LOGIN", children: "Login" }),
1738
+ /* @__PURE__ */ jsx("option", { value: "LOGOUT", children: "Logout" })
1739
+ ]
1740
+ }
1741
+ )
1742
+ ] }),
1743
+ /* @__PURE__ */ jsxs("div", { children: [
1744
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: "Table" }),
1745
+ /* @__PURE__ */ jsx(
1746
+ "input",
1747
+ {
1748
+ type: "text",
1749
+ value: currentFilters.table || "",
1750
+ onChange: (e) => handleFilterChange("table", e.target.value),
1751
+ placeholder: "e.g. users",
1752
+ className: "w-full px-3 py-2 border rounded-md"
1753
+ }
1754
+ )
1755
+ ] }),
1756
+ /* @__PURE__ */ jsxs("div", { children: [
1757
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: "Start Date" }),
1758
+ /* @__PURE__ */ jsx(
1759
+ "input",
1760
+ {
1761
+ type: "date",
1762
+ value: currentFilters.startDate ? currentFilters.startDate.toISOString().split("T")[0] : "",
1763
+ onChange: (e) => handleFilterChange("startDate", e.target.value ? new Date(e.target.value) : void 0),
1764
+ className: "w-full px-3 py-2 border rounded-md"
1765
+ }
1766
+ )
1767
+ ] }),
1768
+ /* @__PURE__ */ jsxs("div", { children: [
1769
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: "End Date" }),
1770
+ /* @__PURE__ */ jsx(
1771
+ "input",
1772
+ {
1773
+ type: "date",
1774
+ value: currentFilters.endDate ? currentFilters.endDate.toISOString().split("T")[0] : "",
1775
+ onChange: (e) => handleFilterChange("endDate", e.target.value ? new Date(e.target.value) : void 0),
1776
+ className: "w-full px-3 py-2 border rounded-md"
1777
+ }
1778
+ )
1779
+ ] })
1780
+ ] }) }),
1781
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
1782
+ /* @__PURE__ */ jsxs("div", { className: "p-4 bg-white rounded-md border", children: [
1783
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500", children: "Total Entries" }),
1784
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold", children: total })
1785
+ ] }),
1786
+ /* @__PURE__ */ jsxs("div", { className: "p-4 bg-white rounded-md border", children: [
1787
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500", children: "Showing" }),
1788
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold", children: logs.length })
1789
+ ] }),
1790
+ /* @__PURE__ */ jsxs("div", { className: "p-4 bg-white rounded-md border", children: [
1791
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500", children: "Offset" }),
1792
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold", children: offset })
1793
+ ] })
1794
+ ] }),
1795
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [
1796
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsxs("tr", { children: [
1797
+ /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Timestamp" }),
1798
+ /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Action" }),
1799
+ /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Table" }),
1800
+ /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Record ID" }),
1801
+ /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Changes" })
1802
+ ] }) }),
1803
+ /* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: loading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 5, className: "px-6 py-4 text-center text-gray-500", children: "Loading..." }) }) : logs.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 5, className: "px-6 py-4 text-center text-gray-500", children: "No audit logs found" }) }) : logs.map((log) => /* @__PURE__ */ jsxs(
1804
+ "tr",
1805
+ {
1806
+ onClick: () => onLogClick?.(log.id),
1807
+ className: onLogClick ? "cursor-pointer hover:bg-gray-50" : "",
1808
+ children: [
1809
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-900", children: log.timestamp.toLocaleString() }),
1810
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsx(
1811
+ "span",
1812
+ {
1813
+ className: `px-2 py-1 text-xs font-medium rounded-full ${getActionColor(log.action)}`,
1814
+ children: log.action
1815
+ }
1816
+ ) }),
1817
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-900", children: log.tableName }),
1818
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-900", children: log.recordId }),
1819
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 text-sm text-gray-500", children: /* @__PURE__ */ jsxs("details", { children: [
1820
+ /* @__PURE__ */ jsxs("summary", { className: "cursor-pointer", children: [
1821
+ Object.keys(log.oldValue || log.newValue || {}).length,
1822
+ " fields"
1823
+ ] }),
1824
+ /* @__PURE__ */ jsx("pre", { className: "mt-2 text-xs", children: JSON.stringify({ old: log.oldValue, new: log.newValue }, null, 2) })
1825
+ ] }) })
1826
+ ]
1827
+ },
1828
+ log.id
1829
+ )) })
1830
+ ] }) }),
1831
+ hasMore && /* @__PURE__ */ jsx("div", { className: "flex justify-center mt-4", children: /* @__PURE__ */ jsx(
1832
+ "button",
1833
+ {
1834
+ onClick: handleLoadMore,
1835
+ disabled: loading,
1836
+ className: "px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50",
1837
+ children: "Load More"
1838
+ }
1839
+ ) })
1840
+ ] }) });
1841
+ }
1842
+
1843
+ export { CookieConsent, PrivacyPolicy, TermsOfService, VocoAuditLog, VocoAuth, VocoButton, VocoForm, VocoInput };
1844
+ //# sourceMappingURL=react.mjs.map
1845
+ //# sourceMappingURL=react.mjs.map