@grainql/analytics-web 1.7.4 → 2.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.
Files changed (258) hide show
  1. package/README.md +71 -718
  2. package/dist/activity.d.ts +59 -0
  3. package/dist/activity.d.ts.map +1 -0
  4. package/dist/cjs/activity.d.ts +59 -0
  5. package/dist/cjs/activity.d.ts.map +1 -0
  6. package/dist/cjs/activity.js +131 -0
  7. package/dist/cjs/activity.js.map +1 -0
  8. package/dist/cjs/consent.d.ts +68 -0
  9. package/dist/cjs/consent.d.ts.map +1 -0
  10. package/dist/cjs/consent.js +191 -0
  11. package/dist/cjs/consent.js.map +1 -0
  12. package/dist/cjs/cookies.d.ts +28 -0
  13. package/dist/cjs/cookies.d.ts.map +1 -0
  14. package/dist/cjs/cookies.js +95 -0
  15. package/dist/cjs/cookies.js.map +1 -0
  16. package/dist/cjs/heartbeat.d.ts +42 -0
  17. package/dist/cjs/heartbeat.d.ts.map +1 -0
  18. package/dist/cjs/heartbeat.js +92 -0
  19. package/dist/cjs/heartbeat.js.map +1 -0
  20. package/dist/cjs/index.d.ts +100 -3
  21. package/dist/cjs/index.d.ts.map +1 -1
  22. package/dist/cjs/index.js.map +1 -1
  23. package/dist/cjs/page-tracking.d.ts +60 -0
  24. package/dist/cjs/page-tracking.d.ts.map +1 -0
  25. package/dist/cjs/page-tracking.js +180 -0
  26. package/dist/cjs/page-tracking.js.map +1 -0
  27. package/dist/cjs/react/GrainProvider.d.ts +11 -0
  28. package/dist/cjs/react/GrainProvider.d.ts.map +1 -0
  29. package/dist/cjs/react/GrainProvider.js +79 -0
  30. package/dist/cjs/react/GrainProvider.js.map +1 -0
  31. package/dist/cjs/react/components/ConsentBanner.d.ts +16 -0
  32. package/dist/cjs/react/components/ConsentBanner.d.ts.map +1 -0
  33. package/dist/cjs/react/components/ConsentBanner.js +112 -0
  34. package/dist/cjs/react/components/ConsentBanner.js.map +1 -0
  35. package/dist/cjs/react/components/CookieNotice.d.ts +12 -0
  36. package/dist/cjs/react/components/CookieNotice.d.ts.map +1 -0
  37. package/dist/cjs/react/components/CookieNotice.js +62 -0
  38. package/dist/cjs/react/components/CookieNotice.js.map +1 -0
  39. package/dist/cjs/react/components/PrivacyPreferenceCenter.d.ts +12 -0
  40. package/dist/cjs/react/components/PrivacyPreferenceCenter.d.ts.map +1 -0
  41. package/dist/cjs/react/components/PrivacyPreferenceCenter.js +120 -0
  42. package/dist/cjs/react/components/PrivacyPreferenceCenter.js.map +1 -0
  43. package/dist/cjs/react/context.d.ts +11 -0
  44. package/dist/cjs/react/context.d.ts.map +1 -0
  45. package/dist/cjs/react/context.js +43 -0
  46. package/dist/cjs/react/context.js.map +1 -0
  47. package/dist/cjs/react/hooks/useAllConfigs.d.ts +8 -0
  48. package/dist/cjs/react/hooks/useAllConfigs.d.ts.map +1 -0
  49. package/dist/cjs/react/hooks/useAllConfigs.js +112 -0
  50. package/dist/cjs/react/hooks/useAllConfigs.js.map +1 -0
  51. package/dist/cjs/react/hooks/useConfig.d.ts +9 -0
  52. package/dist/cjs/react/hooks/useConfig.d.ts.map +1 -0
  53. package/dist/cjs/react/hooks/useConfig.js +116 -0
  54. package/dist/cjs/react/hooks/useConfig.js.map +1 -0
  55. package/dist/cjs/react/hooks/useConsent.d.ts +13 -0
  56. package/dist/cjs/react/hooks/useConsent.d.ts.map +1 -0
  57. package/dist/cjs/react/hooks/useConsent.js +84 -0
  58. package/dist/cjs/react/hooks/useConsent.js.map +1 -0
  59. package/dist/cjs/react/hooks/useDataDeletion.d.ts +17 -0
  60. package/dist/cjs/react/hooks/useDataDeletion.d.ts.map +1 -0
  61. package/dist/cjs/react/hooks/useDataDeletion.js +117 -0
  62. package/dist/cjs/react/hooks/useDataDeletion.js.map +1 -0
  63. package/dist/cjs/react/hooks/useGrainAnalytics.d.ts +6 -0
  64. package/dist/cjs/react/hooks/useGrainAnalytics.d.ts.map +1 -0
  65. package/dist/cjs/react/hooks/useGrainAnalytics.js +50 -0
  66. package/dist/cjs/react/hooks/useGrainAnalytics.js.map +1 -0
  67. package/dist/cjs/react/hooks/usePrivacyPreferences.d.ts +15 -0
  68. package/dist/cjs/react/hooks/usePrivacyPreferences.d.ts.map +1 -0
  69. package/dist/cjs/react/hooks/usePrivacyPreferences.js +82 -0
  70. package/dist/cjs/react/hooks/usePrivacyPreferences.js.map +1 -0
  71. package/dist/cjs/react/hooks/useTrack.d.ts +9 -0
  72. package/dist/cjs/react/hooks/useTrack.d.ts.map +1 -0
  73. package/dist/cjs/react/hooks/useTrack.js +53 -0
  74. package/dist/cjs/react/hooks/useTrack.js.map +1 -0
  75. package/dist/cjs/react/index.d.ts +47 -0
  76. package/dist/cjs/react/index.d.ts.map +1 -0
  77. package/dist/cjs/react/index.js +59 -0
  78. package/dist/cjs/react/index.js.map +1 -0
  79. package/dist/cjs/react/types.d.ts +33 -0
  80. package/dist/cjs/react/types.d.ts.map +1 -0
  81. package/dist/cjs/react/types.js +6 -0
  82. package/dist/cjs/react/types.js.map +1 -0
  83. package/dist/consent.d.ts +68 -0
  84. package/dist/consent.d.ts.map +1 -0
  85. package/dist/cookies.d.ts +28 -0
  86. package/dist/cookies.d.ts.map +1 -0
  87. package/dist/esm/activity.d.ts +59 -0
  88. package/dist/esm/activity.d.ts.map +1 -0
  89. package/dist/esm/activity.js +127 -0
  90. package/dist/esm/activity.js.map +1 -0
  91. package/dist/esm/consent.d.ts +68 -0
  92. package/dist/esm/consent.d.ts.map +1 -0
  93. package/dist/esm/consent.js +187 -0
  94. package/dist/esm/consent.js.map +1 -0
  95. package/dist/esm/cookies.d.ts +28 -0
  96. package/dist/esm/cookies.d.ts.map +1 -0
  97. package/dist/esm/cookies.js +89 -0
  98. package/dist/esm/cookies.js.map +1 -0
  99. package/dist/esm/heartbeat.d.ts +42 -0
  100. package/dist/esm/heartbeat.d.ts.map +1 -0
  101. package/dist/esm/heartbeat.js +88 -0
  102. package/dist/esm/heartbeat.js.map +1 -0
  103. package/dist/esm/index.d.ts +100 -3
  104. package/dist/esm/index.d.ts.map +1 -1
  105. package/dist/esm/index.js.map +1 -1
  106. package/dist/esm/page-tracking.d.ts +60 -0
  107. package/dist/esm/page-tracking.d.ts.map +1 -0
  108. package/dist/esm/page-tracking.js +176 -0
  109. package/dist/esm/page-tracking.js.map +1 -0
  110. package/dist/esm/react/GrainProvider.d.ts +11 -0
  111. package/dist/esm/react/GrainProvider.d.ts.map +1 -0
  112. package/dist/esm/react/GrainProvider.js +43 -0
  113. package/dist/esm/react/GrainProvider.js.map +1 -0
  114. package/dist/esm/react/components/ConsentBanner.d.ts +16 -0
  115. package/dist/esm/react/components/ConsentBanner.d.ts.map +1 -0
  116. package/dist/esm/react/components/ConsentBanner.js +76 -0
  117. package/dist/esm/react/components/ConsentBanner.js.map +1 -0
  118. package/dist/esm/react/components/CookieNotice.d.ts +12 -0
  119. package/dist/esm/react/components/CookieNotice.d.ts.map +1 -0
  120. package/dist/esm/react/components/CookieNotice.js +26 -0
  121. package/dist/esm/react/components/CookieNotice.js.map +1 -0
  122. package/dist/esm/react/components/PrivacyPreferenceCenter.d.ts +12 -0
  123. package/dist/esm/react/components/PrivacyPreferenceCenter.d.ts.map +1 -0
  124. package/dist/esm/react/components/PrivacyPreferenceCenter.js +84 -0
  125. package/dist/esm/react/components/PrivacyPreferenceCenter.js.map +1 -0
  126. package/dist/esm/react/context.d.ts +11 -0
  127. package/dist/esm/react/context.d.ts.map +1 -0
  128. package/dist/esm/react/context.js +7 -0
  129. package/dist/esm/react/context.js.map +1 -0
  130. package/dist/esm/react/hooks/useAllConfigs.d.ts +8 -0
  131. package/dist/esm/react/hooks/useAllConfigs.d.ts.map +1 -0
  132. package/dist/esm/react/hooks/useAllConfigs.js +76 -0
  133. package/dist/esm/react/hooks/useAllConfigs.js.map +1 -0
  134. package/dist/esm/react/hooks/useConfig.d.ts +9 -0
  135. package/dist/esm/react/hooks/useConfig.d.ts.map +1 -0
  136. package/dist/esm/react/hooks/useConfig.js +80 -0
  137. package/dist/esm/react/hooks/useConfig.js.map +1 -0
  138. package/dist/esm/react/hooks/useConsent.d.ts +13 -0
  139. package/dist/esm/react/hooks/useConsent.d.ts.map +1 -0
  140. package/dist/esm/react/hooks/useConsent.js +48 -0
  141. package/dist/esm/react/hooks/useConsent.js.map +1 -0
  142. package/dist/esm/react/hooks/useDataDeletion.d.ts +17 -0
  143. package/dist/esm/react/hooks/useDataDeletion.d.ts.map +1 -0
  144. package/dist/esm/react/hooks/useDataDeletion.js +81 -0
  145. package/dist/esm/react/hooks/useDataDeletion.js.map +1 -0
  146. package/dist/esm/react/hooks/useGrainAnalytics.d.ts +6 -0
  147. package/dist/esm/react/hooks/useGrainAnalytics.d.ts.map +1 -0
  148. package/dist/esm/react/hooks/useGrainAnalytics.js +14 -0
  149. package/dist/esm/react/hooks/useGrainAnalytics.js.map +1 -0
  150. package/dist/esm/react/hooks/usePrivacyPreferences.d.ts +15 -0
  151. package/dist/esm/react/hooks/usePrivacyPreferences.d.ts.map +1 -0
  152. package/dist/esm/react/hooks/usePrivacyPreferences.js +46 -0
  153. package/dist/esm/react/hooks/usePrivacyPreferences.js.map +1 -0
  154. package/dist/esm/react/hooks/useTrack.d.ts +9 -0
  155. package/dist/esm/react/hooks/useTrack.d.ts.map +1 -0
  156. package/dist/esm/react/hooks/useTrack.js +17 -0
  157. package/dist/esm/react/hooks/useTrack.js.map +1 -0
  158. package/dist/esm/react/index.d.ts +47 -0
  159. package/dist/esm/react/index.d.ts.map +1 -0
  160. package/dist/esm/react/index.js +45 -0
  161. package/dist/esm/react/index.js.map +1 -0
  162. package/dist/esm/react/types.d.ts +33 -0
  163. package/dist/esm/react/types.d.ts.map +1 -0
  164. package/dist/esm/react/types.js +5 -0
  165. package/dist/esm/react/types.js.map +1 -0
  166. package/dist/heartbeat.d.ts +42 -0
  167. package/dist/heartbeat.d.ts.map +1 -0
  168. package/dist/index.d.ts +100 -3
  169. package/dist/index.d.ts.map +1 -1
  170. package/dist/index.global.dev.js +903 -12
  171. package/dist/index.global.dev.js.map +3 -3
  172. package/dist/index.global.js +2 -2
  173. package/dist/index.global.js.map +4 -4
  174. package/dist/index.js +321 -11
  175. package/dist/index.mjs +321 -11
  176. package/dist/page-tracking.d.ts +60 -0
  177. package/dist/page-tracking.d.ts.map +1 -0
  178. package/dist/react/activity.d.ts +59 -0
  179. package/dist/react/activity.d.ts.map +1 -0
  180. package/dist/react/activity.js +130 -0
  181. package/dist/react/activity.mjs +126 -0
  182. package/dist/react/consent.d.ts +68 -0
  183. package/dist/react/consent.d.ts.map +1 -0
  184. package/dist/react/consent.js +190 -0
  185. package/dist/react/consent.mjs +186 -0
  186. package/dist/react/cookies.d.ts +28 -0
  187. package/dist/react/cookies.d.ts.map +1 -0
  188. package/dist/react/cookies.js +94 -0
  189. package/dist/react/cookies.mjs +88 -0
  190. package/dist/react/heartbeat.d.ts +42 -0
  191. package/dist/react/heartbeat.d.ts.map +1 -0
  192. package/dist/react/heartbeat.js +91 -0
  193. package/dist/react/heartbeat.mjs +87 -0
  194. package/dist/react/index.d.ts +502 -0
  195. package/dist/react/index.d.ts.map +1 -0
  196. package/dist/react/index.js +1491 -0
  197. package/dist/react/index.mjs +1486 -0
  198. package/dist/react/page-tracking.d.ts +60 -0
  199. package/dist/react/page-tracking.d.ts.map +1 -0
  200. package/dist/react/page-tracking.js +179 -0
  201. package/dist/react/page-tracking.mjs +175 -0
  202. package/dist/react/react/GrainProvider.d.ts +11 -0
  203. package/dist/react/react/GrainProvider.d.ts.map +1 -0
  204. package/dist/react/react/GrainProvider.js +45 -0
  205. package/dist/react/react/GrainProvider.mjs +42 -0
  206. package/dist/react/react/components/ConsentBanner.d.ts +16 -0
  207. package/dist/react/react/components/ConsentBanner.d.ts.map +1 -0
  208. package/dist/react/react/components/ConsentBanner.js +78 -0
  209. package/dist/react/react/components/ConsentBanner.mjs +75 -0
  210. package/dist/react/react/components/CookieNotice.d.ts +12 -0
  211. package/dist/react/react/components/CookieNotice.d.ts.map +1 -0
  212. package/dist/react/react/components/CookieNotice.js +28 -0
  213. package/dist/react/react/components/CookieNotice.mjs +25 -0
  214. package/dist/react/react/components/PrivacyPreferenceCenter.d.ts +12 -0
  215. package/dist/react/react/components/PrivacyPreferenceCenter.d.ts.map +1 -0
  216. package/dist/react/react/components/PrivacyPreferenceCenter.js +86 -0
  217. package/dist/react/react/components/PrivacyPreferenceCenter.mjs +83 -0
  218. package/dist/react/react/context.d.ts +11 -0
  219. package/dist/react/react/context.d.ts.map +1 -0
  220. package/dist/react/react/context.js +9 -0
  221. package/dist/react/react/context.mjs +6 -0
  222. package/dist/react/react/hooks/useAllConfigs.d.ts +8 -0
  223. package/dist/react/react/hooks/useAllConfigs.d.ts.map +1 -0
  224. package/dist/react/react/hooks/useAllConfigs.js +78 -0
  225. package/dist/react/react/hooks/useAllConfigs.mjs +75 -0
  226. package/dist/react/react/hooks/useConfig.d.ts +9 -0
  227. package/dist/react/react/hooks/useConfig.d.ts.map +1 -0
  228. package/dist/react/react/hooks/useConfig.js +82 -0
  229. package/dist/react/react/hooks/useConfig.mjs +79 -0
  230. package/dist/react/react/hooks/useConsent.d.ts +13 -0
  231. package/dist/react/react/hooks/useConsent.d.ts.map +1 -0
  232. package/dist/react/react/hooks/useConsent.js +50 -0
  233. package/dist/react/react/hooks/useConsent.mjs +47 -0
  234. package/dist/react/react/hooks/useDataDeletion.d.ts +17 -0
  235. package/dist/react/react/hooks/useDataDeletion.d.ts.map +1 -0
  236. package/dist/react/react/hooks/useDataDeletion.js +83 -0
  237. package/dist/react/react/hooks/useDataDeletion.mjs +80 -0
  238. package/dist/react/react/hooks/useGrainAnalytics.d.ts +6 -0
  239. package/dist/react/react/hooks/useGrainAnalytics.d.ts.map +1 -0
  240. package/dist/react/react/hooks/useGrainAnalytics.js +16 -0
  241. package/dist/react/react/hooks/useGrainAnalytics.mjs +13 -0
  242. package/dist/react/react/hooks/usePrivacyPreferences.d.ts +15 -0
  243. package/dist/react/react/hooks/usePrivacyPreferences.d.ts.map +1 -0
  244. package/dist/react/react/hooks/usePrivacyPreferences.js +48 -0
  245. package/dist/react/react/hooks/usePrivacyPreferences.mjs +45 -0
  246. package/dist/react/react/hooks/useTrack.d.ts +9 -0
  247. package/dist/react/react/hooks/useTrack.d.ts.map +1 -0
  248. package/dist/react/react/hooks/useTrack.js +19 -0
  249. package/dist/react/react/hooks/useTrack.mjs +16 -0
  250. package/dist/react/react/index.d.ts +47 -0
  251. package/dist/react/react/index.d.ts.map +1 -0
  252. package/dist/react/react/index.js +58 -0
  253. package/dist/react/react/index.mjs +44 -0
  254. package/dist/react/react/types.d.ts +33 -0
  255. package/dist/react/react/types.d.ts.map +1 -0
  256. package/dist/react/react/types.js +5 -0
  257. package/dist/react/react/types.mjs +4 -0
  258. package/package.json +20 -2
