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