react-lgpd-consent 0.2.1 → 0.2.2

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.
@@ -8,10 +8,208 @@ import FormControlLabel from "@mui/material/FormControlLabel";
8
8
  import FormGroup from "@mui/material/FormGroup";
9
9
  import Switch from "@mui/material/Switch";
10
10
  import Typography2 from "@mui/material/Typography";
11
- import { useEffect as useEffect2, useState as useState2 } from "react";
11
+ import { useEffect as useEffect4, useState as useState2 } from "react";
12
12
 
13
- // src/context/ConsentContext.tsx
13
+ // src/context/CategoriesContext.tsx
14
14
  import * as React2 from "react";
15
+
16
+ // src/utils/developerGuidance.ts
17
+ import * as React from "react";
18
+ var DEFAULT_PROJECT_CATEGORIES = {
19
+ enabledCategories: ["analytics"],
20
+ // Só analytics além de necessary
21
+ customCategories: []
22
+ };
23
+ function analyzeDeveloperConfiguration(config) {
24
+ const guidance = {
25
+ warnings: [],
26
+ suggestions: [],
27
+ activeCategoriesInfo: [],
28
+ usingDefaults: !config
29
+ };
30
+ const finalConfig = config || DEFAULT_PROJECT_CATEGORIES;
31
+ if (!config) {
32
+ guidance.warnings.push(
33
+ 'LGPD-CONSENT: Nenhuma configura\xE7\xE3o de categorias especificada. Usando padr\xE3o: necessary + analytics. Para produ\xE7\xE3o, recomenda-se especificar explicitamente as categorias via prop "categories".'
34
+ );
35
+ }
36
+ guidance.activeCategoriesInfo.push({
37
+ id: "necessary",
38
+ name: "Cookies Necess\xE1rios",
39
+ description: "Essenciais para funcionamento b\xE1sico do site",
40
+ essential: true,
41
+ uiRequired: false
42
+ // Não precisa de toggle (sempre ativo)
43
+ });
44
+ const enabledCategories = finalConfig.enabledCategories || [];
45
+ const categoryNames = {
46
+ analytics: {
47
+ name: "Cookies Anal\xEDticos",
48
+ description: "Medem uso e performance do site"
49
+ },
50
+ functional: {
51
+ name: "Cookies Funcionais",
52
+ description: "Melhoram experi\xEAncia e funcionalidades"
53
+ },
54
+ marketing: {
55
+ name: "Cookies de Marketing",
56
+ description: "Publicidade direcionada e campanhas"
57
+ },
58
+ social: {
59
+ name: "Cookies de Redes Sociais",
60
+ description: "Integra\xE7\xE3o com plataformas sociais"
61
+ },
62
+ personalization: {
63
+ name: "Cookies de Personaliza\xE7\xE3o",
64
+ description: "Adaptam conte\xFAdo \xE0s prefer\xEAncias do usu\xE1rio"
65
+ }
66
+ };
67
+ enabledCategories.forEach((categoryId) => {
68
+ const categoryInfo = categoryNames[categoryId];
69
+ if (categoryInfo) {
70
+ guidance.activeCategoriesInfo.push({
71
+ id: categoryId,
72
+ name: categoryInfo.name,
73
+ description: categoryInfo.description,
74
+ essential: false,
75
+ uiRequired: true
76
+ // Precisa de toggle na UI
77
+ });
78
+ }
79
+ });
80
+ const customCategories = finalConfig.customCategories || [];
81
+ customCategories.forEach((category) => {
82
+ guidance.activeCategoriesInfo.push({
83
+ id: category.id,
84
+ name: category.name,
85
+ description: category.description,
86
+ essential: category.essential === true,
87
+ uiRequired: category.essential !== true
88
+ // Apenas não-essenciais precisam toggle
89
+ });
90
+ });
91
+ const totalToggleable = guidance.activeCategoriesInfo.filter(
92
+ (c) => c.uiRequired
93
+ ).length;
94
+ if (totalToggleable === 0) {
95
+ guidance.suggestions.push(
96
+ 'Apenas cookies necess\xE1rios est\xE3o configurados. Para compliance completo LGPD, considere adicionar categorias como "analytics" ou "functional" conforme uso real.'
97
+ );
98
+ }
99
+ if (totalToggleable > 5) {
100
+ guidance.warnings.push(
101
+ `${totalToggleable} categorias opcionais detectadas. UI com muitas op\xE7\xF5es pode ' +
102
+ 'prejudicar experi\xEAncia do usu\xE1rio. Considere agrupar categorias similares.`
103
+ );
104
+ }
105
+ const poorDescriptions = customCategories.filter(
106
+ (c) => !c.description || c.description.length < 10
107
+ );
108
+ if (poorDescriptions.length > 0) {
109
+ guidance.warnings.push(
110
+ `Categorias customizadas com descri\xE7\xF5es inadequadas: ${poorDescriptions.map((c) => c.id).join(", ")}. Descri\xE7\xF5es claras s\xE3o obrigat\xF3rias para compliance LGPD.`
111
+ );
112
+ }
113
+ return guidance;
114
+ }
115
+ function logDeveloperGuidance(guidance) {
116
+ const isProduction = typeof globalThis !== "undefined" && globalThis.__LGPD_PRODUCTION__ || typeof window !== "undefined" && !window.__LGPD_DEV__;
117
+ if (isProduction) return;
118
+ if (guidance.warnings.length > 0) {
119
+ console.group("\u{1F7E8} LGPD-CONSENT: Avisos de Configura\xE7\xE3o");
120
+ guidance.warnings.forEach((warning) => console.warn(warning));
121
+ console.groupEnd();
122
+ }
123
+ if (guidance.suggestions.length > 0) {
124
+ console.group("\u{1F4A1} LGPD-CONSENT: Sugest\xF5es");
125
+ guidance.suggestions.forEach((suggestion) => console.info(suggestion));
126
+ console.groupEnd();
127
+ }
128
+ if (guidance.usingDefaults) {
129
+ console.info(
130
+ '\u{1F4CB} LGPD-CONSENT: Usando configura\xE7\xE3o padr\xE3o. Para personalizar, use a prop "categories" no ConsentProvider.'
131
+ );
132
+ }
133
+ console.group("\u{1F527} LGPD-CONSENT: Categorias Ativas (para UI customizada)");
134
+ console.table(
135
+ guidance.activeCategoriesInfo.map((cat) => ({
136
+ ID: cat.id,
137
+ Nome: cat.name,
138
+ "Toggle UI?": cat.uiRequired ? "\u2705 SIM" : "\u274C N\xC3O (sempre ativo)",
139
+ "Essencial?": cat.essential ? "\u{1F512} SIM" : "\u2699\uFE0F N\xC3O"
140
+ }))
141
+ );
142
+ console.groupEnd();
143
+ }
144
+
145
+ // src/context/CategoriesContext.tsx
146
+ import { jsx } from "react/jsx-runtime";
147
+ var CategoriesContext = React2.createContext(
148
+ null
149
+ );
150
+ var CategoriesCtx = React2.createContext([]);
151
+ function CategoriesProvider({
152
+ children,
153
+ categories,
154
+ // LEGACY: prop antiga (apenas customCategories)
155
+ config
156
+ // NOVO: configuração completa
157
+ }) {
158
+ const contextValue = React2.useMemo(() => {
159
+ let finalConfig;
160
+ if (categories && !config) {
161
+ finalConfig = {
162
+ enabledCategories: DEFAULT_PROJECT_CATEGORIES.enabledCategories,
163
+ customCategories: categories
164
+ };
165
+ } else {
166
+ finalConfig = config || DEFAULT_PROJECT_CATEGORIES;
167
+ }
168
+ const guidance = analyzeDeveloperConfiguration(
169
+ config || (categories ? { customCategories: categories } : void 0)
170
+ );
171
+ const toggleableCategories = guidance.activeCategoriesInfo.filter(
172
+ (cat) => cat.uiRequired
173
+ );
174
+ return {
175
+ config: finalConfig,
176
+ guidance,
177
+ toggleableCategories,
178
+ allCategories: guidance.activeCategoriesInfo,
179
+ legacyCategories: categories || []
180
+ };
181
+ }, [config, categories]);
182
+ React2.useEffect(() => {
183
+ logDeveloperGuidance(contextValue.guidance);
184
+ }, [contextValue.guidance]);
185
+ return /* @__PURE__ */ jsx(CategoriesContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(CategoriesCtx.Provider, { value: contextValue.legacyCategories, children }) });
186
+ }
187
+ function useCategories() {
188
+ const context = React2.useContext(CategoriesContext);
189
+ if (!context) {
190
+ throw new Error(
191
+ "useCategories deve ser usado dentro de CategoriesProvider. Certifique-se de que o ConsentProvider est\xE1 envolvendo seu componente."
192
+ );
193
+ }
194
+ return context;
195
+ }
196
+ function useCategoryStatus(categoryId) {
197
+ const { allCategories } = useCategories();
198
+ const category = allCategories.find((cat) => cat.id === categoryId);
199
+ return {
200
+ isActive: !!category,
201
+ isEssential: category?.essential || false,
202
+ needsToggle: category?.uiRequired || false,
203
+ name: category?.name,
204
+ description: category?.description
205
+ };
206
+ }
207
+ function useCustomCategories() {
208
+ return React2.useContext(CategoriesCtx);
209
+ }
210
+
211
+ // src/context/ConsentContext.tsx
212
+ import * as React3 from "react";
15
213
  import { ThemeProvider } from "@mui/material/styles";
