@iqworksai/consentiq-react 0.1.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/index.js ADDED
@@ -0,0 +1,958 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ConsentGate: () => ConsentGate,
24
+ ConsentIQApiClient: () => ConsentIQApiClient,
25
+ ConsentIQProvider: () => ConsentIQProvider,
26
+ CookieBanner: () => CookieBanner,
27
+ PreferenceCenter: () => PreferenceCenter,
28
+ detectCountry: () => detectCountry,
29
+ detectLanguage: () => detectLanguage,
30
+ generateSubjectId: () => generateSubjectId,
31
+ getOrCreateSubjectId: () => getOrCreateSubjectId,
32
+ useConsent: () => useConsent,
33
+ useConsentGate: () => useConsentGate,
34
+ useConsentIQ: () => useConsentIQ,
35
+ withConsentGate: () => withConsentGate
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/provider.tsx
40
+ var import_react = require("react");
41
+
42
+ // src/api-client.ts
43
+ var DEFAULT_API_URL = "https://api.consentiq.io";
44
+ var ConsentIQApiClient = class {
45
+ constructor(propertyKey, apiUrl, debug = false) {
46
+ this.propertyKey = propertyKey;
47
+ this.apiUrl = apiUrl || DEFAULT_API_URL;
48
+ this.debug = debug;
49
+ }
50
+ log(...args) {
51
+ if (this.debug) {
52
+ console.log("[ConsentIQ]", ...args);
53
+ }
54
+ }
55
+ async fetch(url, options) {
56
+ const response = await fetch(url, {
57
+ ...options,
58
+ headers: {
59
+ "Content-Type": "application/json",
60
+ ...options?.headers
61
+ }
62
+ });
63
+ if (!response.ok) {
64
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
65
+ throw new Error(error.error || `HTTP ${response.status}`);
66
+ }
67
+ return response.json();
68
+ }
69
+ /**
70
+ * Fetch property configuration including banner settings and translations
71
+ */
72
+ async getConfig() {
73
+ this.log("Fetching config for property:", this.propertyKey);
74
+ const config = await this.fetch(
75
+ `${this.apiUrl}/api/public/config/${this.propertyKey}`
76
+ );
77
+ this.log("Config loaded:", config);
78
+ return config;
79
+ }
80
+ /**
81
+ * Get existing consent for a subject
82
+ */
83
+ async getConsent(subjectId) {
84
+ this.log("Fetching consent for subject:", subjectId);
85
+ const response = await this.fetch(
86
+ `${this.apiUrl}/api/public/consent/${this.propertyKey}/${subjectId}`
87
+ );
88
+ this.log("Consent loaded:", response.consent);
89
+ return response.consent;
90
+ }
91
+ /**
92
+ * Submit consent to the server
93
+ */
94
+ async submitConsent(data) {
95
+ this.log("Submitting consent:", data);
96
+ const response = await this.fetch(`${this.apiUrl}/api/public/consent`, {
97
+ method: "POST",
98
+ body: JSON.stringify({
99
+ propertyKey: this.propertyKey,
100
+ ...data
101
+ })
102
+ });
103
+ this.log("Consent submitted:", response);
104
+ return response;
105
+ }
106
+ /**
107
+ * Verify a consent record
108
+ */
109
+ async verifyConsent(consentId) {
110
+ return this.fetch(`${this.apiUrl}/api/public/verify/${consentId}`);
111
+ }
112
+ };
113
+ function generateSubjectId() {
114
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
115
+ return crypto.randomUUID();
116
+ }
117
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
118
+ const r = Math.random() * 16 | 0;
119
+ const v = c === "x" ? r : r & 3 | 8;
120
+ return v.toString(16);
121
+ });
122
+ }
123
+ function getOrCreateSubjectId() {
124
+ const STORAGE_KEY = "consentiq_subject_id";
125
+ if (typeof window === "undefined") {
126
+ return generateSubjectId();
127
+ }
128
+ try {
129
+ let subjectId = localStorage.getItem(STORAGE_KEY);
130
+ if (!subjectId) {
131
+ subjectId = generateSubjectId();
132
+ localStorage.setItem(STORAGE_KEY, subjectId);
133
+ }
134
+ return subjectId;
135
+ } catch {
136
+ return generateSubjectId();
137
+ }
138
+ }
139
+ function detectCountry() {
140
+ if (typeof navigator === "undefined") return void 0;
141
+ const language = navigator.language || navigator.userLanguage;
142
+ if (language) {
143
+ const parts = language.split("-");
144
+ if (parts.length > 1) {
145
+ return parts[1].toUpperCase();
146
+ }
147
+ }
148
+ return void 0;
149
+ }
150
+ function detectLanguage(supportedLanguages) {
151
+ if (typeof navigator === "undefined") return supportedLanguages[0] || "en";
152
+ const browserLanguages = navigator.languages || [navigator.language];
153
+ for (const lang of browserLanguages) {
154
+ const code = lang.split("-")[0].toLowerCase();
155
+ if (supportedLanguages.includes(code)) {
156
+ return code;
157
+ }
158
+ }
159
+ return supportedLanguages[0] || "en";
160
+ }
161
+
162
+ // src/provider.tsx
163
+ var import_jsx_runtime = require("react/jsx-runtime");
164
+ var ConsentIQContext = (0, import_react.createContext)(null);
165
+ var DEFAULT_CONSENT = {
166
+ necessary: true,
167
+ // Always true
168
+ analytics: false,
169
+ marketing: false,
170
+ preferences: false,
171
+ social: false
172
+ };
173
+ var CONSENT_STORAGE_KEY = "consentiq_consent";
174
+ function ConsentIQProvider({
175
+ children,
176
+ propertyKey,
177
+ apiUrl,
178
+ subjectId: providedSubjectId,
179
+ autoGenerateSubjectId = true,
180
+ onConsentChange,
181
+ onOpen,
182
+ onClose,
183
+ autoShow = true,
184
+ language: preferredLanguage,
185
+ debug = false
186
+ }) {
187
+ const [config, setConfig] = (0, import_react.useState)(null);
188
+ const [consent, setConsent] = (0, import_react.useState)(DEFAULT_CONSENT);
189
+ const [marketingConsent, setMarketingConsent] = (0, import_react.useState)({});
190
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
191
+ const [isBannerVisible, setIsBannerVisible] = (0, import_react.useState)(false);
192
+ const [isPreferenceCenterVisible, setIsPreferenceCenterVisible] = (0, import_react.useState)(false);
193
+ const [language, setLanguage] = (0, import_react.useState)("en");
194
+ const [regulation, setRegulation] = (0, import_react.useState)(null);
195
+ const [subjectId, setSubjectId] = (0, import_react.useState)("");
196
+ const [hasExistingConsent, setHasExistingConsent] = (0, import_react.useState)(false);
197
+ const apiClient = (0, import_react.useMemo)(
198
+ () => new ConsentIQApiClient(propertyKey, apiUrl, debug),
199
+ [propertyKey, apiUrl, debug]
200
+ );
201
+ const loadLocalConsent = (0, import_react.useCallback)(() => {
202
+ if (typeof window === "undefined") return null;
203
+ try {
204
+ const saved = localStorage.getItem(CONSENT_STORAGE_KEY);
205
+ if (saved) {
206
+ return JSON.parse(saved);
207
+ }
208
+ } catch {
209
+ }
210
+ return null;
211
+ }, []);
212
+ const saveLocalConsent = (0, import_react.useCallback)((consentData) => {
213
+ if (typeof window === "undefined") return;
214
+ try {
215
+ localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(consentData));
216
+ } catch {
217
+ }
218
+ }, []);
219
+ (0, import_react.useEffect)(() => {
220
+ async function initialize() {
221
+ try {
222
+ const sid = providedSubjectId || (autoGenerateSubjectId ? getOrCreateSubjectId() : "");
223
+ setSubjectId(sid);
224
+ const propertyConfig = await apiClient.getConfig();
225
+ setConfig(propertyConfig);
226
+ const detectedLang = preferredLanguage || detectLanguage(propertyConfig.property.supportedLanguages);
227
+ setLanguage(detectedLang);
228
+ const country = detectCountry();
229
+ if (country && propertyConfig.geoRules) {
230
+ for (const rule of propertyConfig.geoRules) {
231
+ if (rule.countries.includes(country)) {
232
+ setRegulation(rule.regulation);
233
+ break;
234
+ }
235
+ }
236
+ }
237
+ if (sid) {
238
+ const existingConsent = await apiClient.getConsent(sid).catch(() => null);
239
+ if (existingConsent) {
240
+ setConsent(existingConsent.cookieConsents);
241
+ if (existingConsent.marketingConsents) {
242
+ setMarketingConsent(existingConsent.marketingConsents);
243
+ }
244
+ setHasExistingConsent(true);
245
+ saveLocalConsent(existingConsent.cookieConsents);
246
+ } else {
247
+ const localConsent = loadLocalConsent();
248
+ if (localConsent) {
249
+ setConsent(localConsent);
250
+ setHasExistingConsent(true);
251
+ }
252
+ }
253
+ }
254
+ setIsLoading(false);
255
+ if (autoShow && !hasExistingConsent) {
256
+ setIsBannerVisible(true);
257
+ onOpen?.();
258
+ }
259
+ } catch (error) {
260
+ console.error("[ConsentIQ] Failed to initialize:", error);
261
+ setIsLoading(false);
262
+ }
263
+ }
264
+ initialize();
265
+ }, [
266
+ propertyKey,
267
+ apiClient,
268
+ providedSubjectId,
269
+ autoGenerateSubjectId,
270
+ preferredLanguage,
271
+ autoShow,
272
+ loadLocalConsent,
273
+ saveLocalConsent,
274
+ onOpen,
275
+ hasExistingConsent
276
+ ]);
277
+ const submitConsent = (0, import_react.useCallback)(
278
+ async (cookieConsents, marketingConsents) => {
279
+ if (!subjectId) return;
280
+ try {
281
+ await apiClient.submitConsent({
282
+ subjectId,
283
+ cookieConsents,
284
+ marketingConsents,
285
+ geoCountry: detectCountry(),
286
+ consentMethod: isPreferenceCenterVisible ? "preference_center" : "banner"
287
+ });
288
+ saveLocalConsent(cookieConsents);
289
+ onConsentChange?.(cookieConsents, marketingConsents);
290
+ } catch (error) {
291
+ console.error("[ConsentIQ] Failed to submit consent:", error);
292
+ }
293
+ },
294
+ [subjectId, apiClient, isPreferenceCenterVisible, saveLocalConsent, onConsentChange]
295
+ );
296
+ const hasConsent = (0, import_react.useCallback)(
297
+ (category) => {
298
+ return consent[category] === true;
299
+ },
300
+ [consent]
301
+ );
302
+ const hasMarketingConsent = (0, import_react.useCallback)(
303
+ (channel) => {
304
+ return marketingConsent[channel] === true;
305
+ },
306
+ [marketingConsent]
307
+ );
308
+ const acceptAll = (0, import_react.useCallback)(async () => {
309
+ const allAccepted = { ...DEFAULT_CONSENT };
310
+ if (config) {
311
+ for (const category of config.categories) {
312
+ if (category.enabled) {
313
+ allAccepted[category.code] = true;
314
+ }
315
+ }
316
+ } else {
317
+ allAccepted.analytics = true;
318
+ allAccepted.marketing = true;
319
+ allAccepted.preferences = true;
320
+ allAccepted.social = true;
321
+ }
322
+ setConsent(allAccepted);
323
+ await submitConsent(allAccepted, marketingConsent);
324
+ setIsBannerVisible(false);
325
+ setIsPreferenceCenterVisible(false);
326
+ onClose?.();
327
+ }, [config, marketingConsent, submitConsent, onClose]);
328
+ const rejectAll = (0, import_react.useCallback)(async () => {
329
+ const allRejected = {
330
+ necessary: true,
331
+ // Always true
332
+ analytics: false,
333
+ marketing: false,
334
+ preferences: false,
335
+ social: false
336
+ };
337
+ setConsent(allRejected);
338
+ await submitConsent(allRejected, {});
339
+ setIsBannerVisible(false);
340
+ setIsPreferenceCenterVisible(false);
341
+ onClose?.();
342
+ }, [submitConsent, onClose]);
343
+ const updateConsent = (0, import_react.useCallback)(
344
+ async (updates) => {
345
+ const newConsent = {
346
+ ...consent,
347
+ ...updates,
348
+ necessary: true
349
+ // Always enforce necessary
350
+ };
351
+ setConsent(newConsent);
352
+ await submitConsent(newConsent, marketingConsent);
353
+ },
354
+ [consent, marketingConsent, submitConsent]
355
+ );
356
+ const updateMarketingConsent = (0, import_react.useCallback)(
357
+ async (updates) => {
358
+ const newMarketingConsent = { ...marketingConsent, ...updates };
359
+ setMarketingConsent(newMarketingConsent);
360
+ await submitConsent(consent, newMarketingConsent);
361
+ },
362
+ [consent, marketingConsent, submitConsent]
363
+ );
364
+ const showBanner = (0, import_react.useCallback)(() => {
365
+ setIsBannerVisible(true);
366
+ onOpen?.();
367
+ }, [onOpen]);
368
+ const hideBanner = (0, import_react.useCallback)(() => {
369
+ setIsBannerVisible(false);
370
+ onClose?.();
371
+ }, [onClose]);
372
+ const showPreferenceCenter = (0, import_react.useCallback)(() => {
373
+ setIsPreferenceCenterVisible(true);
374
+ setIsBannerVisible(false);
375
+ onOpen?.();
376
+ }, [onOpen]);
377
+ const hidePreferenceCenter = (0, import_react.useCallback)(() => {
378
+ setIsPreferenceCenterVisible(false);
379
+ onClose?.();
380
+ }, [onClose]);
381
+ const resetConsent = (0, import_react.useCallback)(() => {
382
+ setConsent(DEFAULT_CONSENT);
383
+ setMarketingConsent({});
384
+ setHasExistingConsent(false);
385
+ if (typeof window !== "undefined") {
386
+ localStorage.removeItem(CONSENT_STORAGE_KEY);
387
+ localStorage.removeItem("consentiq_subject_id");
388
+ }
389
+ }, []);
390
+ const contextValue = (0, import_react.useMemo)(
391
+ () => ({
392
+ consent,
393
+ marketingConsent,
394
+ config,
395
+ isLoading,
396
+ isBannerVisible,
397
+ isPreferenceCenterVisible,
398
+ language,
399
+ regulation,
400
+ hasConsent,
401
+ hasMarketingConsent,
402
+ acceptAll,
403
+ rejectAll,
404
+ updateConsent,
405
+ updateMarketingConsent,
406
+ showBanner,
407
+ hideBanner,
408
+ showPreferenceCenter,
409
+ hidePreferenceCenter,
410
+ resetConsent
411
+ }),
412
+ [
413
+ consent,
414
+ marketingConsent,
415
+ config,
416
+ isLoading,
417
+ isBannerVisible,
418
+ isPreferenceCenterVisible,
419
+ language,
420
+ regulation,
421
+ hasConsent,
422
+ hasMarketingConsent,
423
+ acceptAll,
424
+ rejectAll,
425
+ updateConsent,
426
+ updateMarketingConsent,
427
+ showBanner,
428
+ hideBanner,
429
+ showPreferenceCenter,
430
+ hidePreferenceCenter,
431
+ resetConsent
432
+ ]
433
+ );
434
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConsentIQContext.Provider, { value: contextValue, children });
435
+ }
436
+ function useConsentIQ() {
437
+ const context = (0, import_react.useContext)(ConsentIQContext);
438
+ if (!context) {
439
+ throw new Error("useConsentIQ must be used within a ConsentIQProvider");
440
+ }
441
+ return context;
442
+ }
443
+ function useConsent(category) {
444
+ const { hasConsent } = useConsentIQ();
445
+ return hasConsent(category);
446
+ }
447
+ function useConsentGate(category) {
448
+ const { hasConsent, isLoading } = useConsentIQ();
449
+ return {
450
+ hasConsent: hasConsent(category),
451
+ isLoading
452
+ };
453
+ }
454
+
455
+ // src/components/CookieBanner.tsx
456
+ var import_jsx_runtime2 = require("react/jsx-runtime");
457
+ function CookieBanner({ className, position, theme, style }) {
458
+ const {
459
+ config,
460
+ isBannerVisible,
461
+ isLoading,
462
+ language,
463
+ acceptAll,
464
+ rejectAll,
465
+ showPreferenceCenter
466
+ } = useConsentIQ();
467
+ if (isLoading || !isBannerVisible || !config) {
468
+ return null;
469
+ }
470
+ const bannerConfig = config.banner;
471
+ const translations = config.translations[language] || config.translations[config.property.defaultLanguage] || {};
472
+ const resolvedPosition = position || bannerConfig.position || "bottom";
473
+ const resolvedTheme = theme || bannerConfig.theme || "dark";
474
+ const effectiveTheme = resolvedTheme === "auto" ? typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : resolvedTheme;
475
+ const positionStyles = {
476
+ position: "fixed",
477
+ zIndex: 9999,
478
+ ...resolvedPosition === "bottom" && { bottom: 0, left: 0, right: 0 },
479
+ ...resolvedPosition === "top" && { top: 0, left: 0, right: 0 },
480
+ ...resolvedPosition === "bottom-left" && { bottom: "1rem", left: "1rem", maxWidth: "400px" },
481
+ ...resolvedPosition === "bottom-right" && { bottom: "1rem", right: "1rem", maxWidth: "400px" },
482
+ ...resolvedPosition === "center" && {
483
+ top: "50%",
484
+ left: "50%",
485
+ transform: "translate(-50%, -50%)",
486
+ maxWidth: "500px",
487
+ width: "90%"
488
+ }
489
+ };
490
+ const themeStyles = {
491
+ backgroundColor: effectiveTheme === "dark" ? "#1e293b" : "#ffffff",
492
+ color: effectiveTheme === "dark" ? "#f1f5f9" : "#1e293b",
493
+ borderColor: effectiveTheme === "dark" ? "#334155" : "#e2e8f0"
494
+ };
495
+ const buttonPrimaryStyle = {
496
+ backgroundColor: bannerConfig.primaryColor || "#3b82f6",
497
+ color: "#ffffff",
498
+ padding: "0.5rem 1rem",
499
+ borderRadius: "0.5rem",
500
+ border: "none",
501
+ cursor: "pointer",
502
+ fontWeight: 500,
503
+ fontSize: "0.875rem"
504
+ };
505
+ const buttonSecondaryStyle = {
506
+ backgroundColor: effectiveTheme === "dark" ? "#475569" : "#e2e8f0",
507
+ color: effectiveTheme === "dark" ? "#f1f5f9" : "#1e293b",
508
+ padding: "0.5rem 1rem",
509
+ borderRadius: "0.5rem",
510
+ border: "none",
511
+ cursor: "pointer",
512
+ fontWeight: 500,
513
+ fontSize: "0.875rem"
514
+ };
515
+ const buttonLinkStyle = {
516
+ background: "none",
517
+ border: "none",
518
+ color: effectiveTheme === "dark" ? "#94a3b8" : "#64748b",
519
+ cursor: "pointer",
520
+ fontSize: "0.875rem",
521
+ padding: "0.5rem"
522
+ };
523
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
524
+ resolvedPosition === "center" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
525
+ "div",
526
+ {
527
+ style: {
528
+ position: "fixed",
529
+ inset: 0,
530
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
531
+ zIndex: 9998
532
+ }
533
+ }
534
+ ),
535
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
536
+ "div",
537
+ {
538
+ className,
539
+ style: {
540
+ ...positionStyles,
541
+ ...themeStyles,
542
+ padding: "1rem",
543
+ borderRadius: resolvedPosition === "bottom" || resolvedPosition === "top" ? 0 : "0.75rem",
544
+ border: "1px solid",
545
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
546
+ fontFamily: "system-ui, -apple-system, sans-serif",
547
+ ...style
548
+ },
549
+ role: "dialog",
550
+ "aria-label": "Cookie consent",
551
+ "aria-modal": resolvedPosition === "center",
552
+ children: [
553
+ bannerConfig.showLogo && bannerConfig.logoUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
554
+ "img",
555
+ {
556
+ src: bannerConfig.logoUrl,
557
+ alt: "",
558
+ style: { height: "1.5rem", marginBottom: "0.75rem" }
559
+ }
560
+ ),
561
+ translations.bannerTitle && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
562
+ "h2",
563
+ {
564
+ style: {
565
+ fontSize: "1rem",
566
+ fontWeight: 600,
567
+ marginBottom: "0.5rem",
568
+ margin: 0
569
+ },
570
+ children: translations.bannerTitle
571
+ }
572
+ ),
573
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
574
+ "p",
575
+ {
576
+ style: {
577
+ fontSize: "0.875rem",
578
+ lineHeight: 1.5,
579
+ margin: "0.5rem 0 1rem",
580
+ opacity: 0.9
581
+ },
582
+ children: translations.bannerDescription || "We use cookies to enhance your experience. By continuing, you agree to our use of cookies."
583
+ }
584
+ ),
585
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
586
+ "div",
587
+ {
588
+ style: {
589
+ display: "flex",
590
+ flexWrap: "wrap",
591
+ gap: "0.5rem",
592
+ alignItems: "center"
593
+ },
594
+ children: [
595
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
596
+ "button",
597
+ {
598
+ onClick: acceptAll,
599
+ style: buttonPrimaryStyle,
600
+ type: "button",
601
+ children: translations.acceptAllText || "Accept All"
602
+ }
603
+ ),
604
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
605
+ "button",
606
+ {
607
+ onClick: rejectAll,
608
+ style: buttonSecondaryStyle,
609
+ type: "button",
610
+ children: translations.rejectAllText || "Reject All"
611
+ }
612
+ ),
613
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
614
+ "button",
615
+ {
616
+ onClick: showPreferenceCenter,
617
+ style: buttonLinkStyle,
618
+ type: "button",
619
+ children: translations.customizeText || "Customize"
620
+ }
621
+ )
622
+ ]
623
+ }
624
+ )
625
+ ]
626
+ }
627
+ )
628
+ ] });
629
+ }
630
+
631
+ // src/components/PreferenceCenter.tsx
632
+ var import_react2 = require("react");
633
+ var import_jsx_runtime3 = require("react/jsx-runtime");
634
+ function PreferenceCenter({ className, theme, style }) {
635
+ const {
636
+ config,
637
+ consent,
638
+ isPreferenceCenterVisible,
639
+ isLoading,
640
+ language,
641
+ updateConsent,
642
+ hidePreferenceCenter
643
+ } = useConsentIQ();
644
+ const [localConsent, setLocalConsent] = (0, import_react2.useState)(consent);
645
+ (0, import_react2.useEffect)(() => {
646
+ setLocalConsent(consent);
647
+ }, [consent]);
648
+ if (isLoading || !isPreferenceCenterVisible || !config) {
649
+ return null;
650
+ }
651
+ const bannerConfig = config.banner;
652
+ const translations = config.translations[language] || config.translations[config.property.defaultLanguage] || {};
653
+ const resolvedTheme = theme || bannerConfig.theme || "dark";
654
+ const effectiveTheme = resolvedTheme === "auto" ? typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : resolvedTheme;
655
+ const isDark = effectiveTheme === "dark";
656
+ const handleToggle = (categoryCode) => {
657
+ setLocalConsent((prev) => ({
658
+ ...prev,
659
+ [categoryCode]: !prev[categoryCode]
660
+ }));
661
+ };
662
+ const handleSave = async () => {
663
+ await updateConsent(localConsent);
664
+ hidePreferenceCenter();
665
+ };
666
+ const handleAcceptAll = () => {
667
+ const allAccepted = {
668
+ necessary: true,
669
+ analytics: true,
670
+ marketing: true,
671
+ preferences: true,
672
+ social: true
673
+ };
674
+ for (const category of config.categories) {
675
+ if (category.enabled) {
676
+ allAccepted[category.code] = true;
677
+ }
678
+ }
679
+ setLocalConsent(allAccepted);
680
+ };
681
+ const handleRejectAll = () => {
682
+ const allRejected = {
683
+ necessary: true,
684
+ analytics: false,
685
+ marketing: false,
686
+ preferences: false,
687
+ social: false
688
+ };
689
+ for (const category of config.categories) {
690
+ allRejected[category.code] = category.isRequired;
691
+ }
692
+ setLocalConsent(allRejected);
693
+ };
694
+ const containerStyle = {
695
+ position: "fixed",
696
+ inset: 0,
697
+ zIndex: 9999,
698
+ display: "flex",
699
+ alignItems: "center",
700
+ justifyContent: "center",
701
+ padding: "1rem"
702
+ };
703
+ const backdropStyle = {
704
+ position: "absolute",
705
+ inset: 0,
706
+ backgroundColor: "rgba(0, 0, 0, 0.5)"
707
+ };
708
+ const modalStyle = {
709
+ position: "relative",
710
+ backgroundColor: isDark ? "#1e293b" : "#ffffff",
711
+ color: isDark ? "#f1f5f9" : "#1e293b",
712
+ borderRadius: "0.75rem",
713
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
714
+ maxWidth: "600px",
715
+ width: "100%",
716
+ maxHeight: "80vh",
717
+ overflow: "hidden",
718
+ display: "flex",
719
+ flexDirection: "column",
720
+ fontFamily: "system-ui, -apple-system, sans-serif",
721
+ ...style
722
+ };
723
+ const headerStyle = {
724
+ padding: "1rem 1.5rem",
725
+ borderBottom: `1px solid ${isDark ? "#334155" : "#e2e8f0"}`,
726
+ display: "flex",
727
+ justifyContent: "space-between",
728
+ alignItems: "center"
729
+ };
730
+ const bodyStyle = {
731
+ padding: "1.5rem",
732
+ overflowY: "auto",
733
+ flex: 1
734
+ };
735
+ const footerStyle = {
736
+ padding: "1rem 1.5rem",
737
+ borderTop: `1px solid ${isDark ? "#334155" : "#e2e8f0"}`,
738
+ display: "flex",
739
+ gap: "0.5rem",
740
+ justifyContent: "flex-end"
741
+ };
742
+ const categoryStyle = {
743
+ padding: "1rem",
744
+ backgroundColor: isDark ? "#0f172a" : "#f8fafc",
745
+ borderRadius: "0.5rem",
746
+ marginBottom: "0.75rem"
747
+ };
748
+ const toggleContainerStyle = {
749
+ display: "flex",
750
+ alignItems: "center",
751
+ justifyContent: "space-between"
752
+ };
753
+ const buttonPrimaryStyle = {
754
+ backgroundColor: bannerConfig.primaryColor || "#3b82f6",
755
+ color: "#ffffff",
756
+ padding: "0.5rem 1rem",
757
+ borderRadius: "0.5rem",
758
+ border: "none",
759
+ cursor: "pointer",
760
+ fontWeight: 500,
761
+ fontSize: "0.875rem"
762
+ };
763
+ const buttonSecondaryStyle = {
764
+ backgroundColor: isDark ? "#475569" : "#e2e8f0",
765
+ color: isDark ? "#f1f5f9" : "#1e293b",
766
+ padding: "0.5rem 1rem",
767
+ borderRadius: "0.5rem",
768
+ border: "none",
769
+ cursor: "pointer",
770
+ fontWeight: 500,
771
+ fontSize: "0.875rem"
772
+ };
773
+ const buttonTextStyle = {
774
+ background: "none",
775
+ border: "none",
776
+ color: isDark ? "#94a3b8" : "#64748b",
777
+ cursor: "pointer",
778
+ fontSize: "0.875rem",
779
+ padding: "0.5rem"
780
+ };
781
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: containerStyle, className, children: [
782
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: backdropStyle, onClick: hidePreferenceCenter }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-label": "Cookie preferences", children: [
784
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: headerStyle, children: [
785
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: { margin: 0, fontSize: "1.25rem", fontWeight: 600 }, children: "Cookie Preferences" }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
787
+ "button",
788
+ {
789
+ onClick: hidePreferenceCenter,
790
+ style: {
791
+ background: "none",
792
+ border: "none",
793
+ fontSize: "1.5rem",
794
+ cursor: "pointer",
795
+ color: isDark ? "#94a3b8" : "#64748b",
796
+ lineHeight: 1
797
+ },
798
+ "aria-label": "Close",
799
+ type: "button",
800
+ children: "\xD7"
801
+ }
802
+ )
803
+ ] }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: bodyStyle, children: [
805
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { marginTop: 0, marginBottom: "1.5rem", opacity: 0.8, fontSize: "0.875rem" }, children: translations.bannerDescription || "Manage your cookie preferences. Some cookies are essential for the site to function properly." }),
806
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "0.5rem", marginBottom: "1.5rem" }, children: [
807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleAcceptAll, style: buttonTextStyle, type: "button", children: "Enable All" }),
808
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleRejectAll, style: buttonTextStyle, type: "button", children: "Disable All" })
809
+ ] }),
810
+ config.categories.filter((cat) => cat.enabled).map((category) => {
811
+ const categoryTranslation = translations.categories?.[category.code];
812
+ const isEnabled = localConsent[category.code] ?? category.defaultState;
813
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: categoryStyle, children: [
814
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: toggleContainerStyle, children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
816
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
817
+ "h3",
818
+ {
819
+ style: {
820
+ margin: 0,
821
+ fontSize: "0.95rem",
822
+ fontWeight: 600
823
+ },
824
+ children: categoryTranslation?.title || category.name
825
+ }
826
+ ),
827
+ category.isRequired && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
828
+ "span",
829
+ {
830
+ style: {
831
+ fontSize: "0.75rem",
832
+ color: isDark ? "#94a3b8" : "#64748b",
833
+ marginLeft: "0.5rem"
834
+ },
835
+ children: "(Required)"
836
+ }
837
+ )
838
+ ] }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
840
+ Toggle,
841
+ {
842
+ checked: isEnabled,
843
+ onChange: () => handleToggle(category.code),
844
+ disabled: category.isRequired,
845
+ primaryColor: bannerConfig.primaryColor
846
+ }
847
+ )
848
+ ] }),
849
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
850
+ "p",
851
+ {
852
+ style: {
853
+ margin: "0.5rem 0 0",
854
+ fontSize: "0.8rem",
855
+ opacity: 0.7
856
+ },
857
+ children: categoryTranslation?.description || category.description
858
+ }
859
+ )
860
+ ] }, category.code);
861
+ })
862
+ ] }),
863
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: footerStyle, children: [
864
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: hidePreferenceCenter, style: buttonSecondaryStyle, type: "button", children: "Cancel" }),
865
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleSave, style: buttonPrimaryStyle, type: "button", children: translations.savePreferencesText || "Save Preferences" })
866
+ ] })
867
+ ] })
868
+ ] });
869
+ }
870
+ function Toggle({
871
+ checked,
872
+ onChange,
873
+ disabled,
874
+ primaryColor
875
+ }) {
876
+ const trackStyle = {
877
+ position: "relative",
878
+ width: "44px",
879
+ height: "24px",
880
+ backgroundColor: checked ? primaryColor || "#3b82f6" : "#94a3b8",
881
+ borderRadius: "12px",
882
+ cursor: disabled ? "not-allowed" : "pointer",
883
+ opacity: disabled ? 0.5 : 1,
884
+ transition: "background-color 0.2s"
885
+ };
886
+ const thumbStyle = {
887
+ position: "absolute",
888
+ top: "2px",
889
+ left: checked ? "22px" : "2px",
890
+ width: "20px",
891
+ height: "20px",
892
+ backgroundColor: "#ffffff",
893
+ borderRadius: "50%",
894
+ transition: "left 0.2s",
895
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
896
+ };
897
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
898
+ "button",
899
+ {
900
+ type: "button",
901
+ role: "switch",
902
+ "aria-checked": checked,
903
+ onClick: disabled ? void 0 : onChange,
904
+ style: trackStyle,
905
+ disabled,
906
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: thumbStyle })
907
+ }
908
+ );
909
+ }
910
+
911
+ // src/components/ConsentGate.tsx
912
+ var import_jsx_runtime4 = require("react/jsx-runtime");
913
+ function ConsentGate({
914
+ category,
915
+ children,
916
+ fallback = null,
917
+ loading = null
918
+ }) {
919
+ const { hasConsent, isLoading } = useConsentGate(category);
920
+ if (isLoading) {
921
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: loading });
922
+ }
923
+ if (hasConsent) {
924
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
925
+ }
926
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fallback });
927
+ }
928
+ function withConsentGate(Component, category, FallbackComponent) {
929
+ return function ConsentGatedComponent(props) {
930
+ const { hasConsent, isLoading } = useConsentGate(category);
931
+ if (isLoading) {
932
+ return null;
933
+ }
934
+ if (hasConsent) {
935
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Component, { ...props });
936
+ }
937
+ if (FallbackComponent) {
938
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FallbackComponent, { ...props });
939
+ }
940
+ return null;
941
+ };
942
+ }
943
+ // Annotate the CommonJS export names for ESM import in node:
944
+ 0 && (module.exports = {
945
+ ConsentGate,
946
+ ConsentIQApiClient,
947
+ ConsentIQProvider,
948
+ CookieBanner,
949
+ PreferenceCenter,
950
+ detectCountry,
951
+ detectLanguage,
952
+ generateSubjectId,
953
+ getOrCreateSubjectId,
954
+ useConsent,
955
+ useConsentGate,
956
+ useConsentIQ,
957
+ withConsentGate
958
+ });