@@ -0,0 +1,75 @@
1
+ /**
2
+ * ConsentBanner - Glassmorphic consent popup for GDPR compliance
3
+ * Follows Grain Design System specifications
4
+ */
5
+ import * as React from 'react';
6
+ import { useGrainAnalytics } from '../hooks/useGrainAnalytics';
7
+ export function ConsentBanner({ position = 'bottom', theme = 'glass', customText, onAccept, onDecline, showPreferences = false, privacyPolicyUrl, }) {
8
+ const client = useGrainAnalytics();
9
+ const [visible, setVisible] = React.useState(false);
10
+ const [showPreferencesModal, setShowPreferencesModal] = React.useState(false);
11
+ React.useEffect(() => {
12
+ if (!client)
13
+ return;
14
+ // Check if user has already made a consent decision
15
+ const consentState = client.getConsentState();
16
+ if (!consentState) {
17
+ setVisible(true);
18
+ }
19
+ }, [client]);
20
+ const handleAccept = () => {
21
+ if (client) {
22
+ client.grantConsent(['necessary', 'analytics', 'functional']);
23
+ }
24
+ setVisible(false);
25
+ onAccept?.();
26
+ };
27
+ const handleDecline = () => {
28
+ if (client) {
29
+ client.revokeConsent();
30
+ }
31
+ setVisible(false);
32
+ onDecline?.();
33
+ };
34
+ const handleEscape = (e) => {
35
+ if (e.key === 'Escape') {
36
+ handleDecline();
37
+ }
38
+ };
39
+ if (!visible)
40
+ return null;
41
+ const defaultText = customText ||
42
+ "We use cookies and similar technologies to improve your experience. By accepting, you consent to our use of analytics and functional cookies.";
43
+ // Position styles
44
+ const positionStyles = {
45
+ top: 'top-4 left-4 right-4',
46
+ bottom: 'bottom-4 left-4 right-4',
47
+ center: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
48
+ };
49
+ // Theme styles (glassmorphic by default)
50
+ const themeStyles = {
51
+ light: 'bg-white/95 border-gray-200 text-gray-900',
52
+ dark: 'bg-zinc-900/95 border-zinc-800 text-zinc-100',
53
+ glass: 'bg-zinc-950/40 backdrop-blur-xl border-zinc-800/40 text-zinc-100',
54
+ };
55
+ const buttonAcceptStyles = {
56
+ light: 'bg-blue-600 hover:bg-blue-700 text-white',
57
+ dark: 'bg-emerald-600 hover:bg-emerald-700 text-white',
58
+ glass: 'bg-emerald-600 hover:bg-emerald-700 text-white',
59
+ };
60
+ const buttonDeclineStyles = {
61
+ light: 'bg-gray-200 hover:bg-gray-300 text-gray-800',
62
+ dark: 'bg-zinc-800 hover:bg-zinc-700 text-zinc-200',
63
+ glass: 'bg-zinc-900/60 hover:bg-zinc-800/60 text-zinc-300 border border-zinc-800/60',
64
+ };
65
+ return (React.createElement("div", { className: `fixed z-50 max-w-2xl ${positionStyles[position]}`, onKeyDown: handleEscape, role: "dialog", "aria-labelledby": "consent-title", "aria-describedby": "consent-description" },
66
+ React.createElement("div", { className: `rounded-lg shadow-2xl border p-6 transition-all ${themeStyles[theme]}` },
67
+ React.createElement("h2", { id: "consent-title", className: "text-lg font-semibold mb-2" }, "Cookie Consent"),
68
+ React.createElement("p", { id: "consent-description", className: "text-sm opacity-80 mb-4" }, defaultText),
69
+ privacyPolicyUrl && (React.createElement("a", { href: privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", className: "text-sm underline opacity-70 hover:opacity-100 transition-opacity block mb-4" }, "Read our Privacy Policy")),
70
+ React.createElement("div", { className: "flex flex-wrap gap-2" },
71
+ React.createElement("button", { onClick: handleAccept, className: `px-4 py-2 rounded-lg font-medium transition-all ${buttonAcceptStyles[theme]}`, "aria-label": "Accept cookies" }, "Accept All"),
72
+ React.createElement("button", { onClick: handleDecline, className: `px-4 py-2 rounded-lg font-medium transition-all ${buttonDeclineStyles[theme]}`, "aria-label": "Decline cookies" }, "Decline"),
73
+ showPreferences && (React.createElement("button", { onClick: () => setShowPreferencesModal(true), className: `px-4 py-2 rounded-lg font-medium transition-all ${buttonDeclineStyles[theme]}`, "aria-label": "Manage preferences" }, "Manage Preferences")),
74
+ React.createElement("kbd", { className: "ml-auto px-2 py-1 bg-zinc-900/50 border border-zinc-800 rounded text-[10px] font-mono self-center" }, "ESC")))));
75
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * CookieNotice - Simple cookie notification banner
3
+ */
4
+ import * as React from 'react';
5
+ export interface CookieNoticeProps {
6
+ message?: string;
7
+ privacyPolicyUrl?: string;
8
+ onDismiss?: () => void;
9
+ position?: 'top' | 'bottom';
10
+ }
11
+ export declare function CookieNotice({ message, privacyPolicyUrl, onDismiss, position, }: CookieNoticeProps): React.JSX.Element;
12
+ //# sourceMappingURL=CookieNotice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CookieNotice.d.ts","sourceRoot":"","sources":["../../../../src/react/components/CookieNotice.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,QAAmB,GACpB,EAAE,iBAAiB,qBA6CnB"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * CookieNotice - Simple cookie notification banner
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CookieNotice = CookieNotice;
7
+ const React = require("react");
8
+ function CookieNotice({ message, privacyPolicyUrl, onDismiss, position = 'bottom', }) {
9
+ const [visible, setVisible] = React.useState(true);
10
+ const defaultMessage = message ||
11
+ "This website uses cookies to enhance your experience.";
12
+ const handleDismiss = () => {
13
+ setVisible(false);
14
+ onDismiss?.();
15
+ };
16
+ if (!visible)
17
+ return null;
18
+ const positionStyles = position === 'top' ? 'top-0' : 'bottom-0';
19
+ return (React.createElement("div", { className: `fixed ${positionStyles} left-0 right-0 z-40` },
20
+ React.createElement("div", { className: "bg-zinc-950/90 backdrop-blur-sm border-t border-zinc-800/40 px-4 py-3" },
21
+ React.createElement("div", { className: "max-w-7xl mx-auto flex items-center justify-between gap-4" },
22
+ React.createElement("p", { className: "text-sm text-zinc-300" },
23
+ defaultMessage,
24
+ privacyPolicyUrl && (React.createElement(React.Fragment, null,
25
+ ' ',
26
+ React.createElement("a", { href: privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", className: "underline opacity-70 hover:opacity-100 transition-opacity" }, "Learn more")))),
27
+ React.createElement("button", { onClick: handleDismiss, className: "px-3 py-1 bg-zinc-800/60 hover:bg-zinc-700/60 text-zinc-300 rounded text-sm font-medium transition-all flex-shrink-0" }, "Dismiss")))));
28
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * CookieNotice - Simple cookie notification banner
3
+ */
4
+ import * as React from 'react';
5
+ export function CookieNotice({ message, privacyPolicyUrl, onDismiss, position = 'bottom', }) {
6
+ const [visible, setVisible] = React.useState(true);
7
+ const defaultMessage = message ||
8
+ "This website uses cookies to enhance your experience.";
9
+ const handleDismiss = () => {
10
+ setVisible(false);
11
+ onDismiss?.();
12
+ };
13
+ if (!visible)
14
+ return null;
15
+ const positionStyles = position === 'top' ? 'top-0' : 'bottom-0';
16
+ return (React.createElement("div", { className: `fixed ${positionStyles} left-0 right-0 z-40` },
17
+ React.createElement("div", { className: "bg-zinc-950/90 backdrop-blur-sm border-t border-zinc-800/40 px-4 py-3" },
18
+ React.createElement("div", { className: "max-w-7xl mx-auto flex items-center justify-between gap-4" },
19
+ React.createElement("p", { className: "text-sm text-zinc-300" },
20
+ defaultMessage,
21
+ privacyPolicyUrl && (React.createElement(React.Fragment, null,
22
+ ' ',
23
+ React.createElement("a", { href: privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", className: "underline opacity-70 hover:opacity-100 transition-opacity" }, "Learn more")))),
24
+ React.createElement("button", { onClick: handleDismiss, className: "px-3 py-1 bg-zinc-800/60 hover:bg-zinc-700/60 text-zinc-300 rounded text-sm font-medium transition-all flex-shrink-0" }, "Dismiss")))));
25
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * PrivacyPreferenceCenter - Detailed preference management modal
3
+ * Follows Grain Design System specifications
4
+ */
5
+ import * as React from 'react';
6
+ export interface PrivacyPreferenceCenterProps {
7
+ isOpen: boolean;
8
+ onClose: () => void;
9
+ onSave?: (categories: string[]) => void;
10
+ }
11
+ export declare function PrivacyPreferenceCenter({ isOpen, onClose, onSave, }: PrivacyPreferenceCenterProps): React.JSX.Element;
12
+ //# sourceMappingURL=PrivacyPreferenceCenter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrivacyPreferenceCenter.d.ts","sourceRoot":"","sources":["../../../../src/react/components/PrivacyPreferenceCenter.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACzC;AA8BD,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,OAAO,EACP,MAAM,GACP,EAAE,4BAA4B,qBAkH9B"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * PrivacyPreferenceCenter - Detailed preference management modal
4
+ * Follows Grain Design System specifications
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PrivacyPreferenceCenter = PrivacyPreferenceCenter;
8
+ const React = require("react");
9
+ const useGrainAnalytics_1 = require("../hooks/useGrainAnalytics");
10
+ const CATEGORIES = [
11
+ {
12
+ id: 'necessary',
13
+ name: 'Necessary',
14
+ description: 'Essential for the website to function properly. Cannot be disabled.',
15
+ required: true,
16
+ },
17
+ {
18
+ id: 'analytics',
19
+ name: 'Analytics',
20
+ description: 'Help us understand how visitors interact with our website.',
21
+ required: false,
22
+ },
23
+ {
24
+ id: 'functional',
25
+ name: 'Functional',
26
+ description: 'Enable enhanced functionality and personalization.',
27
+ required: false,
28
+ },
29
+ ];
30
+ function PrivacyPreferenceCenter({ isOpen, onClose, onSave, }) {
31
+ const client = (0, useGrainAnalytics_1.useGrainAnalytics)();
32
+ const [selectedCategories, setSelectedCategories] = React.useState(['necessary']);
33
+ React.useEffect(() => {
34
+ if (!client)
35
+ return;
36
+ const consentState = client.getConsentState();
37
+ if (consentState) {
38
+ setSelectedCategories(consentState.categories);
39
+ }
40
+ }, [client, isOpen]);
41
+ const handleToggle = (categoryId, required) => {
42
+ if (required)
43
+ return; // Cannot toggle required categories
44
+ setSelectedCategories((prev) => prev.includes(categoryId)
45
+ ? prev.filter((id) => id !== categoryId)
46
+ : [...prev, categoryId]);
47
+ };
48
+ const handleSave = () => {
49
+ if (client) {
50
+ if (selectedCategories.length > 0) {
51
+ client.grantConsent(selectedCategories);
52
+ }
53
+ else {
54
+ client.revokeConsent();
55
+ }
56
+ }
57
+ onSave?.(selectedCategories);
58
+ onClose();
59
+ };
60
+ const handleEscape = (e) => {
61
+ if (e.key === 'Escape') {
62
+ onClose();
63
+ }
64
+ };
65
+ if (!isOpen)
66
+ return null;
67
+ return (React.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center", onKeyDown: handleEscape, role: "dialog", "aria-labelledby": "preferences-title", "aria-modal": "true" },
68
+ React.createElement("div", { className: "fixed inset-0 bg-zinc-950/80 backdrop-blur-sm", onClick: onClose }),
69
+ React.createElement("div", { className: "relative bg-zinc-950/95 border border-zinc-800/60 backdrop-blur-xl rounded-lg shadow-2xl max-w-2xl w-full mx-4 p-6" },
70
+ React.createElement("h2", { id: "preferences-title", className: "text-xl font-semibold text-zinc-100 mb-4" }, "Privacy Preferences"),
71
+ React.createElement("div", { className: "space-y-4 mb-6" }, CATEGORIES.map((category) => (React.createElement("div", { key: category.id, className: "p-4 bg-zinc-900/40 border border-zinc-800/40 rounded-lg" },
72
+ React.createElement("div", { className: "flex items-start justify-between" },
73
+ React.createElement("div", { className: "flex-1" },
74
+ React.createElement("h3", { className: "font-medium text-zinc-200 mb-1" },
75
+ category.name,
76
+ category.required && (React.createElement("span", { className: "ml-2 text-xs text-emerald-500" }, "(Required)"))),
77
+ React.createElement("p", { className: "text-sm text-zinc-400" }, category.description)),
78
+ React.createElement("label", { className: "relative inline-flex items-center cursor-pointer" },
79
+ React.createElement("input", { type: "checkbox", checked: selectedCategories.includes(category.id), onChange: () => handleToggle(category.id, category.required), disabled: category.required, className: "sr-only peer" }),
80
+ React.createElement("div", { className: "w-11 h-6 bg-zinc-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-600" }))))))),
81
+ React.createElement("div", { className: "flex justify-end gap-2" },
82
+ React.createElement("button", { onClick: onClose, className: "px-4 py-2 bg-zinc-900/60 hover:bg-zinc-800/60 text-zinc-300 border border-zinc-800/60 rounded-lg font-medium transition-all" },
83
+ "Cancel",
84
+ React.createElement("kbd", { className: "ml-2 px-2 py-0.5 bg-zinc-900/50 border border-zinc-800 rounded text-[10px] font-mono" }, "ESC")),
85
+ React.createElement("button", { onClick: handleSave, className: "px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-medium transition-all" }, "Save Preferences")))));
86
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * PrivacyPreferenceCenter - Detailed preference management modal
3
+ * Follows Grain Design System specifications
4
+ */
5
+ import * as React from 'react';
6
+ import { useGrainAnalytics } from '../hooks/useGrainAnalytics';
7
+ const CATEGORIES = [
8
+ {
9
+ id: 'necessary',
10
+ name: 'Necessary',
11
+ description: 'Essential for the website to function properly. Cannot be disabled.',
12
+ required: true,
13
+ },
14
+ {
15
+ id: 'analytics',
16
+ name: 'Analytics',
17
+ description: 'Help us understand how visitors interact with our website.',
18
+ required: false,
19
+ },
20
+ {
21
+ id: 'functional',
22
+ name: 'Functional',
23
+ description: 'Enable enhanced functionality and personalization.',
24
+ required: false,
25
+ },
26
+ ];
27
+ export function PrivacyPreferenceCenter({ isOpen, onClose, onSave, }) {
28
+ const client = useGrainAnalytics();
29
+ const [selectedCategories, setSelectedCategories] = React.useState(['necessary']);
30
+ React.useEffect(() => {
31
+ if (!client)
32
+ return;
33
+ const consentState = client.getConsentState();
34
+ if (consentState) {
35
+ setSelectedCategories(consentState.categories);
36
+ }
37
+ }, [client, isOpen]);
38
+ const handleToggle = (categoryId, required) => {
39
+ if (required)
40
+ return; // Cannot toggle required categories
41
+ setSelectedCategories((prev) => prev.includes(categoryId)
42
+ ? prev.filter((id) => id !== categoryId)
43
+ : [...prev, categoryId]);
44
+ };
45
+ const handleSave = () => {
46
+ if (client) {
47
+ if (selectedCategories.length > 0) {
48
+ client.grantConsent(selectedCategories);
49
+ }
50
+ else {
51
+ client.revokeConsent();
52
+ }
53
+ }
54
+ onSave?.(selectedCategories);
55
+ onClose();
56
+ };
57
+ const handleEscape = (e) => {
58
+ if (e.key === 'Escape') {
59
+ onClose();
60
+ }
61
+ };
62
+ if (!isOpen)
63
+ return null;
64
+ return (React.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center", onKeyDown: handleEscape, role: "dialog", "aria-labelledby": "preferences-title", "aria-modal": "true" },
65
+ React.createElement("div", { className: "fixed inset-0 bg-zinc-950/80 backdrop-blur-sm", onClick: onClose }),
66
+ React.createElement("div", { className: "relative bg-zinc-950/95 border border-zinc-800/60 backdrop-blur-xl rounded-lg shadow-2xl max-w-2xl w-full mx-4 p-6" },
67
+ React.createElement("h2", { id: "preferences-title", className: "text-xl font-semibold text-zinc-100 mb-4" }, "Privacy Preferences"),
68
+ React.createElement("div", { className: "space-y-4 mb-6" }, CATEGORIES.map((category) => (React.createElement("div", { key: category.id, className: "p-4 bg-zinc-900/40 border border-zinc-800/40 rounded-lg" },
69
+ React.createElement("div", { className: "flex items-start justify-between" },
70
+ React.createElement("div", { className: "flex-1" },
71
+ React.createElement("h3", { className: "font-medium text-zinc-200 mb-1" },
72
+ category.name,
73
+ category.required && (React.createElement("span", { className: "ml-2 text-xs text-emerald-500" }, "(Required)"))),
74
+ React.createElement("p", { className: "text-sm text-zinc-400" }, category.description)),
75
+ React.createElement("label", { className: "relative inline-flex items-center cursor-pointer" },
76
+ React.createElement("input", { type: "checkbox", checked: selectedCategories.includes(category.id), onChange: () => handleToggle(category.id, category.required), disabled: category.required, className: "sr-only peer" }),
77
+ React.createElement("div", { className: "w-11 h-6 bg-zinc-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-600" }))))))),
78
+ React.createElement("div", { className: "flex justify-end gap-2" },
79
+ React.createElement("button", { onClick: onClose, className: "px-4 py-2 bg-zinc-900/60 hover:bg-zinc-800/60 text-zinc-300 border border-zinc-800/60 rounded-lg font-medium transition-all" },
80
+ "Cancel",
81
+ React.createElement("kbd", { className: "ml-2 px-2 py-0.5 bg-zinc-900/50 border border-zinc-800 rounded text-[10px] font-mono" }, "ESC")),
82
+ React.createElement("button", { onClick: handleSave, className: "px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-medium transition-all" }, "Save Preferences")))));
83
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * React context for Grain Analytics
3
+ */
4
+ import * as React from 'react';
5
+ import type { GrainAnalytics } from '../index';
6
+ export interface GrainContextValue {
7
+ client: GrainAnalytics;
8
+ isProviderManaged: boolean;
9
+ }
10
+ export declare const GrainContext: React.Context<GrainContextValue>;
11
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/react/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY,kCAAsD,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * React context for Grain Analytics
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GrainContext = void 0;
7
+ const React = require("react");
8
+ exports.GrainContext = React.createContext(null);
9
+ exports.GrainContext.displayName = 'GrainContext';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * React context for Grain Analytics
3
+ */
4
+ import * as React from 'react';
5
+ export const GrainContext = React.createContext(null);
6
+ GrainContext.displayName = 'GrainContext';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * useAllConfigs - Get all configurations with automatic updates
3
+ *
4
+ * Returns all configs as a reactive object with cache-first strategy.
5
+ */
6
+ import type { UseAllConfigsOptions, UseAllConfigsResult } from '../types';
7
+ export declare function useAllConfigs(options?: UseAllConfigsOptions): UseAllConfigsResult;
8
+ //# sourceMappingURL=useAllConfigs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAllConfigs.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks/useAllConfigs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE1E,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CA4ErF"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * useAllConfigs - Get all configurations with automatic updates
4
+ *
5
+ * Returns all configs as a reactive object with cache-first strategy.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.useAllConfigs = useAllConfigs;
9
+ const React = require("react");
10
+ const useGrainAnalytics_1 = require("./useGrainAnalytics");
11
+ function useAllConfigs(options = {}) {
12
+ const client = (0, useGrainAnalytics_1.useGrainAnalytics)();
13
+ // Get initial configs from cache/defaults (synchronous, instant)
14
+ const [configs, setConfigs] = React.useState(() => client.getAllConfigs());
15
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
16
+ const [error, setError] = React.useState(null);
17
+ const isMountedRef = React.useRef(true);
18
+ // Fetch fresh configs from API
19
+ const fetchConfigs = React.useCallback(async () => {
20
+ if (!isMountedRef.current)
21
+ return;
22
+ setIsRefreshing(true);
23
+ setError(null);
24
+ try {
25
+ const response = await client.fetchConfig({
26
+ immediateKeys: options.immediateKeys || [],
27
+ properties: options.properties,
28
+ forceRefresh: options.forceRefresh,
29
+ });
30
+ if (isMountedRef.current && response) {
31
+ setConfigs(response.configurations);
32
+ }
33
+ }
34
+ catch (err) {
35
+ if (isMountedRef.current) {
36
+ setError(err instanceof Error ? err : new Error(String(err)));
37
+ }
38
+ }
39
+ finally {
40
+ if (isMountedRef.current) {
41
+ setIsRefreshing(false);
42
+ }
43
+ }
44
+ }, [client, options.immediateKeys, options.properties, options.forceRefresh]);
45
+ // Manual refresh function
46
+ const refresh = React.useCallback(async () => {
47
+ await fetchConfigs();
48
+ }, [fetchConfigs]);
49
+ // Background fetch on mount and when options change
50
+ React.useEffect(() => {
51
+ fetchConfigs();
52
+ }, [fetchConfigs]);
53
+ // Subscribe to config changes
54
+ React.useEffect(() => {
55
+ const listener = (configurations) => {
56
+ if (isMountedRef.current) {
57
+ setConfigs(configurations);
58
+ }
59
+ };
60
+ client.addConfigChangeListener(listener);
61
+ return () => {
62
+ client.removeConfigChangeListener(listener);
63
+ };
64
+ }, [client]);
65
+ // Track mount state
66
+ React.useEffect(() => {
67
+ isMountedRef.current = true;
68
+ return () => {
69
+ isMountedRef.current = false;
70
+ };
71
+ }, []);
72
+ return {
73
+ configs,
74
+ isRefreshing,
75
+ error,
76
+ refresh,
77
+ };
78
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * useAllConfigs - Get all configurations with automatic updates
3
+ *
4
+ * Returns all configs as a reactive object with cache-first strategy.
5
+ */
6
+ import * as React from 'react';
7
+ import { useGrainAnalytics } from './useGrainAnalytics';
8
+ export function useAllConfigs(options = {}) {
9
+ const client = useGrainAnalytics();
10
+ // Get initial configs from cache/defaults (synchronous, instant)
11
+ const [configs, setConfigs] = React.useState(() => client.getAllConfigs());
12
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
13
+ const [error, setError] = React.useState(null);
14
+ const isMountedRef = React.useRef(true);
15
+ // Fetch fresh configs from API
16
+ const fetchConfigs = React.useCallback(async () => {
17
+ if (!isMountedRef.current)
18
+ return;
19
+ setIsRefreshing(true);
20
+ setError(null);
21
+ try {
22
+ const response = await client.fetchConfig({
23
+ immediateKeys: options.immediateKeys || [],
24
+ properties: options.properties,
25
+ forceRefresh: options.forceRefresh,
26
+ });
27
+ if (isMountedRef.current && response) {
28
+ setConfigs(response.configurations);
29
+ }
30
+ }
31
+ catch (err) {
32
+ if (isMountedRef.current) {
33
+ setError(err instanceof Error ? err : new Error(String(err)));
34
+ }
35
+ }
36
+ finally {
37
+ if (isMountedRef.current) {
38
+ setIsRefreshing(false);
39
+ }
40
+ }
41
+ }, [client, options.immediateKeys, options.properties, options.forceRefresh]);
42
+ // Manual refresh function
43
+ const refresh = React.useCallback(async () => {
44
+ await fetchConfigs();
45
+ }, [fetchConfigs]);
46
+ // Background fetch on mount and when options change
47
+ React.useEffect(() => {
48
+ fetchConfigs();
49
+ }, [fetchConfigs]);
50
+ // Subscribe to config changes
51
+ React.useEffect(() => {
52
+ const listener = (configurations) => {
53
+ if (isMountedRef.current) {
54
+ setConfigs(configurations);
55
+ }
56
+ };
57
+ client.addConfigChangeListener(listener);
58
+ return () => {
59
+ client.removeConfigChangeListener(listener);
60
+ };
61
+ }, [client]);
62
+ // Track mount state
63
+ React.useEffect(() => {
64
+ isMountedRef.current = true;
65
+ return () => {
66
+ isMountedRef.current = false;
67
+ };
68
+ }, []);
69
+ return {
70
+ configs,
71
+ isRefreshing,
72
+ error,
73
+ refresh,
74
+ };
75
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * useConfig - Cache-first config access with automatic background refresh
3
+ *
4
+ * Returns cached/default value immediately, fetches fresh data in background,
5
+ * and automatically updates when configuration changes.
6
+ */
7
+ import type { UseConfigOptions, UseConfigResult } from '../types';
8
+ export declare function useConfig(key: string, options?: UseConfigOptions): UseConfigResult;
9
+ //# sourceMappingURL=useConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConfig.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks/useConfig.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAElE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,eAAe,CA+EtF"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * useConfig - Cache-first config access with automatic background refresh
4
+ *
5
+ * Returns cached/default value immediately, fetches fresh data in background,
6
+ * and automatically updates when configuration changes.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.useConfig = useConfig;
10
+ const React = require("react");
11
+ const useGrainAnalytics_1 = require("./useGrainAnalytics");
12
+ function useConfig(key, options = {}) {
13
+ const client = (0, useGrainAnalytics_1.useGrainAnalytics)();
14
+ // Get initial value from cache/defaults (synchronous, instant)
15
+ const [value, setValue] = React.useState(() => client.getConfig(key));
16
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
17
+ const [error, setError] = React.useState(null);
18
+ const isMountedRef = React.useRef(true);
19
+ // Fetch fresh config from API
20
+ const fetchConfig = React.useCallback(async () => {
21
+ if (!isMountedRef.current)
22
+ return;
23
+ setIsRefreshing(true);
24
+ setError(null);
25
+ try {
26
+ const response = await client.fetchConfig({
27
+ immediateKeys: options.immediateKeys || [key],
28
+ properties: options.properties,
29
+ forceRefresh: options.forceRefresh,
30
+ });
31
+ if (isMountedRef.current && response) {
32
+ const newValue = response.configurations[key];
33
+ if (newValue !== undefined) {
34
+ setValue(newValue);
35
+ }
36
+ }
37
+ }
38
+ catch (err) {
39
+ if (isMountedRef.current) {
40
+ setError(err instanceof Error ? err : new Error(String(err)));
41
+ }
42
+ }
43
+ finally {
44
+ if (isMountedRef.current) {
45
+ setIsRefreshing(false);
46
+ }
47
+ }
48
+ }, [client, key, options.immediateKeys, options.properties, options.forceRefresh]);
49
+ // Manual refresh function
50
+ const refresh = React.useCallback(async () => {
51
+ await fetchConfig();
52
+ }, [fetchConfig]);
53
+ // Background fetch on mount and when key/options change
54
+ React.useEffect(() => {
55
+ fetchConfig();
56
+ }, [fetchConfig]);
57
+ // Subscribe to config changes
58
+ React.useEffect(() => {
59
+ const listener = (configurations) => {
60
+ if (isMountedRef.current && configurations[key] !== undefined) {
61
+ setValue(configurations[key]);
62
+ }
63
+ };
64
+ client.addConfigChangeListener(listener);
65
+ return () => {
66
+ client.removeConfigChangeListener(listener);
67
+ };
68
+ }, [client, key]);
69
+ // Track mount state
70
+ React.useEffect(() => {
71
+ isMountedRef.current = true;
72
+ return () => {
73
+ isMountedRef.current = false;
74
+ };
75
+ }, []);
76
+ return {
77
+ value,
78
+ isRefreshing,
79
+ error,
80
+ refresh,
81
+ };
82
+ }