16
214
 
17
215
  // src/utils/cookieUtils.ts
@@ -158,75 +356,10 @@ var defaultConsentTheme = createTheme({
158
356
  }
159
357
  });
160
358
 
161
- // src/context/CategoriesContext.tsx
162
- import * as React from "react";
163
- import { jsx } from "react/jsx-runtime";
164
- var CategoriesCtx = React.createContext([]);
165
- function CategoriesProvider({
166
- categories,
167
- children
168
- }) {
169
- const value = React.useMemo(() => categories || [], [categories]);
170
- return /* @__PURE__ */ jsx(CategoriesCtx.Provider, { value, children });
171
- }
172
- function useCustomCategories() {
173
- return React.useContext(CategoriesCtx);
174
- }
175
- function useAllCategories() {
176
- const customCategories = useCustomCategories();
177
- return React.useMemo(() => {
178
- const defaultCategories = [
179
- {
180
- id: "necessary",
181
- name: "Cookies Necess\xE1rios",
182
- description: "Essenciais para o funcionamento b\xE1sico do site. Incluem cookies de sess\xE3o, autentica\xE7\xE3o e seguran\xE7a.",
183
- essential: true,
184
- cookies: ["PHPSESSID", "JSESSIONID", "cookieConsent", "csrf_token"]
185
- },
186
- {
187
- id: "analytics",
188
- name: "Analytics e Estat\xEDsticas",
189
- description: "Permitem medir audi\xEAncia e desempenho, gerando estat\xEDsticas an\xF4nimas de uso.",
190
- essential: false,
191
- cookies: ["_ga", "_ga_*", "_gid", "_gat", "gtag"]
192
- },
193
- {
194
- id: "functional",
195
- name: "Cookies Funcionais",
196
- description: "Melhoram a experi\xEAncia do usu\xE1rio, lembrando prefer\xEAncias e configura\xE7\xF5es.",
197
- essential: false,
198
- cookies: ["language", "theme", "timezone", "preferences"]
199
- },
200
- {
201
- id: "marketing",
202
- name: "Marketing e Publicidade",
203
- description: "Utilizados para publicidade direcionada e medi\xE7\xE3o de campanhas publicit\xE1rias.",
204
- essential: false,
205
- cookies: ["_fbp", "fr", "tr", "ads_*", "doubleclick"]
206
- },
207
- {
208
- id: "social",
209
- name: "Redes Sociais",
210
- description: "Permitem compartilhamento e integra\xE7\xE3o com redes sociais como Facebook, YouTube, etc.",
211
- essential: false,
212
- cookies: ["__Secure-*", "sb", "datr", "c_user", "social_*"]
213
- },
214
- {
215
- id: "personalization",
216
- name: "Personaliza\xE7\xE3o",
217
- description: "Adaptam o conte\xFAdo e interface \xE0s prefer\xEAncias individuais do usu\xE1rio.",
218
- essential: false,
219
- cookies: ["personalization_*", "content_*", "layout_*"]
220
- }
221
- ];
222
- return [...defaultCategories, ...customCategories];
223
- }, [customCategories]);
224
- }
225
-
226
359
  // src/context/ConsentContext.tsx
227
360
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
228
- var PreferencesModal = React2.lazy(
229
- () => import("./PreferencesModal-4WHKT67T.js").then((m) => ({
361
+ var PreferencesModal = React3.lazy(
362
+ () => import("./PreferencesModal-UL552BFP.js").then((m) => ({
230
363
  default: m.PreferencesModal
231
364
  }))
232
365
  );
@@ -346,15 +479,18 @@ function reducer(state, action) {
346
479
  return state;
347
480
  }
348
481
  }
349
- var StateCtx = React2.createContext(null);
350
- var ActionsCtx = React2.createContext(null);
351
- var TextsCtx = React2.createContext(DEFAULT_TEXTS);
352
- var HydrationCtx = React2.createContext(false);
482
+ var StateCtx = React3.createContext(null);
483
+ var ActionsCtx = React3.createContext(null);
484
+ var TextsCtx = React3.createContext(DEFAULT_TEXTS);
485
+ var HydrationCtx = React3.createContext(false);
353
486
  function ConsentProvider({
354
487
  initialState,
488
+ categories,
489
+ // NOVO: configuração completa de categorias
355
490
  texts: textsProp,
356
491
  theme,
357
492
  customCategories,
493
+ // LEGACY: compatibilidade
358
494
  scriptIntegrations,
359
495
  PreferencesModalComponent,
360
496
  preferencesModalProps = {},
@@ -365,19 +501,30 @@ function ConsentProvider({
365
501
  cookie: cookieOpts,
366
502
  children
367
503
  }) {
368
- const texts = React2.useMemo(
504
+ const texts = React3.useMemo(
369
505
  () => ({ ...DEFAULT_TEXTS, ...textsProp ?? {} }),
370
506
  [textsProp]
371
507
  );
372
- const cookie = React2.useMemo(
508
+ const cookie = React3.useMemo(
373
509
  () => ({ ...DEFAULT_COOKIE_OPTS, ...cookieOpts ?? {} }),
374
510
  [cookieOpts]
375
511
  );
376
- const appliedTheme = React2.useMemo(
512
+ const appliedTheme = React3.useMemo(
377
513
  () => theme || defaultConsentTheme,
378
514
  [theme]
379
515
  );
380
- const boot = React2.useMemo(() => {
516
+ const finalCategoriesConfig = React3.useMemo(() => {
517
+ if (categories) return categories;
518
+ if (customCategories) {
519
+ return {
520
+ enabledCategories: ["analytics"],
521
+ // padrão quando usando API antiga
522
+ customCategories
523
+ };
524
+ }
525
+ return void 0;
526
+ }, [categories, customCategories]);
527
+ const boot = React3.useMemo(() => {
381
528
  if (initialState) return { ...initialState, isModalOpen: false };
382
529
  return createFullConsentState(
383
530
  false,
@@ -386,9 +533,9 @@ function ConsentProvider({
386
533
  false
387
534
  );
388
535
  }, [initialState, customCategories]);
389
- const [state, dispatch] = React2.useReducer(reducer, boot);
390
- const [isHydrated, setIsHydrated] = React2.useState(false);
391
- React2.useEffect(() => {
536
+ const [state, dispatch] = React3.useReducer(reducer, boot);
537
+ const [isHydrated, setIsHydrated] = React3.useState(false);
538
+ React3.useEffect(() => {
392
539
  if (!initialState) {
393
540
  const saved = readConsentCookie(cookie.name);
394
541
  if (saved?.consented) {
@@ -398,24 +545,24 @@ function ConsentProvider({
398
545
  }
399
546
  setIsHydrated(true);
400
547
  }, [cookie.name, initialState]);
401
- React2.useEffect(() => {
548
+ React3.useEffect(() => {
402
549
  if (state.consented) writeConsentCookie(state, state.source, cookie);
403
550
  }, [state, cookie]);
404
- const prevConsented = React2.useRef(state.consented);
405
- React2.useEffect(() => {
551
+ const prevConsented = React3.useRef(state.consented);
552
+ React3.useEffect(() => {
406
553
  if (!prevConsented.current && state.consented && onConsentGiven) {
407
554
  setTimeout(() => onConsentGiven(state), 150);
408
555
  }
409
556
  prevConsented.current = state.consented;
410
557
  }, [state, onConsentGiven]);
411
- const prevPrefs = React2.useRef(state.preferences);
412
- React2.useEffect(() => {
558
+ const prevPrefs = React3.useRef(state.preferences);
559
+ React3.useEffect(() => {
413
560
  if (state.consented && onPreferencesSaved && prevPrefs.current !== state.preferences) {
414
561
  setTimeout(() => onPreferencesSaved(state.preferences), 150);
415
562
  prevPrefs.current = state.preferences;
416
563
  }
417
564
  }, [state, onPreferencesSaved]);
418
- const api = React2.useMemo(() => {
565
+ const api = React3.useMemo(() => {
419
566
  const acceptAll = () => dispatch({ type: "ACCEPT_ALL", customCategories });
420
567
  const rejectAll = () => dispatch({ type: "REJECT_ALL", customCategories });
421
568
  const setPreference = (category, value) => dispatch({ type: "SET_CATEGORY", category, value });
@@ -439,29 +586,36 @@ function ConsentProvider({
439
586
  resetConsent
440
587
  };
441
588
  }, [state, cookie, customCategories]);
442
- return /* @__PURE__ */ jsx2(ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ jsx2(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx2(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsx2(TextsCtx.Provider, { value: texts, children: /* @__PURE__ */ jsx2(HydrationCtx.Provider, { value: isHydrated, children: /* @__PURE__ */ jsxs(CategoriesProvider, { categories: customCategories, children: [
443
- children,
444
- !disableAutomaticModal && /* @__PURE__ */ jsx2(React2.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ jsx2(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ jsx2(PreferencesModal, { hideBranding }) })
445
- ] }) }) }) }) }) });
589
+ return /* @__PURE__ */ jsx2(ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ jsx2(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx2(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsx2(TextsCtx.Provider, { value: texts, children: /* @__PURE__ */ jsx2(HydrationCtx.Provider, { value: isHydrated, children: /* @__PURE__ */ jsxs(
590
+ CategoriesProvider,
591
+ {
592
+ config: finalCategoriesConfig,
593
+ categories: customCategories,
594
+ children: [
595
+ children,
596
+ !disableAutomaticModal && /* @__PURE__ */ jsx2(React3.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ jsx2(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ jsx2(PreferencesModal, { hideBranding }) })
597
+ ]
598
+ }
599
+ ) }) }) }) }) });
446
600
  }
447
601
  function useConsentStateInternal() {
448
- const ctx = React2.useContext(StateCtx);
602
+ const ctx = React3.useContext(StateCtx);
449
603
  if (!ctx)
450
604
  throw new Error("useConsentState must be used within ConsentProvider");
451
605
  return ctx;
452
606
  }
453
607
  function useConsentActionsInternal() {
454
- const ctx = React2.useContext(ActionsCtx);
608
+ const ctx = React3.useContext(ActionsCtx);
455
609
  if (!ctx)
456
610
  throw new Error("useConsentActions must be used within ConsentProvider");
457
611
  return ctx;
458
612
  }
459
613
  function useConsentTextsInternal() {
460
- const ctx = React2.useContext(TextsCtx);
614
+ const ctx = React3.useContext(TextsCtx);
461
615
  return ctx;
462
616
  }
463
617
  function useConsentHydrationInternal() {
464
- return React2.useContext(HydrationCtx);
618
+ return React3.useContext(HydrationCtx);
465
619
  }
466
620
 
467
621
  // src/hooks/useConsent.ts
@@ -555,12 +709,25 @@ function PreferencesModal2({
555
709
  }) {
556
710
  const { preferences, setPreferences, closePreferences, isModalOpen } = useConsent();
557
711
  const texts = useConsentTexts();
558
- const [tempPreferences, setTempPreferences] = useState2(preferences);
559
- useEffect2(() => {
712
+ const { toggleableCategories } = useCategories();
713
+ const [tempPreferences, setTempPreferences] = useState2(
714
+ () => {
715
+ const initialPrefs = { necessary: true };
716
+ toggleableCategories.forEach((category) => {
717
+ initialPrefs[category.id] = preferences[category.id] ?? false;
718
+ });
719
+ return initialPrefs;
720
+ }
721
+ );
722
+ useEffect4(() => {
560
723
  if (isModalOpen) {
561
- setTempPreferences(preferences);
724
+ const syncedPrefs = { necessary: true };
725
+ toggleableCategories.forEach((category) => {
726
+ syncedPrefs[category.id] = preferences[category.id] ?? false;
727
+ });
728
+ setTempPreferences(syncedPrefs);
562
729
  }
563
- }, [isModalOpen, preferences]);
730
+ }, [isModalOpen, preferences, toggleableCategories]);
564
731
  const open = DialogProps2?.open ?? isModalOpen ?? false;
565
732
  const handleSave = () => {
566
733
  setPreferences(tempPreferences);
@@ -581,38 +748,23 @@ function PreferencesModal2({
581
748
  /* @__PURE__ */ jsxs3(DialogContent, { dividers: true, children: [
582
749
  /* @__PURE__ */ jsx4(Typography2, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
583
750
  /* @__PURE__ */ jsxs3(FormGroup, { children: [
584
- /* @__PURE__ */ jsx4(
751
+ toggleableCategories.map((category) => /* @__PURE__ */ jsx4(
585
752
  FormControlLabel,
586
753
  {
587
754
  control: /* @__PURE__ */ jsx4(
588
755
  Switch,
589
756
  {
590
- checked: tempPreferences.analytics,
757
+ checked: tempPreferences[category.id] ?? false,
591
758
  onChange: (e) => setTempPreferences((prev) => ({
592
759
  ...prev,
593
- analytics: e.target.checked
760
+ [category.id]: e.target.checked
594
761
  }))
595
762
  }
596
763
  ),
597
- label: "Cookies Anal\xEDticos (medem uso do site)"
598
- }
599
- ),
600
- /* @__PURE__ */ jsx4(
601
- FormControlLabel,
602
- {
603
- control: /* @__PURE__ */ jsx4(
604
- Switch,
605
- {
606
- checked: tempPreferences.marketing,
607
- onChange: (e) => setTempPreferences((prev) => ({
608
- ...prev,
609
- marketing: e.target.checked
610
- }))
611
- }
612
- ),
613
- label: "Cookies de Marketing/Publicidade"
614
- }
615
- ),
764
+ label: `${category.name} - ${category.description}`
765
+ },
766
+ category.id
767
+ )),
616
768
  /* @__PURE__ */ jsx4(
617
769
  FormControlLabel,
618
770
  {
@@ -634,8 +786,11 @@ function PreferencesModal2({
634
786
 
635
787
  export {
636
788
  defaultConsentTheme,
789
+ DEFAULT_PROJECT_CATEGORIES,
790
+ analyzeDeveloperConfiguration,
791
+ useCategories,
792
+ useCategoryStatus,
637
793
  useCustomCategories,
638
- useAllCategories,
639
794
  Branding,
640
795
  PreferencesModal2 as PreferencesModal,
641
796
  ConsentProvider,