@tiendanube/live-state 1.0.0-beta.13 → 1.0.0-beta.16

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/README.md CHANGED
@@ -146,6 +146,60 @@ vi.mock('@tiendanube/live-state', async (importOriginal) => ({
146
146
 
147
147
  ---
148
148
 
149
+ ## Disabling analytics (dev / test / staging)
150
+
151
+ Pass `disabled={true}` to `LiveStateProvider` to prevent the Amplitude and Clarity SDKs from being initialised. The prop value should come from your app's own environment variable — the library does not define or read any env var itself.
152
+
153
+ ```tsx
154
+ // Vite
155
+ <LiveStateProvider
156
+ fetcher={fetchLiveState}
157
+ disabled={import.meta.env.VITE_APP_ENV !== 'production'}
158
+ >
159
+ ...
160
+ </LiveStateProvider>
161
+
162
+ // Create React App / Next.js
163
+ <LiveStateProvider
164
+ fetcher={fetchLiveState}
165
+ disabled={process.env.NODE_ENV !== 'production'}
166
+ >
167
+ ...
168
+ </LiveStateProvider>
169
+ ```
170
+
171
+ When `disabled` is `true`, `onEvent` callbacks still fire normally so you can observe tracking without hitting the real SDKs.
172
+
173
+ ---
174
+
175
+ ## Fetcher timeouts
176
+
177
+ The library does not impose a timeout on the fetcher — if the endpoint hangs, `isLoading` will remain `true` indefinitely. Because live state notifications are non-critical UI, you should configure a timeout directly in your fetcher so that a slow backend never blocks the page.
178
+
179
+ ```tsx
180
+ // Using axios
181
+ const fetcher: LiveStateFetcher = async () => {
182
+ const { data } = await axios.get('/api/live-state', { timeout: 5000 });
183
+ return data ?? null;
184
+ };
185
+
186
+ // Using native fetch + AbortSignal
187
+ const fetcher: LiveStateFetcher = async () => {
188
+ const controller = new AbortController();
189
+ const id = setTimeout(() => controller.abort(), 5000);
190
+ try {
191
+ const res = await fetch('/api/live-state', { signal: controller.signal });
192
+ return res.ok ? await res.json() : null;
193
+ } catch {
194
+ return null; // timeout or network error — fail silently
195
+ } finally {
196
+ clearTimeout(id);
197
+ }
198
+ };
199
+ ```
200
+
201
+ ---
202
+
149
203
  ## Documentation
150
204
 
151
205
  - [Implementation Guide](./docs/IMPLEMENTATION_GUIDE.md) — full integration guide with usage patterns, API reference, and troubleshooting
@@ -30,7 +30,7 @@ function u(e, t) {
30
30
  async function d(e, t) {
31
31
  let n = c(t);
32
32
  try {
33
- if (window.clarity) {
33
+ if (v()) {
34
34
  n("info", "Clarity already initialized by client");
35
35
  return;
36
36
  }
@@ -48,7 +48,7 @@ function f() {
48
48
  try {
49
49
  let e = document.querySelector("script[src*=\"clarity.ms\"]");
50
50
  if (e) return e.getAttribute("src")?.match(/\/([a-z0-9]+)$/)?.[1];
51
- let t = window.clarity?.q?.[0]?.[1];
51
+ let t = v()?.q?.[0]?.[1];
52
52
  if (typeof t == "string") return t;
53
53
  } catch {}
54
54
  }
@@ -66,8 +66,9 @@ function p(e, t, n) {
66
66
  function m(e, t, n) {
67
67
  let r = c(n);
68
68
  try {
69
- window.clarity && (window.clarity("event", e), t && Object.entries(t).forEach(([e, t]) => {
70
- window.clarity?.("set", e, String(t));
69
+ let n = v();
70
+ n && (n("event", e), t && Object.entries(t).forEach(([e, t]) => {
71
+ n("set", e, String(t));
71
72
  }));
72
73
  } catch (t) {
73
74
  r("error", "Failed to track Clarity event", {
@@ -89,10 +90,13 @@ function g(e) {
89
90
  function _(e, t, n) {
90
91
  p(e, t, n), m(e, t, n);
91
92
  }
93
+ function v() {
94
+ return window.clarity;
95
+ }
92
96
  //#endregion
93
97
  //#region src/providers/LiveStateProvider.tsx
94
- var v = e(null);
95
- function y({ children: e, fetcher: t, analytics: a, onEvent: s, onLog: l, disabled: f = !1 }) {
98
+ var y = e(null);
99
+ function b({ children: e, fetcher: t, analytics: a, onEvent: s, onLog: l, disabled: f = !1 }) {
96
100
  let p = r(() => c(l), [l]);
97
101
  n(() => {
98
102
  f || (a?.amplitudeKey && u(a.amplitudeKey, p), a?.clarityProjectId && d(a.clarityProjectId, p));
@@ -116,18 +120,18 @@ function y({ children: e, fetcher: t, analytics: a, onEvent: s, onLog: l, disabl
116
120
  ]);
117
121
  return /* @__PURE__ */ o(i, {
118
122
  value: { provider: () => /* @__PURE__ */ new Map() },
119
- children: /* @__PURE__ */ o(v.Provider, {
123
+ children: /* @__PURE__ */ o(y.Provider, {
120
124
  value: m,
121
125
  children: e
122
126
  })
123
127
  });
124
128
  }
125
- function b() {
126
- let e = t(v);
129
+ function x() {
130
+ let e = t(y);
127
131
  if (!e) throw Error("useLiveState must be used within LiveStateProvider");
128
132
  return e;
129
133
  }
130
134
  //#endregion
131
- export { _ as a, g as i, b as n, c as o, h as r, y as t };
135
+ export { _ as a, g as i, x as n, c as o, h as r, b as t };
132
136
 
133
- //# sourceMappingURL=LiveStateProvider-BH31kAuo.js.map
137
+ //# sourceMappingURL=LiveStateProvider-DeRIGqmT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveStateProvider-DeRIGqmT.js","names":[],"sources":["../src/utils/logger.ts","../src/utils/analytics.ts","../src/providers/LiveStateProvider.tsx"],"sourcesContent":["import type { LogLevel } from '../types';\n\n/**\n * Logger function type — matches the signature of LiveStateProviderProps.onLog.\n */\nexport type LoggerFn = (\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n) => void;\n\n/**\n * Default logger — falls back to console.* when no onLog is provided.\n * Uses console.warn for 'info' and 'warn', console.error for 'error'.\n */\nconst defaultLogger: LoggerFn = (level, message, context) => {\n const prefix = '[LiveState]';\n if (level === 'error') {\n console.error(prefix, message, ...(context ? [context] : []));\n } else {\n console.warn(prefix, message, ...(context ? [context] : []));\n }\n};\n\n/**\n * Creates a logger bound to an optional consumer-provided onLog callback.\n * When onLog is provided it is called exclusively — the default console.*\n * fallback does NOT run, giving the consumer full control over output.\n *\n * @example\n * const log = createLogger(onLog);\n * log('warn', 'Amplitude API key not provided');\n * log('error', 'Failed to fetch', { url: '/v1/live-state', status: 500 });\n */\nexport function createLogger(onLog?: LoggerFn): LoggerFn {\n return (level, message, context) => {\n if (onLog) {\n onLog(level, message, context);\n } else {\n defaultLogger(level, message, context);\n }\n };\n}\n","import * as amplitude from '@amplitude/analytics-browser';\nimport type { TrackingProperties, TrackingValue } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Isolated Amplitude instance for the lib — avoids conflicts with the\n * consumer app's own Amplitude instance when both coexist on the same page.\n */\nconst amplitudeInstance = amplitude.createInstance();\n\n/**\n * Initialize Amplitude (fail silently)\n */\nexport function initializeAmplitude(apiKey: string, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n if (!apiKey) {\n logger('warn', 'Amplitude API key not provided');\n return;\n }\n\n amplitudeInstance.init(apiKey, {\n defaultTracking: false,\n });\n } catch (error) {\n logger('error', 'Failed to initialize Amplitude', { error });\n }\n}\n\n/**\n * Initialize Clarity (fail silently)\n */\nexport async function initializeClarity(\n projectId?: string,\n log?: LoggerFn,\n): Promise<void> {\n const logger = createLogger(log);\n try {\n // Check if Clarity is already loaded by the client\n if (getClarity()) {\n logger('info', 'Clarity already initialized by client');\n return;\n }\n\n // If client provided project ID, use it\n const finalProjectId = projectId || getClientClarityProjectId();\n\n if (!finalProjectId) {\n logger('warn', 'Clarity project ID not provided');\n return;\n }\n\n // Initialize Clarity with project ID\n const Clarity = (await import('@microsoft/clarity')).default;\n Clarity.init(finalProjectId);\n } catch (error) {\n logger('error', 'Failed to initialize Clarity', { error });\n }\n}\n\n/**\n * Try to extract Clarity project ID from client's installation\n */\nfunction getClientClarityProjectId(): string | undefined {\n try {\n // Check if Clarity script tag exists\n const clarityScript = document.querySelector('script[src*=\"clarity.ms\"]');\n if (clarityScript) {\n const src = clarityScript.getAttribute('src');\n const match = src?.match(/\\/([a-z0-9]+)$/);\n return match?.[1];\n }\n\n // Check global clarity config: shape is clarity.q = [['init', projectId, ...]]\n const projectId = getClarity()?.q?.[0]?.[1];\n if (typeof projectId === 'string') {\n return projectId;\n }\n } catch {\n // Fail silently\n }\n\n return undefined;\n}\n\n/**\n * Track event with Amplitude (fail silently)\n */\nexport function trackAmplitudeEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n amplitudeInstance.track(eventName, properties);\n } catch (error) {\n logger('error', 'Failed to track Amplitude event', { eventName, error });\n }\n}\n\n/**\n * Track event with Clarity (fail silently)\n */\nexport function trackClarityEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n const clarity = getClarity();\n if (clarity) {\n clarity('event', eventName);\n\n // Set custom tags for properties\n if (properties) {\n Object.entries(properties).forEach(([key, value]) => {\n clarity('set', key, String(value));\n });\n }\n }\n } catch (error) {\n logger('error', 'Failed to track Clarity event', { eventName, error });\n }\n}\n\n/**\n * Builds a tracking event name from prefix, page and event segments.\n * The prefix is expected to already include a trailing separator (e.g. 'nuvempago_lending_').\n * Page is optional — when absent the segment is omitted from the name.\n *\n * @example\n * buildEventName('nuvempago_lending_', 'dashboard', 'charge', 'view')\n * // → 'nuvempago_lending_dashboard_charge_view'\n *\n * buildEventName('nuvempago_lending_', undefined, 'charge', 'view')\n * // → 'nuvempago_lending_charge_view'\n */\nexport function buildEventName(\n prefix: string,\n page: string | undefined,\n context: string,\n event: string,\n): string {\n // Prefix already contains its trailing separator — join only the remaining segments\n const segments = [page, context, event].filter(Boolean);\n return `${prefix}${segments.join('_')}`;\n}\n\n/**\n * Builds a TrackingProperties object from a loose record, dropping entries\n * whose value is undefined so callers don't need to guard optional fields.\n */\nexport function buildTrackingProperties(\n source: Record<string, TrackingValue | undefined>,\n): TrackingProperties {\n return Object.fromEntries(\n Object.entries(source).filter(\n (entry): entry is [string, TrackingValue] => entry[1] !== undefined,\n ),\n );\n}\n\n/**\n * Track event with both Amplitude and Clarity (fail silently)\n */\nexport function trackEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n trackAmplitudeEvent(eventName, properties, log);\n trackClarityEvent(eventName, properties, log);\n}\n\n/**\n * Clarity command API shape — kept module-local to avoid polluting the\n * consumer's global Window type with a conflicting declaration.\n * https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api\n */\ntype ClarityCommand =\n | 'init'\n | 'event'\n | 'set'\n | 'identify'\n | 'consent'\n | 'upgrade';\n\ntype ClarityFn = {\n (command: ClarityCommand, ...args: string[]): void;\n q?: string[][];\n};\n\n/** Access window.clarity without declaring a global augmentation */\nfunction getClarity(): ClarityFn | undefined {\n return (window as unknown as Record<string, unknown>).clarity as\n | ClarityFn\n | undefined;\n}\n","import React, { createContext, useContext, useEffect, useMemo } from 'react';\nimport { SWRConfig } from 'swr';\nimport type {\n LiveStateFetcher,\n AnalyticsConfig,\n TrackingProperties,\n} from '../types';\nimport { initializeAmplitude, initializeClarity } from '../utils/analytics';\nimport { createLogger, type LoggerFn } from '../utils/logger';\n\ninterface LiveStateContextValue {\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n disabled?: boolean;\n log: LoggerFn;\n}\n\nconst LiveStateContext = createContext<LiveStateContextValue | null>(null);\n\nexport interface LiveStateProviderProps {\n children: React.ReactNode;\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n onLog?: LoggerFn;\n /**\n * When true, disables all analytics SDKs (Amplitude and Clarity).\n * Useful in development and test environments to suppress SDK warnings\n * and avoid polluting analytics data without needing to mock anything.\n * The `onEvent` and `onLog` callbacks still fire when disabled.\n *\n * @example\n * <LiveStateProvider disabled={process.env.NODE_ENV !== 'production'} />\n */\n disabled?: boolean;\n}\n\n/**\n * LiveStateProvider\n *\n * Root-level provider that configures fetcher function and analytics.\n * Initializes Amplitude and Clarity on mount.\n *\n * The optional `onEvent` callback is fired on every tracking event before\n * sending to Amplitude/Clarity — useful for simulators and debugging tools.\n *\n * The optional `onLog` callback receives all internal lib logs — route them\n * to your APM (Datadog, Sentry, etc.) to avoid relying on console.*.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateProvider({\n children,\n fetcher,\n analytics,\n onEvent,\n onLog,\n disabled = false,\n}: LiveStateProviderProps) {\n const log = useMemo(() => createLogger(onLog), [onLog]);\n\n useEffect(() => {\n if (disabled) return;\n\n if (analytics?.amplitudeKey) {\n initializeAmplitude(analytics.amplitudeKey, log);\n }\n\n if (analytics?.clarityProjectId) {\n initializeClarity(analytics.clarityProjectId, log);\n }\n }, [analytics, disabled, log]);\n\n const contextValue = useMemo(\n () => ({ fetcher, analytics, onEvent, disabled, log }),\n [fetcher, analytics, onEvent, disabled, log],\n );\n\n return (\n <SWRConfig value={{ provider: () => new Map() }}>\n <LiveStateContext.Provider value={contextValue}>\n {children}\n </LiveStateContext.Provider>\n </SWRConfig>\n );\n}\n\n/**\n * useLiveStateContext\n *\n * Internal hook to access LiveStateContext\n */\nexport function useLiveStateContext() {\n const context = useContext(LiveStateContext);\n\n if (!context) {\n throw new Error('useLiveState must be used within LiveStateProvider');\n }\n\n return context;\n}\n"],"mappings":";;;;;AAeA,IAAM,KAA2B,GAAO,GAAS,MAAY;CAC3D,IAAM,IAAS;AACf,CAAI,MAAU,UACZ,QAAQ,MAAM,GAAQ,GAAS,GAAI,IAAU,CAAC,EAAQ,GAAG,EAAE,CAAE,GAE7D,QAAQ,KAAK,GAAQ,GAAS,GAAI,IAAU,CAAC,EAAQ,GAAG,EAAE,CAAE;;AAchE,SAAgB,EAAa,GAA4B;AACvD,SAAQ,GAAO,GAAS,MAAY;AAClC,EAAI,IACF,EAAM,GAAO,GAAS,EAAQ,GAE9B,EAAc,GAAO,GAAS,EAAQ;;;;;AC/B5C,IAAM,IAAoB,EAAU,gBAAgB;AAKpD,SAAgB,EAAoB,GAAgB,GAAsB;CACxE,IAAM,IAAS,EAAa,EAAI;AAChC,KAAI;AACF,MAAI,CAAC,GAAQ;AACX,KAAO,QAAQ,iCAAiC;AAChD;;AAGF,IAAkB,KAAK,GAAQ,EAC7B,iBAAiB,IAClB,CAAC;UACK,GAAO;AACd,IAAO,SAAS,kCAAkC,EAAE,UAAO,CAAC;;;AAOhE,eAAsB,EACpB,GACA,GACe;CACf,IAAM,IAAS,EAAa,EAAI;AAChC,KAAI;AAEF,MAAI,GAAY,EAAE;AAChB,KAAO,QAAQ,wCAAwC;AACvD;;EAIF,IAAM,IAAiB,KAAa,GAA2B;AAE/D,MAAI,CAAC,GAAgB;AACnB,KAAO,QAAQ,kCAAkC;AACjD;;AAKF,GADiB,MAAM,OAAO,uBAAuB,QAC7C,KAAK,EAAe;UACrB,GAAO;AACd,IAAO,SAAS,gCAAgC,EAAE,UAAO,CAAC;;;AAO9D,SAAS,IAAgD;AACvD,KAAI;EAEF,IAAM,IAAgB,SAAS,cAAc,8BAA4B;AACzE,MAAI,EAGF,QAFY,EAAc,aAAa,MAAM,EAC1B,MAAM,iBAAiB,GAC3B;EAIjB,IAAM,IAAY,GAAY,EAAE,IAAI,KAAK;AACzC,MAAI,OAAO,KAAc,SACvB,QAAO;SAEH;;AAUV,SAAgB,EACd,GACA,GACA,GACM;CACN,IAAM,IAAS,EAAa,EAAI;AAChC,KAAI;AACF,IAAkB,MAAM,GAAW,EAAW;UACvC,GAAO;AACd,IAAO,SAAS,mCAAmC;GAAE;GAAW;GAAO,CAAC;;;AAO5E,SAAgB,EACd,GACA,GACA,GACM;CACN,IAAM,IAAS,EAAa,EAAI;AAChC,KAAI;EACF,IAAM,IAAU,GAAY;AAC5B,EAAI,MACF,EAAQ,SAAS,EAAU,EAGvB,KACF,OAAO,QAAQ,EAAW,CAAC,SAAS,CAAC,GAAK,OAAW;AACnD,KAAQ,OAAO,GAAK,OAAO,EAAM,CAAC;IAClC;UAGC,GAAO;AACd,IAAO,SAAS,iCAAiC;GAAE;GAAW;GAAO,CAAC;;;AAgB1E,SAAgB,EACd,GACA,GACA,GACA,GACQ;AAGR,QAAO,GAAG,IADO;EAAC;EAAM;EAAS;EAAM,CAAC,OAAO,QAAQ,CAC3B,KAAK,IAAI;;AAOvC,SAAgB,EACd,GACoB;AACpB,QAAO,OAAO,YACZ,OAAO,QAAQ,EAAO,CAAC,QACpB,MAA4C,EAAM,OAAO,KAAA,EAC3D,CACF;;AAMH,SAAgB,EACd,GACA,GACA,GACM;AAEN,CADA,EAAoB,GAAW,GAAY,EAAI,EAC/C,EAAkB,GAAW,GAAY,EAAI;;AAsB/C,SAAS,IAAoC;AAC3C,QAAQ,OAA8C;;;;AClLxD,IAAM,IAAmB,EAA4C,KAAK;AAkC1E,SAAgB,EAAkB,EAChC,aACA,YACA,cACA,YACA,UACA,cAAW,MACc;CACzB,IAAM,IAAM,QAAc,EAAa,EAAM,EAAE,CAAC,EAAM,CAAC;AAEvD,SAAgB;AACV,QAEA,GAAW,gBACb,EAAoB,EAAU,cAAc,EAAI,EAG9C,GAAW,oBACb,EAAkB,EAAU,kBAAkB,EAAI;IAEnD;EAAC;EAAW;EAAU;EAAI,CAAC;CAE9B,IAAM,IAAe,SACZ;EAAE;EAAS;EAAW;EAAS;EAAU;EAAK,GACrD;EAAC;EAAS;EAAW;EAAS;EAAU;EAAI,CAC7C;AAED,QACE,kBAAC,GAAD;EAAW,OAAO,EAAE,gCAAgB,IAAI,KAAK,EAAE;YAC7C,kBAAC,EAAiB,UAAlB;GAA2B,OAAO;GAC/B;GACyB,CAAA;EAClB,CAAA;;AAShB,SAAgB,IAAsB;CACpC,IAAM,IAAU,EAAW,EAAiB;AAE5C,KAAI,CAAC,EACH,OAAU,MAAM,qDAAqD;AAGvE,QAAO"}
@@ -0,0 +1,2 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`);c=s(c,1);let l=require(`swr`),u=require(`@amplitude/analytics-browser`);u=s(u,1);let d=require(`react/jsx-runtime`);var f=(e,t,n)=>{let r=`[LiveState]`;e===`error`?console.error(r,t,...n?[n]:[]):console.warn(r,t,...n?[n]:[])};function p(e){return(t,n,r)=>{e?e(t,n,r):f(t,n,r)}}var m=u.createInstance();function h(e,t){let n=p(t);try{if(!e){n(`warn`,`Amplitude API key not provided`);return}m.init(e,{defaultTracking:!1})}catch(e){n(`error`,`Failed to initialize Amplitude`,{error:e})}}async function g(e,t){let n=p(t);try{if(C()){n(`info`,`Clarity already initialized by client`);return}let t=e||_();if(!t){n(`warn`,`Clarity project ID not provided`);return}(await import(`@microsoft/clarity`)).default.init(t)}catch(e){n(`error`,`Failed to initialize Clarity`,{error:e})}}function _(){try{let e=document.querySelector(`script[src*="clarity.ms"]`);if(e)return e.getAttribute(`src`)?.match(/\/([a-z0-9]+)$/)?.[1];let t=C()?.q?.[0]?.[1];if(typeof t==`string`)return t}catch{}}function v(e,t,n){let r=p(n);try{m.track(e,t)}catch(t){r(`error`,`Failed to track Amplitude event`,{eventName:e,error:t})}}function y(e,t,n){let r=p(n);try{let n=C();n&&(n(`event`,e),t&&Object.entries(t).forEach(([e,t])=>{n(`set`,e,String(t))}))}catch(t){r(`error`,`Failed to track Clarity event`,{eventName:e,error:t})}}function b(e,t,n,r){return`${e}${[t,n,r].filter(Boolean).join(`_`)}`}function x(e){return Object.fromEntries(Object.entries(e).filter(e=>e[1]!==void 0))}function S(e,t,n){v(e,t,n),y(e,t,n)}function C(){return window.clarity}var w=(0,c.createContext)(null);function T({children:e,fetcher:t,analytics:n,onEvent:r,onLog:i,disabled:a=!1}){let o=(0,c.useMemo)(()=>p(i),[i]);(0,c.useEffect)(()=>{a||(n?.amplitudeKey&&h(n.amplitudeKey,o),n?.clarityProjectId&&g(n.clarityProjectId,o))},[n,a,o]);let s=(0,c.useMemo)(()=>({fetcher:t,analytics:n,onEvent:r,disabled:a,log:o}),[t,n,r,a,o]);return(0,d.jsx)(l.SWRConfig,{value:{provider:()=>new Map},children:(0,d.jsx)(w.Provider,{value:s,children:e})})}function E(){let e=(0,c.useContext)(w);if(!e)throw Error(`useLiveState must be used within LiveStateProvider`);return e}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return T}});
2
+ //# sourceMappingURL=LiveStateProvider-vcADappI.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveStateProvider-vcADappI.cjs","names":[],"sources":["../src/utils/logger.ts","../src/utils/analytics.ts","../src/providers/LiveStateProvider.tsx"],"sourcesContent":["import type { LogLevel } from '../types';\n\n/**\n * Logger function type — matches the signature of LiveStateProviderProps.onLog.\n */\nexport type LoggerFn = (\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n) => void;\n\n/**\n * Default logger — falls back to console.* when no onLog is provided.\n * Uses console.warn for 'info' and 'warn', console.error for 'error'.\n */\nconst defaultLogger: LoggerFn = (level, message, context) => {\n const prefix = '[LiveState]';\n if (level === 'error') {\n console.error(prefix, message, ...(context ? [context] : []));\n } else {\n console.warn(prefix, message, ...(context ? [context] : []));\n }\n};\n\n/**\n * Creates a logger bound to an optional consumer-provided onLog callback.\n * When onLog is provided it is called exclusively — the default console.*\n * fallback does NOT run, giving the consumer full control over output.\n *\n * @example\n * const log = createLogger(onLog);\n * log('warn', 'Amplitude API key not provided');\n * log('error', 'Failed to fetch', { url: '/v1/live-state', status: 500 });\n */\nexport function createLogger(onLog?: LoggerFn): LoggerFn {\n return (level, message, context) => {\n if (onLog) {\n onLog(level, message, context);\n } else {\n defaultLogger(level, message, context);\n }\n };\n}\n","import * as amplitude from '@amplitude/analytics-browser';\nimport type { TrackingProperties, TrackingValue } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Isolated Amplitude instance for the lib — avoids conflicts with the\n * consumer app's own Amplitude instance when both coexist on the same page.\n */\nconst amplitudeInstance = amplitude.createInstance();\n\n/**\n * Initialize Amplitude (fail silently)\n */\nexport function initializeAmplitude(apiKey: string, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n if (!apiKey) {\n logger('warn', 'Amplitude API key not provided');\n return;\n }\n\n amplitudeInstance.init(apiKey, {\n defaultTracking: false,\n });\n } catch (error) {\n logger('error', 'Failed to initialize Amplitude', { error });\n }\n}\n\n/**\n * Initialize Clarity (fail silently)\n */\nexport async function initializeClarity(\n projectId?: string,\n log?: LoggerFn,\n): Promise<void> {\n const logger = createLogger(log);\n try {\n // Check if Clarity is already loaded by the client\n if (getClarity()) {\n logger('info', 'Clarity already initialized by client');\n return;\n }\n\n // If client provided project ID, use it\n const finalProjectId = projectId || getClientClarityProjectId();\n\n if (!finalProjectId) {\n logger('warn', 'Clarity project ID not provided');\n return;\n }\n\n // Initialize Clarity with project ID\n const Clarity = (await import('@microsoft/clarity')).default;\n Clarity.init(finalProjectId);\n } catch (error) {\n logger('error', 'Failed to initialize Clarity', { error });\n }\n}\n\n/**\n * Try to extract Clarity project ID from client's installation\n */\nfunction getClientClarityProjectId(): string | undefined {\n try {\n // Check if Clarity script tag exists\n const clarityScript = document.querySelector('script[src*=\"clarity.ms\"]');\n if (clarityScript) {\n const src = clarityScript.getAttribute('src');\n const match = src?.match(/\\/([a-z0-9]+)$/);\n return match?.[1];\n }\n\n // Check global clarity config: shape is clarity.q = [['init', projectId, ...]]\n const projectId = getClarity()?.q?.[0]?.[1];\n if (typeof projectId === 'string') {\n return projectId;\n }\n } catch {\n // Fail silently\n }\n\n return undefined;\n}\n\n/**\n * Track event with Amplitude (fail silently)\n */\nexport function trackAmplitudeEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n amplitudeInstance.track(eventName, properties);\n } catch (error) {\n logger('error', 'Failed to track Amplitude event', { eventName, error });\n }\n}\n\n/**\n * Track event with Clarity (fail silently)\n */\nexport function trackClarityEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n const clarity = getClarity();\n if (clarity) {\n clarity('event', eventName);\n\n // Set custom tags for properties\n if (properties) {\n Object.entries(properties).forEach(([key, value]) => {\n clarity('set', key, String(value));\n });\n }\n }\n } catch (error) {\n logger('error', 'Failed to track Clarity event', { eventName, error });\n }\n}\n\n/**\n * Builds a tracking event name from prefix, page and event segments.\n * The prefix is expected to already include a trailing separator (e.g. 'nuvempago_lending_').\n * Page is optional — when absent the segment is omitted from the name.\n *\n * @example\n * buildEventName('nuvempago_lending_', 'dashboard', 'charge', 'view')\n * // → 'nuvempago_lending_dashboard_charge_view'\n *\n * buildEventName('nuvempago_lending_', undefined, 'charge', 'view')\n * // → 'nuvempago_lending_charge_view'\n */\nexport function buildEventName(\n prefix: string,\n page: string | undefined,\n context: string,\n event: string,\n): string {\n // Prefix already contains its trailing separator — join only the remaining segments\n const segments = [page, context, event].filter(Boolean);\n return `${prefix}${segments.join('_')}`;\n}\n\n/**\n * Builds a TrackingProperties object from a loose record, dropping entries\n * whose value is undefined so callers don't need to guard optional fields.\n */\nexport function buildTrackingProperties(\n source: Record<string, TrackingValue | undefined>,\n): TrackingProperties {\n return Object.fromEntries(\n Object.entries(source).filter(\n (entry): entry is [string, TrackingValue] => entry[1] !== undefined,\n ),\n );\n}\n\n/**\n * Track event with both Amplitude and Clarity (fail silently)\n */\nexport function trackEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n trackAmplitudeEvent(eventName, properties, log);\n trackClarityEvent(eventName, properties, log);\n}\n\n/**\n * Clarity command API shape — kept module-local to avoid polluting the\n * consumer's global Window type with a conflicting declaration.\n * https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api\n */\ntype ClarityCommand =\n | 'init'\n | 'event'\n | 'set'\n | 'identify'\n | 'consent'\n | 'upgrade';\n\ntype ClarityFn = {\n (command: ClarityCommand, ...args: string[]): void;\n q?: string[][];\n};\n\n/** Access window.clarity without declaring a global augmentation */\nfunction getClarity(): ClarityFn | undefined {\n return (window as unknown as Record<string, unknown>).clarity as\n | ClarityFn\n | undefined;\n}\n","import React, { createContext, useContext, useEffect, useMemo } from 'react';\nimport { SWRConfig } from 'swr';\nimport type {\n LiveStateFetcher,\n AnalyticsConfig,\n TrackingProperties,\n} from '../types';\nimport { initializeAmplitude, initializeClarity } from '../utils/analytics';\nimport { createLogger, type LoggerFn } from '../utils/logger';\n\ninterface LiveStateContextValue {\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n disabled?: boolean;\n log: LoggerFn;\n}\n\nconst LiveStateContext = createContext<LiveStateContextValue | null>(null);\n\nexport interface LiveStateProviderProps {\n children: React.ReactNode;\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n onLog?: LoggerFn;\n /**\n * When true, disables all analytics SDKs (Amplitude and Clarity).\n * Useful in development and test environments to suppress SDK warnings\n * and avoid polluting analytics data without needing to mock anything.\n * The `onEvent` and `onLog` callbacks still fire when disabled.\n *\n * @example\n * <LiveStateProvider disabled={process.env.NODE_ENV !== 'production'} />\n */\n disabled?: boolean;\n}\n\n/**\n * LiveStateProvider\n *\n * Root-level provider that configures fetcher function and analytics.\n * Initializes Amplitude and Clarity on mount.\n *\n * The optional `onEvent` callback is fired on every tracking event before\n * sending to Amplitude/Clarity — useful for simulators and debugging tools.\n *\n * The optional `onLog` callback receives all internal lib logs — route them\n * to your APM (Datadog, Sentry, etc.) to avoid relying on console.*.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateProvider({\n children,\n fetcher,\n analytics,\n onEvent,\n onLog,\n disabled = false,\n}: LiveStateProviderProps) {\n const log = useMemo(() => createLogger(onLog), [onLog]);\n\n useEffect(() => {\n if (disabled) return;\n\n if (analytics?.amplitudeKey) {\n initializeAmplitude(analytics.amplitudeKey, log);\n }\n\n if (analytics?.clarityProjectId) {\n initializeClarity(analytics.clarityProjectId, log);\n }\n }, [analytics, disabled, log]);\n\n const contextValue = useMemo(\n () => ({ fetcher, analytics, onEvent, disabled, log }),\n [fetcher, analytics, onEvent, disabled, log],\n );\n\n return (\n <SWRConfig value={{ provider: () => new Map() }}>\n <LiveStateContext.Provider value={contextValue}>\n {children}\n </LiveStateContext.Provider>\n </SWRConfig>\n );\n}\n\n/**\n * useLiveStateContext\n *\n * Internal hook to access LiveStateContext\n */\nexport function useLiveStateContext() {\n const context = useContext(LiveStateContext);\n\n if (!context) {\n throw new Error('useLiveState must be used within LiveStateProvider');\n }\n\n return context;\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./LiveStateProvider-BAkFYuZ2.cjs`);let t=require(`react`),n=require(`swr`);n=e.s(n,1);let r=require(`react/jsx-runtime`),i=require(`@nimbus-ds/components`),a=require(`@nimbus-ds/icons`);function o(){let{fetcher:t,log:r}=e.n(),{data:i,error:a,isLoading:o,mutate:s}=(0,n.default)(`live-state`,t,{revalidateOnFocus:!1,revalidateOnReconnect:!0,dedupingInterval:6e4,shouldRetryOnError:!1,onError:e=>{r(`error`,`Failed to fetch live state`,{error:e})}});return{liveState:i??null,isLoading:o,isError:a!=null,error:a??null,refresh:async()=>{try{await s()}catch(e){r(`error`,`Failed to refresh live state`,{error:e})}}}}function s(){let{onEvent:n,disabled:r,log:i}=e.n();return(0,t.useCallback)((t,a)=>{n?.(t,a),r||e.a(t,a,i)},[n,r,i])}var c=`@tiendanube/live-state:closable`,l=c;function u(){try{let e=localStorage.getItem(l);if(!e)return{};let t=JSON.parse(e);if(typeof t!=`object`||!t)return{};let n={};for(let[e,r]of Object.entries(t))if(!(typeof r!=`object`||!r)){n[e]={};for(let[t,i]of Object.entries(r))if(typeof i==`number`)n[e][t]={count:i,closedAt:0};else if(typeof i==`object`&&i){let r=i;n[e][t]={count:typeof r.count==`number`?r.count:0,closedAt:typeof r.closedAt==`number`?r.closedAt:0}}}return n}catch{return{}}}function d(e){try{localStorage.setItem(l,JSON.stringify(e))}catch{}}function f({context:e,id:n,maxCloseTimes:r=3,expiresIn:i}){let[a,o]=(0,t.useState)(null);return(0,t.useEffect)(()=>{if(!n){o(!0);return}let t=u(),a=t[e]?.[n];if(!a){o(!0);return}let{count:s,closedAt:c}=a;if(i!=null&&c>0&&Date.now()-c>i){let r={...t};r[e]&&(delete r[e][n],d(r)),o(!0);return}o(s<r)},[e,n,r,i]),{isVisible:a,close:(0,t.useCallback)(()=>{if(o(!1),!n)return;let t=u(),r=t[e]?.[n]?.count??0;d({...t,[e]:{...t[e]??{},[n]:{count:r+1,closedAt:Date.now()}}})},[e,n])}}function p(t,n){let r=e.o(n);try{switch(t.type){case`redirect`:window.location.href=t.url;break;case`whatsapp`:{let e=t.whatsappMessage?`${t.url}?text=${encodeURIComponent(t.whatsappMessage)}`:t.url;window.open(e,`_blank`);break}case`external`:window.open(t.url,`_blank`);break;default:r(`warn`,`Unknown CTA type: ${t.type}`,{cta:t})}}catch(e){r(`error`,`Failed to handle CTA click`,{cta:t,error:e})}}function m({data:n,trackingConfig:a,onClose:o,onCtaClick:c}){let l=s(),{log:u}=e.n(),d=n.type===`alert`?`danger`:`warning`,f=(0,t.useCallback)(()=>e.i({context:n.context,campaignId:n.campaignId,group:n.group,...n.metadata,...a.properties}),[n,a.properties]),m=(0,t.useCallback)(()=>{l(e.r(a.prefix,a.page,n.context,`click`),f()),c?c(n.cta):p(n.cta,u)},[n,a,f,l,c,u]),h=(0,t.useCallback)(()=>{l(e.r(a.prefix,a.page,n.context,`close`),f()),o?.()},[o,n,a,f,l]);return(0,t.useEffect)(()=>{l(e.r(a.prefix,a.page,n.context,`view`),f())},[]),(0,r.jsx)(i.Alert,{appearance:d,title:n.title,onRemove:o?h:void 0,children:(0,r.jsxs)(i.Box,{display:`flex`,alignItems:`flex-start`,gap:`3`,flexWrap:`wrap`,flexDirection:`column`,children:[(0,r.jsx)(i.Box,{flex:`1 1 auto`,minWidth:`220px`,children:(0,r.jsx)(i.Text,{children:n.message})}),(0,r.jsx)(i.Box,{display:`flex`,justifyContent:`flex-end`,marginTop:`2`,children:(0,r.jsx)(i.Button,{appearance:`primary`,onClick:m,children:n.cta.label})})]})})}var h={blue:{background:`#0050C3`,icon:`#FFFFFF`},white:{background:`#FFFFFF`,icon:`#0059D5`}};function g({data:n,trackingConfig:o,defaultVariant:c=`blue`,onClose:l,isMobile:u,onCtaClick:d}){let f=s(),{log:m}=e.n(),g=n.variant||c,_=h[g],[v,y]=(0,t.useState)(()=>window.innerWidth<750);(0,t.useEffect)(()=>{let e=()=>y(window.innerWidth<750);return window.addEventListener(`resize`,e),()=>window.removeEventListener(`resize`,e)},[]);let b=u??v,x=(0,t.useCallback)(()=>e.i({context:n.context,campaignId:n.campaignId,group:n.group,...n.metadata,...o.properties}),[n,o.properties]),S=(0,t.useCallback)(()=>{f(e.r(o.prefix,o.page,n.context,`click`),x()),d?d(n.cta):p(n.cta,m)},[n,o,x,f,d,m]),C=(0,t.useCallback)(()=>{f(e.r(o.prefix,o.page,n.context,`close`),x()),l?.()},[l,n,o,x,f]);(0,t.useEffect)(()=>{f(e.r(o.prefix,o.page,n.context,`view`),x())},[]);let w=(0,r.jsx)(i.Link,{as:`a`,textDecoration:`none`,onClick:S,"data-testid":`live-state-cta-link`,children:(0,r.jsx)(i.Text,{color:`primary-interactive`,fontSize:`base`,children:n.cta.label})});return(0,r.jsx)(i.Card,{children:(0,r.jsxs)(i.Box,{display:`flex`,flexDirection:`row`,justifyContent:`space-between`,children:[(0,r.jsxs)(i.Box,{display:`flex`,gap:`4`,alignItems:b?`flex-start`:`center`,paddingRight:`1-5`,children:[(0,r.jsx)(i.Box,{minWidth:`32px`,children:(0,r.jsx)(`div`,{style:{width:32,height:32,borderRadius:`35%`,borderColor:g===`white`?`#E7E7E7`:`transparent`,borderWidth:1,borderStyle:`solid`,background:_.background,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,r.jsx)(a.MoneyIcon,{style:{color:_.icon}})})}),(0,r.jsxs)(i.Box,{display:`grid`,gap:`2`,children:[(0,r.jsxs)(i.Box,{children:[(0,r.jsx)(i.Text,{fontSize:`base`,color:`neutral-textHigh`,children:n.title}),(0,r.jsx)(i.Text,{fontSize:`base`,color:`neutral-textLow`,children:n.message})]}),b&&w]})]}),(!b||l)&&(0,r.jsxs)(i.Box,{display:`flex`,flexDirection:b?`column`:`row`,alignItems:b?`flex-end`:`center`,gap:`2`,children:[!b&&w,l&&(0,r.jsx)(`button`,{type:`button`,"data-testid":`live-state-close-button`,"aria-label":`Fechar notificação`,onClick:C,style:{background:`none`,border:`none`,padding:0,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,r.jsx)(i.Icon,{source:(0,r.jsx)(a.CloseIcon,{width:`18px`,height:`18px`}),color:`neutral-interactive`})})]})]})})}function _({data:t,loading:n,trackingConfig:i,allowedContexts:a,defaultVariant:o,isMobile:s,onCtaClick:c}){let{log:l}=e.n(),{isVisible:u,close:d}=f({context:t?.context??``,id:t?.campaignId,maxCloseTimes:t?.metadata?.maxCloseTimes,expiresIn:t?.metadata?.expiresIn});if(n||!t)return null;if(!t.context||!t.type||!t.title||!t.message||!t.cta?.label||!t.cta?.url||!t.cta?.type)return l(`warn`,`Invalid payload, missing required fields`,{data:t}),null;if(a&&!a.includes(t.context)||u===null||!u)return null;let p=t.closable?d:void 0;return t.type===`alert`||t.type===`warning`?(0,r.jsx)(m,{data:t,onClose:p,trackingConfig:i,onCtaClick:c}):t.type===`info`?(0,r.jsx)(g,{data:t,onClose:p,trackingConfig:i,defaultVariant:o,isMobile:s,onCtaClick:c}):(l(`warn`,`Unknown type: ${t.type}`,{data:t}),null)}exports.LIVE_STATE_STORAGE_KEY=c,exports.LiveStateAlert=m,exports.LiveStateInfo=g,exports.LiveStateProvider=e.t,exports.LiveStateRenderer=_,exports.handleCtaClick=p,exports.trackEvent=e.a,exports.useLiveState=o,exports.useTrackEvent=s;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./LiveStateProvider-vcADappI.cjs`);let t=require(`react`),n=require(`swr`);n=e.s(n,1);let r=require(`react/jsx-runtime`),i=require(`@nimbus-ds/components`),a=require(`@nimbus-ds/icons`);function o(){let{fetcher:t,log:r}=e.n(),{data:i,error:a,isLoading:o,mutate:s}=(0,n.default)(`live-state`,t,{revalidateOnFocus:!1,revalidateOnReconnect:!0,dedupingInterval:6e4,shouldRetryOnError:!1,onError:e=>{r(`error`,`Failed to fetch live state`,{error:e})}});return{liveState:i??null,isLoading:o,isError:a!=null,error:a??null,refresh:async()=>{try{await s()}catch(e){r(`error`,`Failed to refresh live state`,{error:e})}}}}function s(){let{onEvent:n,disabled:r,log:i}=e.n();return(0,t.useCallback)((t,a)=>{n?.(t,a),r||e.a(t,a,i)},[n,r,i])}var c=`@tiendanube/live-state:closable`,l=c;function u(){try{let e=localStorage.getItem(l);if(!e)return{};let t=JSON.parse(e);if(typeof t!=`object`||!t)return{};let n={};for(let[e,r]of Object.entries(t))if(!(typeof r!=`object`||!r)){n[e]={};for(let[t,i]of Object.entries(r))if(typeof i==`number`)n[e][t]={count:i,closedAt:0};else if(typeof i==`object`&&i){let r=i;n[e][t]={count:typeof r.count==`number`?r.count:0,closedAt:typeof r.closedAt==`number`?r.closedAt:0}}}return n}catch{return{}}}function d(e){try{localStorage.setItem(l,JSON.stringify(e))}catch{}}function f({context:e,id:n,maxCloseTimes:r=3,expiresIn:i}){let[a,o]=(0,t.useState)(null);return(0,t.useEffect)(()=>{if(!n){o(!0);return}let t=u(),a=t[e]?.[n];if(!a){o(!0);return}let{count:s,closedAt:c}=a;if(i!=null&&c>0&&Date.now()-c>i){let r={...t};r[e]&&(delete r[e][n],d(r)),o(!0);return}o(s<r)},[e,n,r,i]),{isVisible:a,close:(0,t.useCallback)(()=>{if(o(!1),!n)return;let t=u(),r=t[e]?.[n]?.count??0;d({...t,[e]:{...t[e]??{},[n]:{count:r+1,closedAt:Date.now()}}})},[e,n])}}function p(t,n){let r=e.o(n);try{switch(t.type){case`redirect`:window.location.href=t.url;break;case`whatsapp`:{let e=t.whatsappMessage?`${t.url}?text=${encodeURIComponent(t.whatsappMessage)}`:t.url;window.open(e,`_blank`);break}case`external`:window.open(t.url,`_blank`);break;default:r(`warn`,`Unknown CTA type: ${t.type}`,{cta:t})}}catch(e){r(`error`,`Failed to handle CTA click`,{cta:t,error:e})}}var m=new Set([`strong`,`em`,`b`,`i`,`br`,`span`]),h=/<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[\s\S]*?<\/\1\s*>|<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[^>]*\/?>/gi;function g(e){return e.replace(h,``).replace(/<([a-zA-Z][a-zA-Z0-9]*)\s[^>]*>/g,`<$1>`).replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*>/g,(e,t)=>m.has(t.toLowerCase())?e:``)}function _(e){return/<[a-zA-Z][^>]*>/.test(e)}function v({content:e,...t}){if(_(e)){let n=g(e);return(0,r.jsx)(i.Text,{...t,children:(0,r.jsx)(`span`,{dangerouslySetInnerHTML:{__html:n}})})}return(0,r.jsx)(i.Text,{...t,children:e})}function y({data:n,trackingConfig:a,onClose:o,onCtaClick:c}){let l=s(),{log:u}=e.n(),d=n.type===`alert`?`danger`:`warning`,f=(0,t.useCallback)(()=>e.i({context:n.context,campaignId:n.campaignId,group:n.group,...n.metadata,...a.properties}),[n,a.properties]),m=(0,t.useCallback)(()=>{l(e.r(a.prefix,a.page,n.context,`click`),f()),c?c(n.cta):p(n.cta,u)},[n,a,f,l,c,u]),h=(0,t.useCallback)(()=>{l(e.r(a.prefix,a.page,n.context,`close`),f()),o?.()},[o,n,a,f,l]);return(0,t.useEffect)(()=>{l(e.r(a.prefix,a.page,n.context,`view`),f())},[]),(0,r.jsx)(i.Alert,{appearance:d,title:n.title,onRemove:o?h:void 0,children:(0,r.jsxs)(i.Box,{display:`flex`,alignItems:`flex-start`,gap:`3`,flexWrap:`wrap`,flexDirection:`column`,children:[(0,r.jsx)(i.Box,{flex:`1 1 auto`,minWidth:`220px`,children:(0,r.jsx)(v,{content:n.message})}),(0,r.jsx)(i.Box,{display:`flex`,justifyContent:`flex-end`,marginTop:`2`,children:(0,r.jsx)(i.Button,{appearance:`primary`,onClick:m,children:n.cta.label})})]})})}var b={blue:{background:`#0050C3`,icon:`#FFFFFF`},white:{background:`#FFFFFF`,icon:`#0059D5`}};function x({data:n,trackingConfig:o,defaultVariant:c=`blue`,onClose:l,isMobile:u,onCtaClick:d}){let f=s(),{log:m}=e.n(),h=n.variant||c,g=b[h],[_,y]=(0,t.useState)(()=>window.innerWidth<750);(0,t.useEffect)(()=>{let e=()=>y(window.innerWidth<750);return window.addEventListener(`resize`,e),()=>window.removeEventListener(`resize`,e)},[]);let x=u??_,S=(0,t.useCallback)(()=>e.i({context:n.context,campaignId:n.campaignId,group:n.group,...n.metadata,...o.properties}),[n,o.properties]),C=(0,t.useCallback)(()=>{f(e.r(o.prefix,o.page,n.context,`click`),S()),d?d(n.cta):p(n.cta,m)},[n,o,S,f,d,m]),w=(0,t.useCallback)(()=>{f(e.r(o.prefix,o.page,n.context,`close`),S()),l?.()},[l,n,o,S,f]);(0,t.useEffect)(()=>{f(e.r(o.prefix,o.page,n.context,`view`),S())},[]);let T=(0,r.jsx)(i.Link,{as:`a`,textDecoration:`none`,onClick:C,"data-testid":`live-state-cta-link`,children:(0,r.jsx)(i.Text,{color:`primary-interactive`,fontSize:`base`,children:n.cta.label})});return(0,r.jsx)(i.Card,{children:(0,r.jsxs)(i.Box,{display:`flex`,flexDirection:`row`,justifyContent:`space-between`,children:[(0,r.jsxs)(i.Box,{display:`flex`,gap:`4`,alignItems:x?`flex-start`:`center`,paddingRight:`1-5`,children:[(0,r.jsx)(i.Box,{minWidth:`32px`,children:(0,r.jsx)(`div`,{style:{width:32,height:32,borderRadius:`35%`,borderColor:h===`white`?`#E7E7E7`:`transparent`,borderWidth:1,borderStyle:`solid`,background:g.background,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,r.jsx)(a.MoneyIcon,{style:{color:g.icon}})})}),(0,r.jsxs)(i.Box,{display:`grid`,gap:`2`,children:[(0,r.jsxs)(i.Box,{children:[(0,r.jsx)(v,{content:n.title,fontSize:`base`,color:`neutral-textHigh`}),(0,r.jsx)(v,{content:n.message,fontSize:`base`,color:`neutral-textLow`})]}),x&&T]})]}),(!x||l)&&(0,r.jsxs)(i.Box,{display:`flex`,flexDirection:x?`column`:`row`,alignItems:x?`flex-end`:`center`,gap:`2`,children:[!x&&T,l&&(0,r.jsx)(`button`,{type:`button`,"data-testid":`live-state-close-button`,"aria-label":`Fechar notificação`,onClick:w,style:{background:`none`,border:`none`,padding:0,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,r.jsx)(i.Icon,{source:(0,r.jsx)(a.CloseIcon,{width:`18px`,height:`18px`}),color:`neutral-interactive`})})]})]})})}function S({data:t,loading:n,trackingConfig:i,allowedContexts:a,defaultVariant:o,isMobile:s,onCtaClick:c}){let{log:l}=e.n(),{isVisible:u,close:d}=f({context:t?.context??``,id:t?.campaignId,maxCloseTimes:t?.metadata?.maxCloseTimes,expiresIn:t?.metadata?.expiresIn});if(n||!t)return null;if(!t.context||!t.type||!t.title||!t.message||!t.cta?.label||!t.cta?.url||!t.cta?.type)return l(`warn`,`Invalid payload, missing required fields`,{data:t}),null;if(a&&!a.includes(t.context)||u===null||!u)return null;let p=t.closable?d:void 0;return t.type===`alert`||t.type===`warning`?(0,r.jsx)(y,{data:t,onClose:p,trackingConfig:i,onCtaClick:c}):t.type===`info`?(0,r.jsx)(x,{data:t,onClose:p,trackingConfig:i,defaultVariant:o,isMobile:s,onCtaClick:c}):(l(`warn`,`Unknown type: ${t.type}`,{data:t}),null)}exports.LIVE_STATE_STORAGE_KEY=c,exports.LiveStateAlert=y,exports.LiveStateInfo=x,exports.LiveStateProvider=e.t,exports.LiveStateRenderer=S,exports.handleCtaClick=p,exports.trackEvent=e.a,exports.useLiveState=o,exports.useTrackEvent=s;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/hooks/useLiveState.ts","../src/hooks/useTrackEvent.ts","../src/utils/closable-storage.ts","../src/hooks/useClosable.ts","../src/utils/cta.ts","../src/components/LiveStateAlert.tsx","../src/components/LiveStateInfo.tsx","../src/components/LiveStateRenderer.tsx"],"sourcesContent":["import useSWR from 'swr';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport type { LiveStateResponse } from '../types';\n\ninterface UseLiveStateReturn {\n /** Live state data */\n liveState: LiveStateResponse | null;\n\n /** True while the first fetch is in progress */\n isLoading: boolean;\n\n /**\n * True when the last fetch attempt resulted in an error.\n * Use this to implement fallbacks or custom error logging.\n * Note: the lib already logs a warning to the console on every error.\n */\n isError: boolean;\n\n /**\n * The raw error from the last failed fetch, or null.\n * Pair with `isError` for more specific error handling.\n */\n error: Error | null;\n\n /** Manually trigger a re-fetch */\n refresh: () => Promise<void>;\n}\n\n/**\n * useLiveState hook\n *\n * Fetches and manages live state data using SWR with automatic caching,\n * deduplication, and revalidation.\n *\n * Must be used within LiveStateProvider. See examples/ directory for usage.\n */\nexport function useLiveState(): UseLiveStateReturn {\n const { fetcher, log } = useLiveStateContext();\n\n const { data, error, isLoading, mutate } = useSWR<LiveStateResponse | null>(\n 'live-state',\n fetcher,\n {\n revalidateOnFocus: false,\n revalidateOnReconnect: true,\n dedupingInterval: 60000, // 1 minute\n shouldRetryOnError: false, // Fail silently\n onError: err => {\n log('error', 'Failed to fetch live state', { error: err });\n },\n },\n );\n\n const refresh = async () => {\n try {\n await mutate();\n } catch (err) {\n log('error', 'Failed to refresh live state', { error: err });\n }\n };\n\n return {\n liveState: data ?? null,\n isLoading,\n isError: error != null,\n error: error ?? null,\n refresh,\n };\n}\n","import { useCallback } from 'react';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { trackEvent } from '../utils/analytics';\nimport type { TrackingProperties } from '../types';\n\n/**\n * useTrackEvent\n *\n * Returns a `track` function that fires both the internal analytics\n * (Amplitude + Clarity) and the optional `onEvent` callback provided\n * to LiveStateProvider — useful for simulators and debugging tools.\n */\nexport function useTrackEvent() {\n const { onEvent, disabled, log } = useLiveStateContext();\n\n const track = useCallback(\n (eventName: string, properties?: TrackingProperties) => {\n // onEvent callback fires even when disabled — it is an observability\n // hook, not an analytics SDK, so the caller opts in deliberately.\n onEvent?.(eventName, properties);\n\n // Skip SDK calls when analytics are disabled (e.g. dev/test environments)\n if (!disabled) {\n trackEvent(eventName, properties, log);\n }\n },\n [onEvent, disabled, log],\n );\n\n return track;\n}\n","export const LIVE_STATE_STORAGE_KEY = '@tiendanube/live-state:closable';\n\n/** @internal */\nconst STORAGE_KEY = LIVE_STATE_STORAGE_KEY;\n\nexport interface ClosableState {\n count: number;\n closedAt: number;\n}\n\nexport type ClosableStates = Record<string, Record<string, ClosableState>>;\n\n/**\n * Reads closable states from localStorage.\n * Normalises legacy format (plain number) to the current shape ({ count, closedAt }).\n * Returns an empty object on any error (fail-silent).\n */\nexport function getClosableStates(): ClosableStates {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return {};\n\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return {};\n\n const normalized: ClosableStates = {};\n\n for (const [ctx, ctxState] of Object.entries(\n parsed as Record<string, unknown>,\n )) {\n if (typeof ctxState !== 'object' || ctxState === null) continue;\n\n normalized[ctx] = {};\n\n for (const [id, entry] of Object.entries(\n ctxState as Record<string, unknown>,\n )) {\n if (typeof entry === 'number') {\n // legacy format: plain close count\n normalized[ctx][id] = { count: entry, closedAt: 0 };\n } else if (typeof entry === 'object' && entry !== null) {\n const candidate = entry as Partial<ClosableState>;\n normalized[ctx][id] = {\n count: typeof candidate.count === 'number' ? candidate.count : 0,\n closedAt:\n typeof candidate.closedAt === 'number' ? candidate.closedAt : 0,\n };\n }\n }\n }\n\n return normalized;\n } catch {\n return {};\n }\n}\n\n/**\n * Persists closable states to localStorage.\n * Fails silently if localStorage is unavailable or full.\n */\nexport function setClosableStates(states: ClosableStates): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(states));\n } catch {\n // localStorage unavailable or quota exceeded — application continues normally\n }\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport {\n getClosableStates,\n setClosableStates,\n} from '../utils/closable-storage';\n\nexport interface UseClosableOptions {\n /** Notification context (e.g. 'awareness', 'charge') */\n context: string;\n /** Unique notification ID (campaignId). If absent, always visible — no close tracking. */\n id: string | undefined;\n /** Maximum number of times the user can close before it is hidden permanently */\n maxCloseTimes?: number;\n /** Milliseconds after which the close counter resets (optional) */\n expiresIn?: number;\n}\n\nexport interface UseClosableReturn {\n /** Whether the notification should be shown. null = initial check in progress. */\n isVisible: boolean | null;\n /** Call this to record a close action */\n close: () => void;\n}\n\n/**\n * Manages the close state of a notification using localStorage.\n *\n * After the user closes a notification `maxCloseTimes` times, it is hidden\n * permanently. Optionally, the counter resets after `expiresIn` milliseconds.\n *\n * Fails silently — errors in localStorage never break the application.\n *\n * @example\n * ```tsx\n * const { isVisible, close } = useClosable({\n * context: 'awareness',\n * id: 'awareness-2026-q1',\n * maxCloseTimes: 3,\n * expiresIn: 86_400_000, // 1 day\n * });\n *\n * if (!isVisible) return null;\n * return <Notification onClose={close} />;\n * ```\n */\nexport function useClosable({\n context,\n id,\n maxCloseTimes = 3,\n expiresIn,\n}: UseClosableOptions): UseClosableReturn {\n const [isVisible, setIsVisible] = useState<boolean | null>(null);\n\n useEffect(() => {\n // No ID means no close tracking — always show\n if (!id) {\n setIsVisible(true);\n return;\n }\n\n const states = getClosableStates();\n const entry = states[context]?.[id];\n\n if (!entry) {\n setIsVisible(true);\n return;\n }\n\n const { count, closedAt } = entry;\n\n // Check expiry — reset counter and show if expired\n if (expiresIn != null && closedAt > 0) {\n const elapsed = Date.now() - closedAt;\n if (elapsed > expiresIn) {\n const updated = { ...states };\n if (updated[context]) {\n delete updated[context][id];\n setClosableStates(updated);\n }\n setIsVisible(true);\n return;\n }\n }\n\n setIsVisible(count < maxCloseTimes);\n }, [context, id, maxCloseTimes, expiresIn]);\n\n const close = useCallback(() => {\n // Always hide visually\n setIsVisible(false);\n\n // Only persist to localStorage if there is an id to track\n if (!id) return;\n\n const states = getClosableStates();\n const currentCount = states[context]?.[id]?.count ?? 0;\n\n const updated: typeof states = {\n ...states,\n [context]: {\n ...(states[context] ?? {}),\n [id]: {\n count: currentCount + 1,\n closedAt: Date.now(),\n },\n },\n };\n\n setClosableStates(updated);\n }, [context, id]);\n\n return { isVisible, close };\n}\n","import type { CtaConfig } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Handle CTA click based on type\n *\n * @param cta - CTA configuration from LiveStateResponse\n * @param log - Optional logger (defaults to console.warn)\n */\nexport function handleCtaClick(cta: CtaConfig, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n switch (cta.type) {\n case 'redirect':\n window.location.href = cta.url;\n break;\n\n case 'whatsapp': {\n const whatsappUrl = cta.whatsappMessage\n ? `${cta.url}?text=${encodeURIComponent(cta.whatsappMessage)}`\n : cta.url;\n window.open(whatsappUrl, '_blank');\n break;\n }\n\n case 'external':\n window.open(cta.url, '_blank');\n break;\n\n default:\n logger('warn', `Unknown CTA type: ${(cta as CtaConfig).type}`, { cta });\n }\n } catch (error) {\n logger('error', 'Failed to handle CTA click', { cta, error });\n }\n}\n","import { useCallback, useEffect } from 'react';\nimport { Alert, Box, Button, Text } from '@nimbus-ds/components';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\n\nexport interface LiveStateAlertProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\n/**\n * LiveStateAlert\n *\n * Renders an Alert component (Nimbus) for alert/warning types.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateAlert({\n data,\n trackingConfig,\n onClose,\n onCtaClick,\n}: LiveStateAlertProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const appearance = data.type === 'alert' ? 'danger' : 'warning';\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <Alert\n appearance={appearance}\n title={data.title}\n onRemove={onClose ? handleClose : undefined}>\n <Box\n display=\"flex\"\n alignItems=\"flex-start\"\n gap=\"3\"\n flexWrap=\"wrap\"\n flexDirection=\"column\">\n <Box flex=\"1 1 auto\" minWidth=\"220px\">\n <Text>{data.message}</Text>\n </Box>\n\n <Box display=\"flex\" justifyContent=\"flex-end\" marginTop=\"2\">\n <Button appearance=\"primary\" onClick={handleClick}>\n {data.cta.label}\n </Button>\n </Box>\n </Box>\n </Alert>\n );\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport { Card, Box, Text, Link, Icon } from '@nimbus-ds/components';\nimport { MoneyIcon, CloseIcon } from '@nimbus-ds/icons';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\n\nexport interface LiveStateInfoProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n defaultVariant?: 'blue' | 'white';\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override mobile layout detection.\n * When provided, bypasses `window.innerWidth < 750` so simulators and\n * testing tools can force a specific layout without resizing the window.\n */\n isMobile?: boolean;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\nconst ICON_COLOR_MAP = {\n blue: { background: '#0050C3', icon: '#FFFFFF' },\n white: { background: '#FFFFFF', icon: '#0059D5' },\n} as const;\n\n/**\n * LiveStateInfo\n *\n * Renders a Card component (Nimbus) for info type.\n * Uses MoneyIcon with colored circle background, Text components,\n * and a Link-style CTA — matching the LendingBanner design reference.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateInfo({\n data,\n trackingConfig,\n defaultVariant = 'blue',\n onClose,\n isMobile: isMobileProp,\n onCtaClick,\n}: LiveStateInfoProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const variant = data.variant || defaultVariant;\n const iconConfig = ICON_COLOR_MAP[variant];\n\n const [isMobileWindow, setIsMobileWindow] = useState(\n () => window.innerWidth < 750,\n );\n\n useEffect(() => {\n const handler = () => setIsMobileWindow(window.innerWidth < 750);\n window.addEventListener('resize', handler);\n return () => window.removeEventListener('resize', handler);\n }, []);\n\n // Prop overrides window detection — allows simulators/tests to force layout\n const isMobile = isMobileProp ?? isMobileWindow;\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const ctaLink = (\n <Link\n as=\"a\"\n textDecoration=\"none\"\n onClick={handleClick}\n data-testid=\"live-state-cta-link\">\n <Text color=\"primary-interactive\" fontSize=\"base\">\n {data.cta.label}\n </Text>\n </Link>\n );\n\n return (\n <Card>\n <Box display=\"flex\" flexDirection=\"row\" justifyContent=\"space-between\">\n {/* Left: icon + text (+ cta on mobile) */}\n <Box\n display=\"flex\"\n gap=\"4\"\n alignItems={isMobile ? 'flex-start' : 'center'}\n paddingRight=\"1-5\">\n <Box minWidth=\"32px\">\n <div\n style={{\n width: 32,\n height: 32,\n borderRadius: '35%',\n borderColor: variant === 'white' ? '#E7E7E7' : 'transparent',\n borderWidth: 1,\n borderStyle: 'solid',\n background: iconConfig.background,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <MoneyIcon style={{ color: iconConfig.icon }} />\n </div>\n </Box>\n\n <Box display=\"grid\" gap=\"2\">\n <Box>\n <Text fontSize=\"base\" color=\"neutral-textHigh\">\n {data.title}\n </Text>\n <Text fontSize=\"base\" color=\"neutral-textLow\">\n {data.message}\n </Text>\n </Box>\n\n {/* CTA shown below text on mobile */}\n {isMobile && ctaLink}\n </Box>\n </Box>\n\n {/* Right: cta (desktop only) + close button */}\n {(!isMobile || onClose) && (\n <Box\n display=\"flex\"\n flexDirection={isMobile ? 'column' : 'row'}\n alignItems={isMobile ? 'flex-end' : 'center'}\n gap=\"2\">\n {/* CTA shown on the right side on desktop */}\n {!isMobile && ctaLink}\n\n {onClose && (\n <button\n type=\"button\"\n data-testid=\"live-state-close-button\"\n aria-label=\"Fechar notificação\"\n onClick={handleClose}\n style={{\n background: 'none',\n border: 'none',\n padding: 0,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <Icon\n source={<CloseIcon width=\"18px\" height=\"18px\" />}\n color=\"neutral-interactive\"\n />\n </button>\n )}\n </Box>\n )}\n </Box>\n </Card>\n );\n}\n","import type { LiveStateRendererProps } from '../types';\nimport { useClosable } from '../hooks/useClosable';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { LiveStateAlert } from './LiveStateAlert';\nimport { LiveStateInfo } from './LiveStateInfo';\n\n/**\n * LiveStateRenderer\n *\n * Main component that validates payload, filters by context, and renders\n * the appropriate visual component (Alert or Info) based on type.\n * Handles closable notifications via useClosable when closable is configured.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateRenderer({\n data,\n loading,\n trackingConfig,\n allowedContexts,\n defaultVariant,\n isMobile,\n onCtaClick,\n}: LiveStateRendererProps) {\n const { log } = useLiveStateContext();\n const { isVisible, close } = useClosable({\n context: data?.context ?? '',\n id: data?.campaignId,\n maxCloseTimes: data?.metadata?.maxCloseTimes,\n expiresIn: data?.metadata?.expiresIn,\n });\n\n // Don't render while loading or no data\n if (loading || !data) {\n return null;\n }\n\n // Validate required fields\n if (\n !data.context ||\n !data.type ||\n !data.title ||\n !data.message ||\n !data.cta?.label ||\n !data.cta?.url ||\n !data.cta?.type\n ) {\n log('warn', 'Invalid payload, missing required fields', { data });\n return null;\n }\n\n // Filter by allowed contexts\n if (allowedContexts && !allowedContexts.includes(data.context)) {\n return null;\n }\n\n // Awaiting localStorage check\n if (isVisible === null) {\n return null;\n }\n\n // Hidden — user reached maxCloseTimes\n if (!isVisible) {\n return null;\n }\n\n // Pass onClose whenever closable is true.\n // campaignId is optional — useClosable handles id=undefined gracefully\n // (always visible, no localStorage tracking).\n const onClose = data.closable ? close : undefined;\n\n // Render appropriate component based on type\n if (data.type === 'alert' || data.type === 'warning') {\n return (\n <LiveStateAlert\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n if (data.type === 'info') {\n return (\n <LiveStateInfo\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n defaultVariant={defaultVariant}\n isMobile={isMobile}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n log('warn', `Unknown type: ${data.type}`, { data });\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/hooks/useLiveState.ts","../src/hooks/useTrackEvent.ts","../src/utils/closable-storage.ts","../src/hooks/useClosable.ts","../src/utils/cta.ts","../src/utils/sanitize.ts","../src/components/RichText.tsx","../src/components/LiveStateAlert.tsx","../src/components/LiveStateInfo.tsx","../src/components/LiveStateRenderer.tsx"],"sourcesContent":["import useSWR from 'swr';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport type { LiveStateResponse } from '../types';\n\ninterface UseLiveStateReturn {\n /** Live state data */\n liveState: LiveStateResponse | null;\n\n /** True while the first fetch is in progress */\n isLoading: boolean;\n\n /**\n * True when the last fetch attempt resulted in an error.\n * Use this to implement fallbacks or custom error logging.\n * Note: the lib already logs a warning to the console on every error.\n */\n isError: boolean;\n\n /**\n * The raw error from the last failed fetch, or null.\n * Pair with `isError` for more specific error handling.\n */\n error: Error | null;\n\n /** Manually trigger a re-fetch */\n refresh: () => Promise<void>;\n}\n\n/**\n * useLiveState hook\n *\n * Fetches and manages live state data using SWR with automatic caching,\n * deduplication, and revalidation.\n *\n * Must be used within LiveStateProvider. See examples/ directory for usage.\n */\nexport function useLiveState(): UseLiveStateReturn {\n const { fetcher, log } = useLiveStateContext();\n\n const { data, error, isLoading, mutate } = useSWR<LiveStateResponse | null>(\n 'live-state',\n fetcher,\n {\n revalidateOnFocus: false,\n revalidateOnReconnect: true,\n dedupingInterval: 60000, // 1 minute\n shouldRetryOnError: false, // Fail silently\n onError: err => {\n log('error', 'Failed to fetch live state', { error: err });\n },\n },\n );\n\n const refresh = async () => {\n try {\n await mutate();\n } catch (err) {\n log('error', 'Failed to refresh live state', { error: err });\n }\n };\n\n return {\n liveState: data ?? null,\n isLoading,\n isError: error != null,\n error: error ?? null,\n refresh,\n };\n}\n","import { useCallback } from 'react';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { trackEvent } from '../utils/analytics';\nimport type { TrackingProperties } from '../types';\n\n/**\n * useTrackEvent\n *\n * Returns a `track` function that fires both the internal analytics\n * (Amplitude + Clarity) and the optional `onEvent` callback provided\n * to LiveStateProvider — useful for simulators and debugging tools.\n */\nexport function useTrackEvent() {\n const { onEvent, disabled, log } = useLiveStateContext();\n\n const track = useCallback(\n (eventName: string, properties?: TrackingProperties) => {\n // onEvent callback fires even when disabled — it is an observability\n // hook, not an analytics SDK, so the caller opts in deliberately.\n onEvent?.(eventName, properties);\n\n // Skip SDK calls when analytics are disabled (e.g. dev/test environments)\n if (!disabled) {\n trackEvent(eventName, properties, log);\n }\n },\n [onEvent, disabled, log],\n );\n\n return track;\n}\n","export const LIVE_STATE_STORAGE_KEY = '@tiendanube/live-state:closable';\n\n/** @internal */\nconst STORAGE_KEY = LIVE_STATE_STORAGE_KEY;\n\nexport interface ClosableState {\n count: number;\n closedAt: number;\n}\n\nexport type ClosableStates = Record<string, Record<string, ClosableState>>;\n\n/**\n * Reads closable states from localStorage.\n * Normalises legacy format (plain number) to the current shape ({ count, closedAt }).\n * Returns an empty object on any error (fail-silent).\n */\nexport function getClosableStates(): ClosableStates {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return {};\n\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return {};\n\n const normalized: ClosableStates = {};\n\n for (const [ctx, ctxState] of Object.entries(\n parsed as Record<string, unknown>,\n )) {\n if (typeof ctxState !== 'object' || ctxState === null) continue;\n\n normalized[ctx] = {};\n\n for (const [id, entry] of Object.entries(\n ctxState as Record<string, unknown>,\n )) {\n if (typeof entry === 'number') {\n // legacy format: plain close count\n normalized[ctx][id] = { count: entry, closedAt: 0 };\n } else if (typeof entry === 'object' && entry !== null) {\n const candidate = entry as Partial<ClosableState>;\n normalized[ctx][id] = {\n count: typeof candidate.count === 'number' ? candidate.count : 0,\n closedAt:\n typeof candidate.closedAt === 'number' ? candidate.closedAt : 0,\n };\n }\n }\n }\n\n return normalized;\n } catch {\n return {};\n }\n}\n\n/**\n * Persists closable states to localStorage.\n * Fails silently if localStorage is unavailable or full.\n */\nexport function setClosableStates(states: ClosableStates): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(states));\n } catch {\n // localStorage unavailable or quota exceeded — application continues normally\n }\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport {\n getClosableStates,\n setClosableStates,\n} from '../utils/closable-storage';\n\nexport interface UseClosableOptions {\n /** Notification context (e.g. 'awareness', 'charge') */\n context: string;\n /** Unique notification ID (campaignId). If absent, always visible — no close tracking. */\n id: string | undefined;\n /** Maximum number of times the user can close before it is hidden permanently */\n maxCloseTimes?: number;\n /** Milliseconds after which the close counter resets (optional) */\n expiresIn?: number;\n}\n\nexport interface UseClosableReturn {\n /** Whether the notification should be shown. null = initial check in progress. */\n isVisible: boolean | null;\n /** Call this to record a close action */\n close: () => void;\n}\n\n/**\n * Manages the close state of a notification using localStorage.\n *\n * After the user closes a notification `maxCloseTimes` times, it is hidden\n * permanently. Optionally, the counter resets after `expiresIn` milliseconds.\n *\n * Fails silently — errors in localStorage never break the application.\n *\n * @example\n * ```tsx\n * const { isVisible, close } = useClosable({\n * context: 'awareness',\n * id: 'awareness-2026-q1',\n * maxCloseTimes: 3,\n * expiresIn: 86_400_000, // 1 day\n * });\n *\n * if (!isVisible) return null;\n * return <Notification onClose={close} />;\n * ```\n */\nexport function useClosable({\n context,\n id,\n maxCloseTimes = 3,\n expiresIn,\n}: UseClosableOptions): UseClosableReturn {\n const [isVisible, setIsVisible] = useState<boolean | null>(null);\n\n useEffect(() => {\n // No ID means no close tracking — always show\n if (!id) {\n setIsVisible(true);\n return;\n }\n\n const states = getClosableStates();\n const entry = states[context]?.[id];\n\n if (!entry) {\n setIsVisible(true);\n return;\n }\n\n const { count, closedAt } = entry;\n\n // Check expiry — reset counter and show if expired\n if (expiresIn != null && closedAt > 0) {\n const elapsed = Date.now() - closedAt;\n if (elapsed > expiresIn) {\n const updated = { ...states };\n if (updated[context]) {\n delete updated[context][id];\n setClosableStates(updated);\n }\n setIsVisible(true);\n return;\n }\n }\n\n setIsVisible(count < maxCloseTimes);\n }, [context, id, maxCloseTimes, expiresIn]);\n\n const close = useCallback(() => {\n // Always hide visually\n setIsVisible(false);\n\n // Only persist to localStorage if there is an id to track\n if (!id) return;\n\n const states = getClosableStates();\n const currentCount = states[context]?.[id]?.count ?? 0;\n\n const updated: typeof states = {\n ...states,\n [context]: {\n ...(states[context] ?? {}),\n [id]: {\n count: currentCount + 1,\n closedAt: Date.now(),\n },\n },\n };\n\n setClosableStates(updated);\n }, [context, id]);\n\n return { isVisible, close };\n}\n","import type { CtaConfig } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Handle CTA click based on type\n *\n * @param cta - CTA configuration from LiveStateResponse\n * @param log - Optional logger (defaults to console.warn)\n */\nexport function handleCtaClick(cta: CtaConfig, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n switch (cta.type) {\n case 'redirect':\n window.location.href = cta.url;\n break;\n\n case 'whatsapp': {\n const whatsappUrl = cta.whatsappMessage\n ? `${cta.url}?text=${encodeURIComponent(cta.whatsappMessage)}`\n : cta.url;\n window.open(whatsappUrl, '_blank');\n break;\n }\n\n case 'external':\n window.open(cta.url, '_blank');\n break;\n\n default:\n logger('warn', `Unknown CTA type: ${(cta as CtaConfig).type}`, { cta });\n }\n } catch (error) {\n logger('error', 'Failed to handle CTA click', { cta, error });\n }\n}\n","/**\n * Minimal HTML sanitiser for notification content.\n *\n * Allows only a strict allowlist of inline formatting tags that are expected\n * in notification messages (strong, em, b, i, br, span). All other tags and\n * attributes are stripped, preventing XSS even if the backend is compromised.\n *\n * This intentionally avoids a runtime dependency on DOMPurify or similar\n * libraries — the allowlist is narrow enough to be maintained safely here.\n */\n\n/** Tags that are safe to render inside notification text */\nconst ALLOWED_TAGS = new Set(['strong', 'em', 'b', 'i', 'br', 'span']);\n\n/**\n * Tags whose entire content (open tag + inner text + close tag) must be\n * removed, not just the surrounding tags. This prevents script injection\n * even if the backend is compromised.\n */\nconst BLOCK_TAGS_RE =\n /<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[\\s\\S]*?<\\/\\1\\s*>|<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[^>]*\\/?>/gi;\n\n/**\n * Strips all HTML tags except those in ALLOWED_TAGS.\n * Attributes are removed from all allowed tags to prevent event-handler\n * injection (e.g. `<span onclick=\"...\">`).\n * Dangerous tags (script, style, iframe, …) have their entire content removed.\n *\n * Returns the sanitised HTML string, safe to pass to `dangerouslySetInnerHTML`.\n */\nexport function sanitizeHtml(input: string): string {\n // Step 1: remove dangerous tags and their inner content entirely\n const withoutDangerous = input.replace(BLOCK_TAGS_RE, '');\n\n // Step 2: remove all attributes from every remaining tag\n const withoutAttributes = withoutDangerous.replace(\n /<([a-zA-Z][a-zA-Z0-9]*)\\s[^>]*>/g,\n '<$1>',\n );\n\n // Step 3: strip any remaining tag that is not in the allowlist\n return withoutAttributes.replace(\n /<\\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*>/g,\n (match, tag: string) => (ALLOWED_TAGS.has(tag.toLowerCase()) ? match : ''),\n );\n}\n\n/**\n * Returns true when the string contains at least one HTML tag,\n * indicating it should be rendered as rich text.\n */\nexport function containsHtml(input: string): boolean {\n return /<[a-zA-Z][^>]*>/.test(input);\n}\n","import { Text } from '@nimbus-ds/components';\nimport type { ComponentProps } from 'react';\nimport { sanitizeHtml, containsHtml } from '../utils/sanitize';\n\ntype TextProps = ComponentProps<typeof Text>;\n\ninterface RichTextProps extends Omit<TextProps, 'children'> {\n content: string;\n}\n\n/**\n * RichText\n *\n * Renders a text string that may contain inline HTML formatting (e.g. <strong>,\n * <em>, <br>). When HTML is detected, the content is sanitised through a strict\n * allowlist before being injected via dangerouslySetInnerHTML. Plain strings\n * are rendered via the Nimbus Text component without any DOM injection.\n *\n * Allowed tags: strong, em, b, i, br, span. All attributes are stripped.\n */\nexport function RichText({ content, ...textProps }: RichTextProps) {\n if (containsHtml(content)) {\n const safe = sanitizeHtml(content);\n return (\n <Text {...textProps}>\n {/* eslint-disable-next-line react/no-danger */}\n <span dangerouslySetInnerHTML={{ __html: safe }} />\n </Text>\n );\n }\n\n return <Text {...textProps}>{content}</Text>;\n}\n","import { useCallback, useEffect } from 'react';\nimport { Alert, Box, Button } from '@nimbus-ds/components';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { RichText } from './RichText';\n\nexport interface LiveStateAlertProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\n/**\n * LiveStateAlert\n *\n * Renders an Alert component (Nimbus) for alert/warning types.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateAlert({\n data,\n trackingConfig,\n onClose,\n onCtaClick,\n}: LiveStateAlertProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const appearance = data.type === 'alert' ? 'danger' : 'warning';\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <Alert\n appearance={appearance}\n title={data.title}\n onRemove={onClose ? handleClose : undefined}>\n <Box\n display=\"flex\"\n alignItems=\"flex-start\"\n gap=\"3\"\n flexWrap=\"wrap\"\n flexDirection=\"column\">\n <Box flex=\"1 1 auto\" minWidth=\"220px\">\n <RichText content={data.message} />\n </Box>\n\n <Box display=\"flex\" justifyContent=\"flex-end\" marginTop=\"2\">\n <Button appearance=\"primary\" onClick={handleClick}>\n {data.cta.label}\n </Button>\n </Box>\n </Box>\n </Alert>\n );\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport { Card, Box, Text, Link, Icon } from '@nimbus-ds/components';\nimport { MoneyIcon, CloseIcon } from '@nimbus-ds/icons';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { RichText } from './RichText';\n\nexport interface LiveStateInfoProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n defaultVariant?: 'blue' | 'white';\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override mobile layout detection.\n * When provided, bypasses `window.innerWidth < 750` so simulators and\n * testing tools can force a specific layout without resizing the window.\n */\n isMobile?: boolean;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\nconst ICON_COLOR_MAP = {\n blue: { background: '#0050C3', icon: '#FFFFFF' },\n white: { background: '#FFFFFF', icon: '#0059D5' },\n} as const;\n\n/**\n * LiveStateInfo\n *\n * Renders a Card component (Nimbus) for info type.\n * Uses MoneyIcon with colored circle background, Text components,\n * and a Link-style CTA — matching the LendingBanner design reference.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateInfo({\n data,\n trackingConfig,\n defaultVariant = 'blue',\n onClose,\n isMobile: isMobileProp,\n onCtaClick,\n}: LiveStateInfoProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const variant = data.variant || defaultVariant;\n const iconConfig = ICON_COLOR_MAP[variant];\n\n const [isMobileWindow, setIsMobileWindow] = useState(\n () => window.innerWidth < 750,\n );\n\n useEffect(() => {\n const handler = () => setIsMobileWindow(window.innerWidth < 750);\n window.addEventListener('resize', handler);\n return () => window.removeEventListener('resize', handler);\n }, []);\n\n // Prop overrides window detection — allows simulators/tests to force layout\n const isMobile = isMobileProp ?? isMobileWindow;\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const ctaLink = (\n <Link\n as=\"a\"\n textDecoration=\"none\"\n onClick={handleClick}\n data-testid=\"live-state-cta-link\">\n <Text color=\"primary-interactive\" fontSize=\"base\">\n {data.cta.label}\n </Text>\n </Link>\n );\n\n return (\n <Card>\n <Box display=\"flex\" flexDirection=\"row\" justifyContent=\"space-between\">\n {/* Left: icon + text (+ cta on mobile) */}\n <Box\n display=\"flex\"\n gap=\"4\"\n alignItems={isMobile ? 'flex-start' : 'center'}\n paddingRight=\"1-5\">\n <Box minWidth=\"32px\">\n <div\n style={{\n width: 32,\n height: 32,\n borderRadius: '35%',\n borderColor: variant === 'white' ? '#E7E7E7' : 'transparent',\n borderWidth: 1,\n borderStyle: 'solid',\n background: iconConfig.background,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <MoneyIcon style={{ color: iconConfig.icon }} />\n </div>\n </Box>\n\n <Box display=\"grid\" gap=\"2\">\n <Box>\n <RichText content={data.title} fontSize=\"base\" color=\"neutral-textHigh\" />\n <RichText content={data.message} fontSize=\"base\" color=\"neutral-textLow\" />\n </Box>\n\n {/* CTA shown below text on mobile */}\n {isMobile && ctaLink}\n </Box>\n </Box>\n\n {/* Right: cta (desktop only) + close button */}\n {(!isMobile || onClose) && (\n <Box\n display=\"flex\"\n flexDirection={isMobile ? 'column' : 'row'}\n alignItems={isMobile ? 'flex-end' : 'center'}\n gap=\"2\">\n {/* CTA shown on the right side on desktop */}\n {!isMobile && ctaLink}\n\n {onClose && (\n <button\n type=\"button\"\n data-testid=\"live-state-close-button\"\n aria-label=\"Fechar notificação\"\n onClick={handleClose}\n style={{\n background: 'none',\n border: 'none',\n padding: 0,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <Icon\n source={<CloseIcon width=\"18px\" height=\"18px\" />}\n color=\"neutral-interactive\"\n />\n </button>\n )}\n </Box>\n )}\n </Box>\n </Card>\n );\n}\n","import type { LiveStateRendererProps } from '../types';\nimport { useClosable } from '../hooks/useClosable';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { LiveStateAlert } from './LiveStateAlert';\nimport { LiveStateInfo } from './LiveStateInfo';\n\n/**\n * LiveStateRenderer\n *\n * Main component that validates payload, filters by context, and renders\n * the appropriate visual component (Alert or Info) based on type.\n * Handles closable notifications via useClosable when closable is configured.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateRenderer({\n data,\n loading,\n trackingConfig,\n allowedContexts,\n defaultVariant,\n isMobile,\n onCtaClick,\n}: LiveStateRendererProps) {\n const { log } = useLiveStateContext();\n const { isVisible, close } = useClosable({\n context: data?.context ?? '',\n id: data?.campaignId,\n maxCloseTimes: data?.metadata?.maxCloseTimes,\n expiresIn: data?.metadata?.expiresIn,\n });\n\n // Don't render while loading or no data\n if (loading || !data) {\n return null;\n }\n\n // Validate required fields\n if (\n !data.context ||\n !data.type ||\n !data.title ||\n !data.message ||\n !data.cta?.label ||\n !data.cta?.url ||\n !data.cta?.type\n ) {\n log('warn', 'Invalid payload, missing required fields', { data });\n return null;\n }\n\n // Filter by allowed contexts\n if (allowedContexts && !allowedContexts.includes(data.context)) {\n return null;\n }\n\n // Awaiting localStorage check\n if (isVisible === null) {\n return null;\n }\n\n // Hidden — user reached maxCloseTimes\n if (!isVisible) {\n return null;\n }\n\n // Pass onClose whenever closable is true.\n // campaignId is optional — useClosable handles id=undefined gracefully\n // (always visible, no localStorage tracking).\n const onClose = data.closable ? close : undefined;\n\n // Render appropriate component based on type\n if (data.type === 'alert' || data.type === 'warning') {\n return (\n <LiveStateAlert\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n if (data.type === 'info') {\n return (\n <LiveStateInfo\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n defaultVariant={defaultVariant}\n isMobile={isMobile}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n log('warn', `Unknown type: ${data.type}`, { data });\n return null;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./LiveStateProvider-BH31kAuo.js";
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./LiveStateProvider-DeRIGqmT.js";
2
2
  import { useCallback as o, useEffect as s, useState as c } from "react";
3
3
  import l from "swr";
4
4
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
@@ -146,15 +146,46 @@ function O(e, t) {
146
146
  }
147
147
  }
148
148
  //#endregion
149
+ //#region src/utils/sanitize.ts
150
+ var k = new Set([
151
+ "strong",
152
+ "em",
153
+ "b",
154
+ "i",
155
+ "br",
156
+ "span"
157
+ ]), A = /<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[\s\S]*?<\/\1\s*>|<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[^>]*\/?>/gi;
158
+ function j(e) {
159
+ return e.replace(A, "").replace(/<([a-zA-Z][a-zA-Z0-9]*)\s[^>]*>/g, "<$1>").replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*>/g, (e, t) => k.has(t.toLowerCase()) ? e : "");
160
+ }
161
+ function M(e) {
162
+ return /<[a-zA-Z][^>]*>/.test(e);
163
+ }
164
+ //#endregion
165
+ //#region src/components/RichText.tsx
166
+ function N({ content: e, ...t }) {
167
+ if (M(e)) {
168
+ let n = j(e);
169
+ return /* @__PURE__ */ u(v, {
170
+ ...t,
171
+ children: /* @__PURE__ */ u("span", { dangerouslySetInnerHTML: { __html: n } })
172
+ });
173
+ }
174
+ return /* @__PURE__ */ u(v, {
175
+ ...t,
176
+ children: e
177
+ });
178
+ }
179
+ //#endregion
149
180
  //#region src/components/LiveStateAlert.tsx
150
- function k({ data: e, trackingConfig: r, onClose: a, onCtaClick: c }) {
181
+ function P({ data: e, trackingConfig: r, onClose: a, onCtaClick: c }) {
151
182
  let l = S(), { log: h } = n(), g = e.type === "alert" ? "danger" : "warning", _ = o(() => t({
152
183
  context: e.context,
153
184
  campaignId: e.campaignId,
154
185
  group: e.group,
155
186
  ...e.metadata,
156
187
  ...r.properties
157
- }), [e, r.properties]), y = o(() => {
188
+ }), [e, r.properties]), v = o(() => {
158
189
  l(i(r.prefix, r.page, e.context, "click"), _()), c ? c(e.cta) : O(e.cta, h);
159
190
  }, [
160
191
  e,
@@ -163,7 +194,7 @@ function k({ data: e, trackingConfig: r, onClose: a, onCtaClick: c }) {
163
194
  l,
164
195
  c,
165
196
  h
166
- ]), b = o(() => {
197
+ ]), y = o(() => {
167
198
  l(i(r.prefix, r.page, e.context, "close"), _()), a?.();
168
199
  }, [
169
200
  a,
@@ -177,7 +208,7 @@ function k({ data: e, trackingConfig: r, onClose: a, onCtaClick: c }) {
177
208
  }, []), /* @__PURE__ */ u(f, {
178
209
  appearance: g,
179
210
  title: e.title,
180
- onRemove: a ? b : void 0,
211
+ onRemove: a ? y : void 0,
181
212
  children: /* @__PURE__ */ d(p, {
182
213
  display: "flex",
183
214
  alignItems: "flex-start",
@@ -187,14 +218,14 @@ function k({ data: e, trackingConfig: r, onClose: a, onCtaClick: c }) {
187
218
  children: [/* @__PURE__ */ u(p, {
188
219
  flex: "1 1 auto",
189
220
  minWidth: "220px",
190
- children: /* @__PURE__ */ u(v, { children: e.message })
221
+ children: /* @__PURE__ */ u(N, { content: e.message })
191
222
  }), /* @__PURE__ */ u(p, {
192
223
  display: "flex",
193
224
  justifyContent: "flex-end",
194
225
  marginTop: "2",
195
226
  children: /* @__PURE__ */ u(m, {
196
227
  appearance: "primary",
197
- onClick: y,
228
+ onClick: v,
198
229
  children: e.cta.label
199
230
  })
200
231
  })]
@@ -203,7 +234,7 @@ function k({ data: e, trackingConfig: r, onClose: a, onCtaClick: c }) {
203
234
  }
204
235
  //#endregion
205
236
  //#region src/components/LiveStateInfo.tsx
206
- var A = {
237
+ var F = {
207
238
  blue: {
208
239
  background: "#0050C3",
209
240
  icon: "#FFFFFF"
@@ -213,43 +244,43 @@ var A = {
213
244
  icon: "#0059D5"
214
245
  }
215
246
  };
216
- function j({ data: e, trackingConfig: r, defaultVariant: a = "blue", onClose: l, isMobile: f, onCtaClick: m }) {
217
- let x = S(), { log: C } = n(), w = e.variant || a, T = A[w], [E, D] = c(() => window.innerWidth < 750);
247
+ function I({ data: e, trackingConfig: r, defaultVariant: a = "blue", onClose: l, isMobile: f, onCtaClick: m }) {
248
+ let x = S(), { log: C } = n(), w = e.variant || a, T = F[w], [E, D] = c(() => window.innerWidth < 750);
218
249
  s(() => {
219
250
  let e = () => D(window.innerWidth < 750);
220
251
  return window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
221
252
  }, []);
222
- let k = f ?? E, j = o(() => t({
253
+ let k = f ?? E, A = o(() => t({
223
254
  context: e.context,
224
255
  campaignId: e.campaignId,
225
256
  group: e.group,
226
257
  ...e.metadata,
227
258
  ...r.properties
228
- }), [e, r.properties]), M = o(() => {
229
- x(i(r.prefix, r.page, e.context, "click"), j()), m ? m(e.cta) : O(e.cta, C);
259
+ }), [e, r.properties]), j = o(() => {
260
+ x(i(r.prefix, r.page, e.context, "click"), A()), m ? m(e.cta) : O(e.cta, C);
230
261
  }, [
231
262
  e,
232
263
  r,
233
- j,
264
+ A,
234
265
  x,
235
266
  m,
236
267
  C
237
- ]), N = o(() => {
238
- x(i(r.prefix, r.page, e.context, "close"), j()), l?.();
268
+ ]), M = o(() => {
269
+ x(i(r.prefix, r.page, e.context, "close"), A()), l?.();
239
270
  }, [
240
271
  l,
241
272
  e,
242
273
  r,
243
- j,
274
+ A,
244
275
  x
245
276
  ]);
246
277
  s(() => {
247
- x(i(r.prefix, r.page, e.context, "view"), j());
278
+ x(i(r.prefix, r.page, e.context, "view"), A());
248
279
  }, []);
249
280
  let P = /* @__PURE__ */ u(_, {
250
281
  as: "a",
251
282
  textDecoration: "none",
252
- onClick: M,
283
+ onClick: j,
253
284
  "data-testid": "live-state-cta-link",
254
285
  children: /* @__PURE__ */ u(v, {
255
286
  color: "primary-interactive",
@@ -286,14 +317,14 @@ function j({ data: e, trackingConfig: r, defaultVariant: a = "blue", onClose: l,
286
317
  }), /* @__PURE__ */ d(p, {
287
318
  display: "grid",
288
319
  gap: "2",
289
- children: [/* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(v, {
320
+ children: [/* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(N, {
321
+ content: e.title,
290
322
  fontSize: "base",
291
- color: "neutral-textHigh",
292
- children: e.title
293
- }), /* @__PURE__ */ u(v, {
323
+ color: "neutral-textHigh"
324
+ }), /* @__PURE__ */ u(N, {
325
+ content: e.message,
294
326
  fontSize: "base",
295
- color: "neutral-textLow",
296
- children: e.message
327
+ color: "neutral-textLow"
297
328
  })] }), k && P]
298
329
  })]
299
330
  }), (!k || l) && /* @__PURE__ */ d(p, {
@@ -305,7 +336,7 @@ function j({ data: e, trackingConfig: r, defaultVariant: a = "blue", onClose: l,
305
336
  type: "button",
306
337
  "data-testid": "live-state-close-button",
307
338
  "aria-label": "Fechar notificação",
308
- onClick: N,
339
+ onClick: M,
309
340
  style: {
310
341
  background: "none",
311
342
  border: "none",
@@ -328,7 +359,7 @@ function j({ data: e, trackingConfig: r, defaultVariant: a = "blue", onClose: l,
328
359
  }
329
360
  //#endregion
330
361
  //#region src/components/LiveStateRenderer.tsx
331
- function M({ data: e, loading: t, trackingConfig: r, allowedContexts: i, defaultVariant: a, isMobile: o, onCtaClick: s }) {
362
+ function L({ data: e, loading: t, trackingConfig: r, allowedContexts: i, defaultVariant: a, isMobile: o, onCtaClick: s }) {
332
363
  let { log: c } = n(), { isVisible: l, close: d } = D({
333
364
  context: e?.context ?? "",
334
365
  id: e?.campaignId,
@@ -339,12 +370,12 @@ function M({ data: e, loading: t, trackingConfig: r, allowedContexts: i, default
339
370
  if (!e.context || !e.type || !e.title || !e.message || !e.cta?.label || !e.cta?.url || !e.cta?.type) return c("warn", "Invalid payload, missing required fields", { data: e }), null;
340
371
  if (i && !i.includes(e.context) || l === null || !l) return null;
341
372
  let f = e.closable ? d : void 0;
342
- return e.type === "alert" || e.type === "warning" ? /* @__PURE__ */ u(k, {
373
+ return e.type === "alert" || e.type === "warning" ? /* @__PURE__ */ u(P, {
343
374
  data: e,
344
375
  onClose: f,
345
376
  trackingConfig: r,
346
377
  onCtaClick: s
347
- }) : e.type === "info" ? /* @__PURE__ */ u(j, {
378
+ }) : e.type === "info" ? /* @__PURE__ */ u(I, {
348
379
  data: e,
349
380
  onClose: f,
350
381
  trackingConfig: r,
@@ -354,6 +385,6 @@ function M({ data: e, loading: t, trackingConfig: r, allowedContexts: i, default
354
385
  }) : (c("warn", `Unknown type: ${e.type}`, { data: e }), null);
355
386
  }
356
387
  //#endregion
357
- export { C as LIVE_STATE_STORAGE_KEY, k as LiveStateAlert, j as LiveStateInfo, a as LiveStateProvider, M as LiveStateRenderer, O as handleCtaClick, e as trackEvent, x as useLiveState, S as useTrackEvent };
388
+ export { C as LIVE_STATE_STORAGE_KEY, P as LiveStateAlert, I as LiveStateInfo, a as LiveStateProvider, L as LiveStateRenderer, O as handleCtaClick, e as trackEvent, x as useLiveState, S as useTrackEvent };
358
389
 
359
390
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/hooks/useLiveState.ts","../src/hooks/useTrackEvent.ts","../src/utils/closable-storage.ts","../src/hooks/useClosable.ts","../src/utils/cta.ts","../src/components/LiveStateAlert.tsx","../src/components/LiveStateInfo.tsx","../src/components/LiveStateRenderer.tsx"],"sourcesContent":["import useSWR from 'swr';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport type { LiveStateResponse } from '../types';\n\ninterface UseLiveStateReturn {\n /** Live state data */\n liveState: LiveStateResponse | null;\n\n /** True while the first fetch is in progress */\n isLoading: boolean;\n\n /**\n * True when the last fetch attempt resulted in an error.\n * Use this to implement fallbacks or custom error logging.\n * Note: the lib already logs a warning to the console on every error.\n */\n isError: boolean;\n\n /**\n * The raw error from the last failed fetch, or null.\n * Pair with `isError` for more specific error handling.\n */\n error: Error | null;\n\n /** Manually trigger a re-fetch */\n refresh: () => Promise<void>;\n}\n\n/**\n * useLiveState hook\n *\n * Fetches and manages live state data using SWR with automatic caching,\n * deduplication, and revalidation.\n *\n * Must be used within LiveStateProvider. See examples/ directory for usage.\n */\nexport function useLiveState(): UseLiveStateReturn {\n const { fetcher, log } = useLiveStateContext();\n\n const { data, error, isLoading, mutate } = useSWR<LiveStateResponse | null>(\n 'live-state',\n fetcher,\n {\n revalidateOnFocus: false,\n revalidateOnReconnect: true,\n dedupingInterval: 60000, // 1 minute\n shouldRetryOnError: false, // Fail silently\n onError: err => {\n log('error', 'Failed to fetch live state', { error: err });\n },\n },\n );\n\n const refresh = async () => {\n try {\n await mutate();\n } catch (err) {\n log('error', 'Failed to refresh live state', { error: err });\n }\n };\n\n return {\n liveState: data ?? null,\n isLoading,\n isError: error != null,\n error: error ?? null,\n refresh,\n };\n}\n","import { useCallback } from 'react';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { trackEvent } from '../utils/analytics';\nimport type { TrackingProperties } from '../types';\n\n/**\n * useTrackEvent\n *\n * Returns a `track` function that fires both the internal analytics\n * (Amplitude + Clarity) and the optional `onEvent` callback provided\n * to LiveStateProvider — useful for simulators and debugging tools.\n */\nexport function useTrackEvent() {\n const { onEvent, disabled, log } = useLiveStateContext();\n\n const track = useCallback(\n (eventName: string, properties?: TrackingProperties) => {\n // onEvent callback fires even when disabled — it is an observability\n // hook, not an analytics SDK, so the caller opts in deliberately.\n onEvent?.(eventName, properties);\n\n // Skip SDK calls when analytics are disabled (e.g. dev/test environments)\n if (!disabled) {\n trackEvent(eventName, properties, log);\n }\n },\n [onEvent, disabled, log],\n );\n\n return track;\n}\n","export const LIVE_STATE_STORAGE_KEY = '@tiendanube/live-state:closable';\n\n/** @internal */\nconst STORAGE_KEY = LIVE_STATE_STORAGE_KEY;\n\nexport interface ClosableState {\n count: number;\n closedAt: number;\n}\n\nexport type ClosableStates = Record<string, Record<string, ClosableState>>;\n\n/**\n * Reads closable states from localStorage.\n * Normalises legacy format (plain number) to the current shape ({ count, closedAt }).\n * Returns an empty object on any error (fail-silent).\n */\nexport function getClosableStates(): ClosableStates {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return {};\n\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return {};\n\n const normalized: ClosableStates = {};\n\n for (const [ctx, ctxState] of Object.entries(\n parsed as Record<string, unknown>,\n )) {\n if (typeof ctxState !== 'object' || ctxState === null) continue;\n\n normalized[ctx] = {};\n\n for (const [id, entry] of Object.entries(\n ctxState as Record<string, unknown>,\n )) {\n if (typeof entry === 'number') {\n // legacy format: plain close count\n normalized[ctx][id] = { count: entry, closedAt: 0 };\n } else if (typeof entry === 'object' && entry !== null) {\n const candidate = entry as Partial<ClosableState>;\n normalized[ctx][id] = {\n count: typeof candidate.count === 'number' ? candidate.count : 0,\n closedAt:\n typeof candidate.closedAt === 'number' ? candidate.closedAt : 0,\n };\n }\n }\n }\n\n return normalized;\n } catch {\n return {};\n }\n}\n\n/**\n * Persists closable states to localStorage.\n * Fails silently if localStorage is unavailable or full.\n */\nexport function setClosableStates(states: ClosableStates): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(states));\n } catch {\n // localStorage unavailable or quota exceeded — application continues normally\n }\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport {\n getClosableStates,\n setClosableStates,\n} from '../utils/closable-storage';\n\nexport interface UseClosableOptions {\n /** Notification context (e.g. 'awareness', 'charge') */\n context: string;\n /** Unique notification ID (campaignId). If absent, always visible — no close tracking. */\n id: string | undefined;\n /** Maximum number of times the user can close before it is hidden permanently */\n maxCloseTimes?: number;\n /** Milliseconds after which the close counter resets (optional) */\n expiresIn?: number;\n}\n\nexport interface UseClosableReturn {\n /** Whether the notification should be shown. null = initial check in progress. */\n isVisible: boolean | null;\n /** Call this to record a close action */\n close: () => void;\n}\n\n/**\n * Manages the close state of a notification using localStorage.\n *\n * After the user closes a notification `maxCloseTimes` times, it is hidden\n * permanently. Optionally, the counter resets after `expiresIn` milliseconds.\n *\n * Fails silently — errors in localStorage never break the application.\n *\n * @example\n * ```tsx\n * const { isVisible, close } = useClosable({\n * context: 'awareness',\n * id: 'awareness-2026-q1',\n * maxCloseTimes: 3,\n * expiresIn: 86_400_000, // 1 day\n * });\n *\n * if (!isVisible) return null;\n * return <Notification onClose={close} />;\n * ```\n */\nexport function useClosable({\n context,\n id,\n maxCloseTimes = 3,\n expiresIn,\n}: UseClosableOptions): UseClosableReturn {\n const [isVisible, setIsVisible] = useState<boolean | null>(null);\n\n useEffect(() => {\n // No ID means no close tracking — always show\n if (!id) {\n setIsVisible(true);\n return;\n }\n\n const states = getClosableStates();\n const entry = states[context]?.[id];\n\n if (!entry) {\n setIsVisible(true);\n return;\n }\n\n const { count, closedAt } = entry;\n\n // Check expiry — reset counter and show if expired\n if (expiresIn != null && closedAt > 0) {\n const elapsed = Date.now() - closedAt;\n if (elapsed > expiresIn) {\n const updated = { ...states };\n if (updated[context]) {\n delete updated[context][id];\n setClosableStates(updated);\n }\n setIsVisible(true);\n return;\n }\n }\n\n setIsVisible(count < maxCloseTimes);\n }, [context, id, maxCloseTimes, expiresIn]);\n\n const close = useCallback(() => {\n // Always hide visually\n setIsVisible(false);\n\n // Only persist to localStorage if there is an id to track\n if (!id) return;\n\n const states = getClosableStates();\n const currentCount = states[context]?.[id]?.count ?? 0;\n\n const updated: typeof states = {\n ...states,\n [context]: {\n ...(states[context] ?? {}),\n [id]: {\n count: currentCount + 1,\n closedAt: Date.now(),\n },\n },\n };\n\n setClosableStates(updated);\n }, [context, id]);\n\n return { isVisible, close };\n}\n","import type { CtaConfig } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Handle CTA click based on type\n *\n * @param cta - CTA configuration from LiveStateResponse\n * @param log - Optional logger (defaults to console.warn)\n */\nexport function handleCtaClick(cta: CtaConfig, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n switch (cta.type) {\n case 'redirect':\n window.location.href = cta.url;\n break;\n\n case 'whatsapp': {\n const whatsappUrl = cta.whatsappMessage\n ? `${cta.url}?text=${encodeURIComponent(cta.whatsappMessage)}`\n : cta.url;\n window.open(whatsappUrl, '_blank');\n break;\n }\n\n case 'external':\n window.open(cta.url, '_blank');\n break;\n\n default:\n logger('warn', `Unknown CTA type: ${(cta as CtaConfig).type}`, { cta });\n }\n } catch (error) {\n logger('error', 'Failed to handle CTA click', { cta, error });\n }\n}\n","import { useCallback, useEffect } from 'react';\nimport { Alert, Box, Button, Text } from '@nimbus-ds/components';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\n\nexport interface LiveStateAlertProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\n/**\n * LiveStateAlert\n *\n * Renders an Alert component (Nimbus) for alert/warning types.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateAlert({\n data,\n trackingConfig,\n onClose,\n onCtaClick,\n}: LiveStateAlertProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const appearance = data.type === 'alert' ? 'danger' : 'warning';\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <Alert\n appearance={appearance}\n title={data.title}\n onRemove={onClose ? handleClose : undefined}>\n <Box\n display=\"flex\"\n alignItems=\"flex-start\"\n gap=\"3\"\n flexWrap=\"wrap\"\n flexDirection=\"column\">\n <Box flex=\"1 1 auto\" minWidth=\"220px\">\n <Text>{data.message}</Text>\n </Box>\n\n <Box display=\"flex\" justifyContent=\"flex-end\" marginTop=\"2\">\n <Button appearance=\"primary\" onClick={handleClick}>\n {data.cta.label}\n </Button>\n </Box>\n </Box>\n </Alert>\n );\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport { Card, Box, Text, Link, Icon } from '@nimbus-ds/components';\nimport { MoneyIcon, CloseIcon } from '@nimbus-ds/icons';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\n\nexport interface LiveStateInfoProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n defaultVariant?: 'blue' | 'white';\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override mobile layout detection.\n * When provided, bypasses `window.innerWidth < 750` so simulators and\n * testing tools can force a specific layout without resizing the window.\n */\n isMobile?: boolean;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\nconst ICON_COLOR_MAP = {\n blue: { background: '#0050C3', icon: '#FFFFFF' },\n white: { background: '#FFFFFF', icon: '#0059D5' },\n} as const;\n\n/**\n * LiveStateInfo\n *\n * Renders a Card component (Nimbus) for info type.\n * Uses MoneyIcon with colored circle background, Text components,\n * and a Link-style CTA — matching the LendingBanner design reference.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateInfo({\n data,\n trackingConfig,\n defaultVariant = 'blue',\n onClose,\n isMobile: isMobileProp,\n onCtaClick,\n}: LiveStateInfoProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const variant = data.variant || defaultVariant;\n const iconConfig = ICON_COLOR_MAP[variant];\n\n const [isMobileWindow, setIsMobileWindow] = useState(\n () => window.innerWidth < 750,\n );\n\n useEffect(() => {\n const handler = () => setIsMobileWindow(window.innerWidth < 750);\n window.addEventListener('resize', handler);\n return () => window.removeEventListener('resize', handler);\n }, []);\n\n // Prop overrides window detection — allows simulators/tests to force layout\n const isMobile = isMobileProp ?? isMobileWindow;\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const ctaLink = (\n <Link\n as=\"a\"\n textDecoration=\"none\"\n onClick={handleClick}\n data-testid=\"live-state-cta-link\">\n <Text color=\"primary-interactive\" fontSize=\"base\">\n {data.cta.label}\n </Text>\n </Link>\n );\n\n return (\n <Card>\n <Box display=\"flex\" flexDirection=\"row\" justifyContent=\"space-between\">\n {/* Left: icon + text (+ cta on mobile) */}\n <Box\n display=\"flex\"\n gap=\"4\"\n alignItems={isMobile ? 'flex-start' : 'center'}\n paddingRight=\"1-5\">\n <Box minWidth=\"32px\">\n <div\n style={{\n width: 32,\n height: 32,\n borderRadius: '35%',\n borderColor: variant === 'white' ? '#E7E7E7' : 'transparent',\n borderWidth: 1,\n borderStyle: 'solid',\n background: iconConfig.background,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <MoneyIcon style={{ color: iconConfig.icon }} />\n </div>\n </Box>\n\n <Box display=\"grid\" gap=\"2\">\n <Box>\n <Text fontSize=\"base\" color=\"neutral-textHigh\">\n {data.title}\n </Text>\n <Text fontSize=\"base\" color=\"neutral-textLow\">\n {data.message}\n </Text>\n </Box>\n\n {/* CTA shown below text on mobile */}\n {isMobile && ctaLink}\n </Box>\n </Box>\n\n {/* Right: cta (desktop only) + close button */}\n {(!isMobile || onClose) && (\n <Box\n display=\"flex\"\n flexDirection={isMobile ? 'column' : 'row'}\n alignItems={isMobile ? 'flex-end' : 'center'}\n gap=\"2\">\n {/* CTA shown on the right side on desktop */}\n {!isMobile && ctaLink}\n\n {onClose && (\n <button\n type=\"button\"\n data-testid=\"live-state-close-button\"\n aria-label=\"Fechar notificação\"\n onClick={handleClose}\n style={{\n background: 'none',\n border: 'none',\n padding: 0,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <Icon\n source={<CloseIcon width=\"18px\" height=\"18px\" />}\n color=\"neutral-interactive\"\n />\n </button>\n )}\n </Box>\n )}\n </Box>\n </Card>\n );\n}\n","import type { LiveStateRendererProps } from '../types';\nimport { useClosable } from '../hooks/useClosable';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { LiveStateAlert } from './LiveStateAlert';\nimport { LiveStateInfo } from './LiveStateInfo';\n\n/**\n * LiveStateRenderer\n *\n * Main component that validates payload, filters by context, and renders\n * the appropriate visual component (Alert or Info) based on type.\n * Handles closable notifications via useClosable when closable is configured.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateRenderer({\n data,\n loading,\n trackingConfig,\n allowedContexts,\n defaultVariant,\n isMobile,\n onCtaClick,\n}: LiveStateRendererProps) {\n const { log } = useLiveStateContext();\n const { isVisible, close } = useClosable({\n context: data?.context ?? '',\n id: data?.campaignId,\n maxCloseTimes: data?.metadata?.maxCloseTimes,\n expiresIn: data?.metadata?.expiresIn,\n });\n\n // Don't render while loading or no data\n if (loading || !data) {\n return null;\n }\n\n // Validate required fields\n if (\n !data.context ||\n !data.type ||\n !data.title ||\n !data.message ||\n !data.cta?.label ||\n !data.cta?.url ||\n !data.cta?.type\n ) {\n log('warn', 'Invalid payload, missing required fields', { data });\n return null;\n }\n\n // Filter by allowed contexts\n if (allowedContexts && !allowedContexts.includes(data.context)) {\n return null;\n }\n\n // Awaiting localStorage check\n if (isVisible === null) {\n return null;\n }\n\n // Hidden — user reached maxCloseTimes\n if (!isVisible) {\n return null;\n }\n\n // Pass onClose whenever closable is true.\n // campaignId is optional — useClosable handles id=undefined gracefully\n // (always visible, no localStorage tracking).\n const onClose = data.closable ? close : undefined;\n\n // Render appropriate component based on type\n if (data.type === 'alert' || data.type === 'warning') {\n return (\n <LiveStateAlert\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n if (data.type === 'info') {\n return (\n <LiveStateInfo\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n defaultVariant={defaultVariant}\n isMobile={isMobile}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n log('warn', `Unknown type: ${data.type}`, { data });\n return null;\n}\n"],"mappings":";;;;;;;AAoCA,SAAgB,IAAmC;CACjD,IAAM,EAAE,YAAS,WAAQ,GAAqB,EAExC,EAAE,SAAM,UAAO,cAAW,cAAW,EACzC,cACA,GACA;EACE,mBAAmB;EACnB,uBAAuB;EACvB,kBAAkB;EAClB,oBAAoB;EACpB,UAAS,MAAO;AACd,KAAI,SAAS,8BAA8B,EAAE,OAAO,GAAK,CAAC;;EAE7D,CACF;AAUD,QAAO;EACL,WAAW,KAAQ;EACnB;EACA,SAAS,KAAS;EAClB,OAAO,KAAS;EAChB,SAbc,YAAY;AAC1B,OAAI;AACF,UAAM,GAAQ;YACP,GAAK;AACZ,MAAI,SAAS,gCAAgC,EAAE,OAAO,GAAK,CAAC;;;EAU/D;;;;ACvDH,SAAgB,IAAgB;CAC9B,IAAM,EAAE,YAAS,aAAU,WAAQ,GAAqB;AAgBxD,QAdc,GACX,GAAmB,MAAoC;AAMtD,EAHA,IAAU,GAAW,EAAW,EAG3B,KACH,EAAW,GAAW,GAAY,EAAI;IAG1C;EAAC;EAAS;EAAU;EAAI,CACzB;;;;AC3BH,IAAa,IAAyB,mCAGhC,IAAc;AAcpB,SAAgB,IAAoC;AAClD,KAAI;EACF,IAAM,IAAM,aAAa,QAAQ,EAAY;AAC7C,MAAI,CAAC,EAAK,QAAO,EAAE;EAEnB,IAAM,IAAS,KAAK,MAAM,EAAI;AAC9B,MAAI,OAAO,KAAW,aAAY,EAAiB,QAAO,EAAE;EAE5D,IAAM,IAA6B,EAAE;AAErC,OAAK,IAAM,CAAC,GAAK,MAAa,OAAO,QACnC,EACD,CACK,cAAO,KAAa,aAAY,IAEpC;KAAW,KAAO,EAAE;AAEpB,QAAK,IAAM,CAAC,GAAI,MAAU,OAAO,QAC/B,EACD,CACC,KAAI,OAAO,KAAU,SAEnB,GAAW,GAAK,KAAM;IAAE,OAAO;IAAO,UAAU;IAAG;YAC1C,OAAO,KAAU,YAAY,GAAgB;IACtD,IAAM,IAAY;AAClB,MAAW,GAAK,KAAM;KACpB,OAAO,OAAO,EAAU,SAAU,WAAW,EAAU,QAAQ;KAC/D,UACE,OAAO,EAAU,YAAa,WAAW,EAAU,WAAW;KACjE;;;AAKP,SAAO;SACD;AACN,SAAO,EAAE;;;AAQb,SAAgB,EAAkB,GAA8B;AAC9D,KAAI;AACF,eAAa,QAAQ,GAAa,KAAK,UAAU,EAAO,CAAC;SACnD;;;;ACnBV,SAAgB,EAAY,EAC1B,YACA,OACA,mBAAgB,GAChB,gBACwC;CACxC,IAAM,CAAC,GAAW,KAAgB,EAAyB,KAAK;AA4DhE,QA1DA,QAAgB;AAEd,MAAI,CAAC,GAAI;AACP,KAAa,GAAK;AAClB;;EAGF,IAAM,IAAS,GAAmB,EAC5B,IAAQ,EAAO,KAAW;AAEhC,MAAI,CAAC,GAAO;AACV,KAAa,GAAK;AAClB;;EAGF,IAAM,EAAE,UAAO,gBAAa;AAG5B,MAAI,KAAa,QAAQ,IAAW,KAClB,KAAK,KAAK,GAAG,IACf,GAAW;GACvB,IAAM,IAAU,EAAE,GAAG,GAAQ;AAK7B,GAJI,EAAQ,OACV,OAAO,EAAQ,GAAS,IACxB,EAAkB,EAAQ,GAE5B,EAAa,GAAK;AAClB;;AAIJ,IAAa,IAAQ,EAAc;IAClC;EAAC;EAAS;EAAI;EAAe;EAAU,CAAC,EA0BpC;EAAE;EAAW,OAxBN,QAAkB;AAK9B,OAHA,EAAa,GAAM,EAGf,CAAC,EAAI;GAET,IAAM,IAAS,GAAmB,EAC5B,IAAe,EAAO,KAAW,IAAK,SAAS;AAarD,KAX+B;IAC7B,GAAG;KACF,IAAU;KACT,GAAI,EAAO,MAAY,EAAE;MACxB,IAAK;MACJ,OAAO,IAAe;MACtB,UAAU,KAAK,KAAK;MACrB;KACF;IACF,CAEyB;KACzB,CAAC,GAAS,EAAG,CAAC;EAEU;;;;ACtG7B,SAAgB,EAAe,GAAgB,GAAsB;CACnE,IAAM,IAAS,EAAa,EAAI;AAChC,KAAI;AACF,UAAQ,EAAI,MAAZ;GACE,KAAK;AACH,WAAO,SAAS,OAAO,EAAI;AAC3B;GAEF,KAAK,YAAY;IACf,IAAM,IAAc,EAAI,kBACpB,GAAG,EAAI,IAAI,QAAQ,mBAAmB,EAAI,gBAAgB,KAC1D,EAAI;AACR,WAAO,KAAK,GAAa,SAAS;AAClC;;GAGF,KAAK;AACH,WAAO,KAAK,EAAI,KAAK,SAAS;AAC9B;GAEF,QACE,GAAO,QAAQ,qBAAsB,EAAkB,QAAQ,EAAE,QAAK,CAAC;;UAEpE,GAAO;AACd,IAAO,SAAS,8BAA8B;GAAE;GAAK;GAAO,CAAC;;;;;ACHjE,SAAgB,EAAe,EAC7B,SACA,mBACA,YACA,iBACsB;CACtB,IAAM,IAAQ,GAAe,EACvB,EAAE,WAAQ,GAAqB,EAC/B,IAAa,EAAK,SAAS,UAAU,WAAW,WAEhD,IAAuB,QAEzB,EAAwB;EACtB,SAAS,EAAK;EACd,YAAY,EAAK;EACjB,OAAO,EAAK;EACZ,GAAG,EAAK;EACR,GAAG,EAAe;EACnB,CAAC,EACJ,CAAC,GAAM,EAAe,WAAW,CAClC,EAEK,IAAc,QAAkB;AAQpC,EADA,EANkB,EAChB,EAAe,QACf,EAAe,MACf,EAAK,SACL,QACD,EACgB,GAAsB,CAAC,EACpC,IACF,EAAW,EAAK,IAAI,GAEpB,EAAe,EAAK,KAAK,EAAI;IAE9B;EAAC;EAAM;EAAgB;EAAsB;EAAO;EAAY;EAAI,CAAC,EAElE,IAAc,QAAkB;AAQpC,EADA,EANkB,EAChB,EAAe,QACf,EAAe,MACf,EAAK,SACL,QACD,EACgB,GAAsB,CAAC,EACxC,KAAW;IACV;EAAC;EAAS;EAAM;EAAgB;EAAsB;EAAM,CAAC;AAahE,QAXA,QAAgB;AAOd,IANkB,EAChB,EAAe,QACf,EAAe,MACf,EAAK,SACL,OACD,EACgB,GAAsB,CAAC;IAEvC,EAAE,CAAC,EAGJ,kBAAC,GAAD;EACc;EACZ,OAAO,EAAK;EACZ,UAAU,IAAU,IAAc,KAAA;YAClC,kBAAC,GAAD;GACE,SAAQ;GACR,YAAW;GACX,KAAI;GACJ,UAAS;GACT,eAAc;aALhB,CAME,kBAAC,GAAD;IAAK,MAAK;IAAW,UAAS;cAC5B,kBAAC,GAAD,EAAA,UAAO,EAAK,SAAe,CAAA;IACvB,CAAA,EAEN,kBAAC,GAAD;IAAK,SAAQ;IAAO,gBAAe;IAAW,WAAU;cACtD,kBAAC,GAAD;KAAQ,YAAW;KAAU,SAAS;eACnC,EAAK,IAAI;KACH,CAAA;IACL,CAAA,CACF;;EACA,CAAA;;;;ACjFZ,IAAM,IAAiB;CACrB,MAAM;EAAE,YAAY;EAAW,MAAM;EAAW;CAChD,OAAO;EAAE,YAAY;EAAW,MAAM;EAAW;CAClD;AAaD,SAAgB,EAAc,EAC5B,SACA,mBACA,oBAAiB,QACjB,YACA,UAAU,GACV,iBACqB;CACrB,IAAM,IAAQ,GAAe,EACvB,EAAE,WAAQ,GAAqB,EAC/B,IAAU,EAAK,WAAW,GAC1B,IAAa,EAAe,IAE5B,CAAC,GAAgB,KAAqB,QACpC,OAAO,aAAa,IAC3B;AAED,SAAgB;EACd,IAAM,UAAgB,EAAkB,OAAO,aAAa,IAAI;AAEhE,SADA,OAAO,iBAAiB,UAAU,EAAQ,QAC7B,OAAO,oBAAoB,UAAU,EAAQ;IACzD,EAAE,CAAC;CAGN,IAAM,IAAW,KAAgB,GAE3B,IAAuB,QAEzB,EAAwB;EACtB,SAAS,EAAK;EACd,YAAY,EAAK;EACjB,OAAO,EAAK;EACZ,GAAG,EAAK;EACR,GAAG,EAAe;EACnB,CAAC,EACJ,CAAC,GAAM,EAAe,WAAW,CAClC,EAEK,IAAc,QAAkB;AAQpC,EADA,EANkB,EAChB,EAAe,QACf,EAAe,MACf,EAAK,SACL,QACD,EACgB,GAAsB,CAAC,EACpC,IACF,EAAW,EAAK,IAAI,GAEpB,EAAe,EAAK,KAAK,EAAI;IAE9B;EAAC;EAAM;EAAgB;EAAsB;EAAO;EAAY;EAAI,CAAC,EAElE,IAAc,QAAkB;AAQpC,EADA,EANkB,EAChB,EAAe,QACf,EAAe,MACf,EAAK,SACL,QACD,EACgB,GAAsB,CAAC,EACxC,KAAW;IACV;EAAC;EAAS;EAAM;EAAgB;EAAsB;EAAM,CAAC;AAEhE,SAAgB;AAOd,IANkB,EAChB,EAAe,QACf,EAAe,MACf,EAAK,SACL,OACD,EACgB,GAAsB,CAAC;IAEvC,EAAE,CAAC;CAEN,IAAM,IACJ,kBAAC,GAAD;EACE,IAAG;EACH,gBAAe;EACf,SAAS;EACT,eAAY;YACZ,kBAAC,GAAD;GAAM,OAAM;GAAsB,UAAS;aACxC,EAAK,IAAI;GACL,CAAA;EACF,CAAA;AAGT,QACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAK,SAAQ;EAAO,eAAc;EAAM,gBAAe;YAAvD,CAEE,kBAAC,GAAD;GACE,SAAQ;GACR,KAAI;GACJ,YAAY,IAAW,eAAe;GACtC,cAAa;aAJf,CAKE,kBAAC,GAAD;IAAK,UAAS;cACZ,kBAAC,OAAD;KACE,OAAO;MACL,OAAO;MACP,QAAQ;MACR,cAAc;MACd,aAAa,MAAY,UAAU,YAAY;MAC/C,aAAa;MACb,aAAa;MACb,YAAY,EAAW;MACvB,SAAS;MACT,YAAY;MACZ,gBAAgB;MACjB;eACD,kBAAC,GAAD,EAAW,OAAO,EAAE,OAAO,EAAW,MAAM,EAAI,CAAA;KAC5C,CAAA;IACF,CAAA,EAEN,kBAAC,GAAD;IAAK,SAAQ;IAAO,KAAI;cAAxB,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAM,UAAS;KAAO,OAAM;eACzB,EAAK;KACD,CAAA,EACP,kBAAC,GAAD;KAAM,UAAS;KAAO,OAAM;eACzB,EAAK;KACD,CAAA,CACH,EAAA,CAAA,EAGL,KAAY,EACT;MACF;OAGJ,CAAC,KAAY,MACb,kBAAC,GAAD;GACE,SAAQ;GACR,eAAe,IAAW,WAAW;GACrC,YAAY,IAAW,aAAa;GACpC,KAAI;aAJN,CAMG,CAAC,KAAY,GAEb,KACC,kBAAC,UAAD;IACE,MAAK;IACL,eAAY;IACZ,cAAW;IACX,SAAS;IACT,OAAO;KACL,YAAY;KACZ,QAAQ;KACR,SAAS;KACT,QAAQ;KACR,SAAS;KACT,YAAY;KACZ,gBAAgB;KACjB;cACD,kBAAC,GAAD;KACE,QAAQ,kBAAC,GAAD;MAAW,OAAM;MAAO,QAAO;MAAS,CAAA;KAChD,OAAM;KACN,CAAA;IACK,CAAA,CAEP;KAEJ;KACD,CAAA;;;;ACjMX,SAAgB,EAAkB,EAChC,SACA,YACA,mBACA,oBACA,mBACA,aACA,iBACyB;CACzB,IAAM,EAAE,WAAQ,GAAqB,EAC/B,EAAE,cAAW,aAAU,EAAY;EACvC,SAAS,GAAM,WAAW;EAC1B,IAAI,GAAM;EACV,eAAe,GAAM,UAAU;EAC/B,WAAW,GAAM,UAAU;EAC5B,CAAC;AAGF,KAAI,KAAW,CAAC,EACd,QAAO;AAIT,KACE,CAAC,EAAK,WACN,CAAC,EAAK,QACN,CAAC,EAAK,SACN,CAAC,EAAK,WACN,CAAC,EAAK,KAAK,SACX,CAAC,EAAK,KAAK,OACX,CAAC,EAAK,KAAK,KAGX,QADA,EAAI,QAAQ,4CAA4C,EAAE,SAAM,CAAC,EAC1D;AAcT,KAVI,KAAmB,CAAC,EAAgB,SAAS,EAAK,QAAQ,IAK1D,MAAc,QAKd,CAAC,EACH,QAAO;CAMT,IAAM,IAAU,EAAK,WAAW,IAAQ,KAAA;AA4BxC,QAzBI,EAAK,SAAS,WAAW,EAAK,SAAS,YAEvC,kBAAC,GAAD;EACQ;EACG;EACO;EACJ;EACZ,CAAA,GAIF,EAAK,SAAS,SAEd,kBAAC,GAAD;EACQ;EACG;EACO;EACA;EACN;EACE;EACZ,CAAA,IAIN,EAAI,QAAQ,iBAAiB,EAAK,QAAQ,EAAE,SAAM,CAAC,EAC5C"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/hooks/useLiveState.ts","../src/hooks/useTrackEvent.ts","../src/utils/closable-storage.ts","../src/hooks/useClosable.ts","../src/utils/cta.ts","../src/utils/sanitize.ts","../src/components/RichText.tsx","../src/components/LiveStateAlert.tsx","../src/components/LiveStateInfo.tsx","../src/components/LiveStateRenderer.tsx"],"sourcesContent":["import useSWR from 'swr';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport type { LiveStateResponse } from '../types';\n\ninterface UseLiveStateReturn {\n /** Live state data */\n liveState: LiveStateResponse | null;\n\n /** True while the first fetch is in progress */\n isLoading: boolean;\n\n /**\n * True when the last fetch attempt resulted in an error.\n * Use this to implement fallbacks or custom error logging.\n * Note: the lib already logs a warning to the console on every error.\n */\n isError: boolean;\n\n /**\n * The raw error from the last failed fetch, or null.\n * Pair with `isError` for more specific error handling.\n */\n error: Error | null;\n\n /** Manually trigger a re-fetch */\n refresh: () => Promise<void>;\n}\n\n/**\n * useLiveState hook\n *\n * Fetches and manages live state data using SWR with automatic caching,\n * deduplication, and revalidation.\n *\n * Must be used within LiveStateProvider. See examples/ directory for usage.\n */\nexport function useLiveState(): UseLiveStateReturn {\n const { fetcher, log } = useLiveStateContext();\n\n const { data, error, isLoading, mutate } = useSWR<LiveStateResponse | null>(\n 'live-state',\n fetcher,\n {\n revalidateOnFocus: false,\n revalidateOnReconnect: true,\n dedupingInterval: 60000, // 1 minute\n shouldRetryOnError: false, // Fail silently\n onError: err => {\n log('error', 'Failed to fetch live state', { error: err });\n },\n },\n );\n\n const refresh = async () => {\n try {\n await mutate();\n } catch (err) {\n log('error', 'Failed to refresh live state', { error: err });\n }\n };\n\n return {\n liveState: data ?? null,\n isLoading,\n isError: error != null,\n error: error ?? null,\n refresh,\n };\n}\n","import { useCallback } from 'react';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { trackEvent } from '../utils/analytics';\nimport type { TrackingProperties } from '../types';\n\n/**\n * useTrackEvent\n *\n * Returns a `track` function that fires both the internal analytics\n * (Amplitude + Clarity) and the optional `onEvent` callback provided\n * to LiveStateProvider — useful for simulators and debugging tools.\n */\nexport function useTrackEvent() {\n const { onEvent, disabled, log } = useLiveStateContext();\n\n const track = useCallback(\n (eventName: string, properties?: TrackingProperties) => {\n // onEvent callback fires even when disabled — it is an observability\n // hook, not an analytics SDK, so the caller opts in deliberately.\n onEvent?.(eventName, properties);\n\n // Skip SDK calls when analytics are disabled (e.g. dev/test environments)\n if (!disabled) {\n trackEvent(eventName, properties, log);\n }\n },\n [onEvent, disabled, log],\n );\n\n return track;\n}\n","export const LIVE_STATE_STORAGE_KEY = '@tiendanube/live-state:closable';\n\n/** @internal */\nconst STORAGE_KEY = LIVE_STATE_STORAGE_KEY;\n\nexport interface ClosableState {\n count: number;\n closedAt: number;\n}\n\nexport type ClosableStates = Record<string, Record<string, ClosableState>>;\n\n/**\n * Reads closable states from localStorage.\n * Normalises legacy format (plain number) to the current shape ({ count, closedAt }).\n * Returns an empty object on any error (fail-silent).\n */\nexport function getClosableStates(): ClosableStates {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return {};\n\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return {};\n\n const normalized: ClosableStates = {};\n\n for (const [ctx, ctxState] of Object.entries(\n parsed as Record<string, unknown>,\n )) {\n if (typeof ctxState !== 'object' || ctxState === null) continue;\n\n normalized[ctx] = {};\n\n for (const [id, entry] of Object.entries(\n ctxState as Record<string, unknown>,\n )) {\n if (typeof entry === 'number') {\n // legacy format: plain close count\n normalized[ctx][id] = { count: entry, closedAt: 0 };\n } else if (typeof entry === 'object' && entry !== null) {\n const candidate = entry as Partial<ClosableState>;\n normalized[ctx][id] = {\n count: typeof candidate.count === 'number' ? candidate.count : 0,\n closedAt:\n typeof candidate.closedAt === 'number' ? candidate.closedAt : 0,\n };\n }\n }\n }\n\n return normalized;\n } catch {\n return {};\n }\n}\n\n/**\n * Persists closable states to localStorage.\n * Fails silently if localStorage is unavailable or full.\n */\nexport function setClosableStates(states: ClosableStates): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(states));\n } catch {\n // localStorage unavailable or quota exceeded — application continues normally\n }\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport {\n getClosableStates,\n setClosableStates,\n} from '../utils/closable-storage';\n\nexport interface UseClosableOptions {\n /** Notification context (e.g. 'awareness', 'charge') */\n context: string;\n /** Unique notification ID (campaignId). If absent, always visible — no close tracking. */\n id: string | undefined;\n /** Maximum number of times the user can close before it is hidden permanently */\n maxCloseTimes?: number;\n /** Milliseconds after which the close counter resets (optional) */\n expiresIn?: number;\n}\n\nexport interface UseClosableReturn {\n /** Whether the notification should be shown. null = initial check in progress. */\n isVisible: boolean | null;\n /** Call this to record a close action */\n close: () => void;\n}\n\n/**\n * Manages the close state of a notification using localStorage.\n *\n * After the user closes a notification `maxCloseTimes` times, it is hidden\n * permanently. Optionally, the counter resets after `expiresIn` milliseconds.\n *\n * Fails silently — errors in localStorage never break the application.\n *\n * @example\n * ```tsx\n * const { isVisible, close } = useClosable({\n * context: 'awareness',\n * id: 'awareness-2026-q1',\n * maxCloseTimes: 3,\n * expiresIn: 86_400_000, // 1 day\n * });\n *\n * if (!isVisible) return null;\n * return <Notification onClose={close} />;\n * ```\n */\nexport function useClosable({\n context,\n id,\n maxCloseTimes = 3,\n expiresIn,\n}: UseClosableOptions): UseClosableReturn {\n const [isVisible, setIsVisible] = useState<boolean | null>(null);\n\n useEffect(() => {\n // No ID means no close tracking — always show\n if (!id) {\n setIsVisible(true);\n return;\n }\n\n const states = getClosableStates();\n const entry = states[context]?.[id];\n\n if (!entry) {\n setIsVisible(true);\n return;\n }\n\n const { count, closedAt } = entry;\n\n // Check expiry — reset counter and show if expired\n if (expiresIn != null && closedAt > 0) {\n const elapsed = Date.now() - closedAt;\n if (elapsed > expiresIn) {\n const updated = { ...states };\n if (updated[context]) {\n delete updated[context][id];\n setClosableStates(updated);\n }\n setIsVisible(true);\n return;\n }\n }\n\n setIsVisible(count < maxCloseTimes);\n }, [context, id, maxCloseTimes, expiresIn]);\n\n const close = useCallback(() => {\n // Always hide visually\n setIsVisible(false);\n\n // Only persist to localStorage if there is an id to track\n if (!id) return;\n\n const states = getClosableStates();\n const currentCount = states[context]?.[id]?.count ?? 0;\n\n const updated: typeof states = {\n ...states,\n [context]: {\n ...(states[context] ?? {}),\n [id]: {\n count: currentCount + 1,\n closedAt: Date.now(),\n },\n },\n };\n\n setClosableStates(updated);\n }, [context, id]);\n\n return { isVisible, close };\n}\n","import type { CtaConfig } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Handle CTA click based on type\n *\n * @param cta - CTA configuration from LiveStateResponse\n * @param log - Optional logger (defaults to console.warn)\n */\nexport function handleCtaClick(cta: CtaConfig, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n switch (cta.type) {\n case 'redirect':\n window.location.href = cta.url;\n break;\n\n case 'whatsapp': {\n const whatsappUrl = cta.whatsappMessage\n ? `${cta.url}?text=${encodeURIComponent(cta.whatsappMessage)}`\n : cta.url;\n window.open(whatsappUrl, '_blank');\n break;\n }\n\n case 'external':\n window.open(cta.url, '_blank');\n break;\n\n default:\n logger('warn', `Unknown CTA type: ${(cta as CtaConfig).type}`, { cta });\n }\n } catch (error) {\n logger('error', 'Failed to handle CTA click', { cta, error });\n }\n}\n","/**\n * Minimal HTML sanitiser for notification content.\n *\n * Allows only a strict allowlist of inline formatting tags that are expected\n * in notification messages (strong, em, b, i, br, span). All other tags and\n * attributes are stripped, preventing XSS even if the backend is compromised.\n *\n * This intentionally avoids a runtime dependency on DOMPurify or similar\n * libraries — the allowlist is narrow enough to be maintained safely here.\n */\n\n/** Tags that are safe to render inside notification text */\nconst ALLOWED_TAGS = new Set(['strong', 'em', 'b', 'i', 'br', 'span']);\n\n/**\n * Tags whose entire content (open tag + inner text + close tag) must be\n * removed, not just the surrounding tags. This prevents script injection\n * even if the backend is compromised.\n */\nconst BLOCK_TAGS_RE =\n /<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[\\s\\S]*?<\\/\\1\\s*>|<(script|style|iframe|object|embed|form|input|button|textarea|select|link|meta|head|body|html|svg|math)[^>]*\\/?>/gi;\n\n/**\n * Strips all HTML tags except those in ALLOWED_TAGS.\n * Attributes are removed from all allowed tags to prevent event-handler\n * injection (e.g. `<span onclick=\"...\">`).\n * Dangerous tags (script, style, iframe, …) have their entire content removed.\n *\n * Returns the sanitised HTML string, safe to pass to `dangerouslySetInnerHTML`.\n */\nexport function sanitizeHtml(input: string): string {\n // Step 1: remove dangerous tags and their inner content entirely\n const withoutDangerous = input.replace(BLOCK_TAGS_RE, '');\n\n // Step 2: remove all attributes from every remaining tag\n const withoutAttributes = withoutDangerous.replace(\n /<([a-zA-Z][a-zA-Z0-9]*)\\s[^>]*>/g,\n '<$1>',\n );\n\n // Step 3: strip any remaining tag that is not in the allowlist\n return withoutAttributes.replace(\n /<\\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*>/g,\n (match, tag: string) => (ALLOWED_TAGS.has(tag.toLowerCase()) ? match : ''),\n );\n}\n\n/**\n * Returns true when the string contains at least one HTML tag,\n * indicating it should be rendered as rich text.\n */\nexport function containsHtml(input: string): boolean {\n return /<[a-zA-Z][^>]*>/.test(input);\n}\n","import { Text } from '@nimbus-ds/components';\nimport type { ComponentProps } from 'react';\nimport { sanitizeHtml, containsHtml } from '../utils/sanitize';\n\ntype TextProps = ComponentProps<typeof Text>;\n\ninterface RichTextProps extends Omit<TextProps, 'children'> {\n content: string;\n}\n\n/**\n * RichText\n *\n * Renders a text string that may contain inline HTML formatting (e.g. <strong>,\n * <em>, <br>). When HTML is detected, the content is sanitised through a strict\n * allowlist before being injected via dangerouslySetInnerHTML. Plain strings\n * are rendered via the Nimbus Text component without any DOM injection.\n *\n * Allowed tags: strong, em, b, i, br, span. All attributes are stripped.\n */\nexport function RichText({ content, ...textProps }: RichTextProps) {\n if (containsHtml(content)) {\n const safe = sanitizeHtml(content);\n return (\n <Text {...textProps}>\n {/* eslint-disable-next-line react/no-danger */}\n <span dangerouslySetInnerHTML={{ __html: safe }} />\n </Text>\n );\n }\n\n return <Text {...textProps}>{content}</Text>;\n}\n","import { useCallback, useEffect } from 'react';\nimport { Alert, Box, Button } from '@nimbus-ds/components';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { RichText } from './RichText';\n\nexport interface LiveStateAlertProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\n/**\n * LiveStateAlert\n *\n * Renders an Alert component (Nimbus) for alert/warning types.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateAlert({\n data,\n trackingConfig,\n onClose,\n onCtaClick,\n}: LiveStateAlertProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const appearance = data.type === 'alert' ? 'danger' : 'warning';\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <Alert\n appearance={appearance}\n title={data.title}\n onRemove={onClose ? handleClose : undefined}>\n <Box\n display=\"flex\"\n alignItems=\"flex-start\"\n gap=\"3\"\n flexWrap=\"wrap\"\n flexDirection=\"column\">\n <Box flex=\"1 1 auto\" minWidth=\"220px\">\n <RichText content={data.message} />\n </Box>\n\n <Box display=\"flex\" justifyContent=\"flex-end\" marginTop=\"2\">\n <Button appearance=\"primary\" onClick={handleClick}>\n {data.cta.label}\n </Button>\n </Box>\n </Box>\n </Alert>\n );\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport { Card, Box, Text, Link, Icon } from '@nimbus-ds/components';\nimport { MoneyIcon, CloseIcon } from '@nimbus-ds/icons';\nimport type { LiveStateResponse, TrackingConfig } from '../types';\nimport { buildTrackingProperties, buildEventName } from '../utils/analytics';\nimport { handleCtaClick } from '../utils/cta';\nimport { useTrackEvent } from '../hooks/useTrackEvent';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { RichText } from './RichText';\n\nexport interface LiveStateInfoProps {\n data: LiveStateResponse;\n trackingConfig: TrackingConfig;\n defaultVariant?: 'blue' | 'white';\n /** When provided, shows a close button and calls this on dismiss */\n onClose?: () => void;\n /**\n * Override mobile layout detection.\n * When provided, bypasses `window.innerWidth < 750` so simulators and\n * testing tools can force a specific layout without resizing the window.\n */\n isMobile?: boolean;\n /**\n * Override CTA click behaviour.\n * When provided, replaces default navigation so the caller controls what\n * happens on click (e.g. prevent navigation in a simulator, use a router).\n */\n onCtaClick?: (cta: import('../types').CtaConfig) => void;\n}\n\nconst ICON_COLOR_MAP = {\n blue: { background: '#0050C3', icon: '#FFFFFF' },\n white: { background: '#FFFFFF', icon: '#0059D5' },\n} as const;\n\n/**\n * LiveStateInfo\n *\n * Renders a Card component (Nimbus) for info type.\n * Uses MoneyIcon with colored circle background, Text components,\n * and a Link-style CTA — matching the LendingBanner design reference.\n * Automatically tracks view, click, and close events.\n * Shows a close button when onClose is provided.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateInfo({\n data,\n trackingConfig,\n defaultVariant = 'blue',\n onClose,\n isMobile: isMobileProp,\n onCtaClick,\n}: LiveStateInfoProps) {\n const track = useTrackEvent();\n const { log } = useLiveStateContext();\n const variant = data.variant || defaultVariant;\n const iconConfig = ICON_COLOR_MAP[variant];\n\n const [isMobileWindow, setIsMobileWindow] = useState(\n () => window.innerWidth < 750,\n );\n\n useEffect(() => {\n const handler = () => setIsMobileWindow(window.innerWidth < 750);\n window.addEventListener('resize', handler);\n return () => window.removeEventListener('resize', handler);\n }, []);\n\n // Prop overrides window detection — allows simulators/tests to force layout\n const isMobile = isMobileProp ?? isMobileWindow;\n\n const buildEventProperties = useCallback(\n () =>\n buildTrackingProperties({\n context: data.context,\n campaignId: data.campaignId,\n group: data.group,\n ...data.metadata,\n ...trackingConfig.properties,\n }),\n [data, trackingConfig.properties],\n );\n\n const handleClick = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'click',\n );\n track(eventName, buildEventProperties());\n if (onCtaClick) {\n onCtaClick(data.cta);\n } else {\n handleCtaClick(data.cta, log);\n }\n }, [data, trackingConfig, buildEventProperties, track, onCtaClick, log]);\n\n const handleClose = useCallback(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'close',\n );\n track(eventName, buildEventProperties());\n onClose?.();\n }, [onClose, data, trackingConfig, buildEventProperties, track]);\n\n useEffect(() => {\n const eventName = buildEventName(\n trackingConfig.prefix,\n trackingConfig.page,\n data.context,\n 'view',\n );\n track(eventName, buildEventProperties());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const ctaLink = (\n <Link\n as=\"a\"\n textDecoration=\"none\"\n onClick={handleClick}\n data-testid=\"live-state-cta-link\">\n <Text color=\"primary-interactive\" fontSize=\"base\">\n {data.cta.label}\n </Text>\n </Link>\n );\n\n return (\n <Card>\n <Box display=\"flex\" flexDirection=\"row\" justifyContent=\"space-between\">\n {/* Left: icon + text (+ cta on mobile) */}\n <Box\n display=\"flex\"\n gap=\"4\"\n alignItems={isMobile ? 'flex-start' : 'center'}\n paddingRight=\"1-5\">\n <Box minWidth=\"32px\">\n <div\n style={{\n width: 32,\n height: 32,\n borderRadius: '35%',\n borderColor: variant === 'white' ? '#E7E7E7' : 'transparent',\n borderWidth: 1,\n borderStyle: 'solid',\n background: iconConfig.background,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <MoneyIcon style={{ color: iconConfig.icon }} />\n </div>\n </Box>\n\n <Box display=\"grid\" gap=\"2\">\n <Box>\n <RichText content={data.title} fontSize=\"base\" color=\"neutral-textHigh\" />\n <RichText content={data.message} fontSize=\"base\" color=\"neutral-textLow\" />\n </Box>\n\n {/* CTA shown below text on mobile */}\n {isMobile && ctaLink}\n </Box>\n </Box>\n\n {/* Right: cta (desktop only) + close button */}\n {(!isMobile || onClose) && (\n <Box\n display=\"flex\"\n flexDirection={isMobile ? 'column' : 'row'}\n alignItems={isMobile ? 'flex-end' : 'center'}\n gap=\"2\">\n {/* CTA shown on the right side on desktop */}\n {!isMobile && ctaLink}\n\n {onClose && (\n <button\n type=\"button\"\n data-testid=\"live-state-close-button\"\n aria-label=\"Fechar notificação\"\n onClick={handleClose}\n style={{\n background: 'none',\n border: 'none',\n padding: 0,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <Icon\n source={<CloseIcon width=\"18px\" height=\"18px\" />}\n color=\"neutral-interactive\"\n />\n </button>\n )}\n </Box>\n )}\n </Box>\n </Card>\n );\n}\n","import type { LiveStateRendererProps } from '../types';\nimport { useClosable } from '../hooks/useClosable';\nimport { useLiveStateContext } from '../providers/LiveStateProvider';\nimport { LiveStateAlert } from './LiveStateAlert';\nimport { LiveStateInfo } from './LiveStateInfo';\n\n/**\n * LiveStateRenderer\n *\n * Main component that validates payload, filters by context, and renders\n * the appropriate visual component (Alert or Info) based on type.\n * Handles closable notifications via useClosable when closable is configured.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateRenderer({\n data,\n loading,\n trackingConfig,\n allowedContexts,\n defaultVariant,\n isMobile,\n onCtaClick,\n}: LiveStateRendererProps) {\n const { log } = useLiveStateContext();\n const { isVisible, close } = useClosable({\n context: data?.context ?? '',\n id: data?.campaignId,\n maxCloseTimes: data?.metadata?.maxCloseTimes,\n expiresIn: data?.metadata?.expiresIn,\n });\n\n // Don't render while loading or no data\n if (loading || !data) {\n return null;\n }\n\n // Validate required fields\n if (\n !data.context ||\n !data.type ||\n !data.title ||\n !data.message ||\n !data.cta?.label ||\n !data.cta?.url ||\n !data.cta?.type\n ) {\n log('warn', 'Invalid payload, missing required fields', { data });\n return null;\n }\n\n // Filter by allowed contexts\n if (allowedContexts && !allowedContexts.includes(data.context)) {\n return null;\n }\n\n // Awaiting localStorage check\n if (isVisible === null) {\n return null;\n }\n\n // Hidden — user reached maxCloseTimes\n if (!isVisible) {\n return null;\n }\n\n // Pass onClose whenever closable is true.\n // campaignId is optional — useClosable handles id=undefined gracefully\n // (always visible, no localStorage tracking).\n const onClose = data.closable ? close : undefined;\n\n // Render appropriate component based on type\n if (data.type === 'alert' || data.type === 'warning') {\n return (\n <LiveStateAlert\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n if (data.type === 'info') {\n return (\n <LiveStateInfo\n data={data}\n onClose={onClose}\n trackingConfig={trackingConfig}\n defaultVariant={defaultVariant}\n isMobile={isMobile}\n onCtaClick={onCtaClick}\n />\n );\n }\n\n log('warn', `Unknown type: ${data.type}`, { data });\n return null;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LiveStateAlert.d.ts","sourceRoot":"","sources":["../../../src/components/LiveStateAlert.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAMlE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,cAAc,EACd,OAAO,EACP,UAAU,GACX,EAAE,mBAAmB,2CA6ErB"}
1
+ {"version":3,"file":"LiveStateAlert.d.ts","sourceRoot":"","sources":["../../../src/components/LiveStateAlert.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAOlE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,cAAc,EACd,OAAO,EACP,UAAU,GACX,EAAE,mBAAmB,2CA6ErB"}
@@ -1 +1 @@
1
- {"version":3,"file":"LiveStateInfo.d.ts","sourceRoot":"","sources":["../../../src/components/LiveStateInfo.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAMlE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,iBAAiB,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,cAAc,EACd,cAAuB,EACvB,OAAO,EACP,QAAQ,EAAE,YAAY,EACtB,UAAU,GACX,EAAE,kBAAkB,2CA8JpB"}
1
+ {"version":3,"file":"LiveStateInfo.d.ts","sourceRoot":"","sources":["../../../src/components/LiveStateInfo.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAOlE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,iBAAiB,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,cAAc,EACd,cAAuB,EACvB,OAAO,EACP,QAAQ,EAAE,YAAY,EACtB,UAAU,GACX,EAAE,kBAAkB,2CA0JpB"}
@@ -0,0 +1,19 @@
1
+ import { Text } from '@nimbus-ds/components';
2
+ import { ComponentProps } from 'react';
3
+ type TextProps = ComponentProps<typeof Text>;
4
+ interface RichTextProps extends Omit<TextProps, 'children'> {
5
+ content: string;
6
+ }
7
+ /**
8
+ * RichText
9
+ *
10
+ * Renders a text string that may contain inline HTML formatting (e.g. <strong>,
11
+ * <em>, <br>). When HTML is detected, the content is sanitised through a strict
12
+ * allowlist before being injected via dangerouslySetInnerHTML. Plain strings
13
+ * are rendered via the Nimbus Text component without any DOM injection.
14
+ *
15
+ * Allowed tags: strong, em, b, i, br, span. All attributes are stripped.
16
+ */
17
+ export declare function RichText({ content, ...textProps }: RichTextProps): import("react/jsx-runtime").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=RichText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RichText.d.ts","sourceRoot":"","sources":["../../../src/components/RichText.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAG5C,KAAK,SAAS,GAAG,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC;AAE7C,UAAU,aAAc,SAAQ,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,EAAE,aAAa,2CAYhE"}
@@ -38,18 +38,4 @@ export declare function buildTrackingProperties(source: Record<string, TrackingV
38
38
  * Track event with both Amplitude and Clarity (fail silently)
39
39
  */
40
40
  export declare function trackEvent(eventName: string, properties?: TrackingProperties, log?: LoggerFn): void;
41
- /**
42
- * Clarity command API shape as documented in the Clarity SDK
43
- * https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api
44
- */
45
- type ClarityCommand = 'init' | 'event' | 'set' | 'identify' | 'consent' | 'upgrade';
46
- declare global {
47
- interface Window {
48
- clarity?: {
49
- (command: ClarityCommand, ...args: string[]): void;
50
- q?: string[][];
51
- };
52
- }
53
- }
54
- export {};
55
41
  //# sourceMappingURL=analytics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/utils/analytics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAQvD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,IAAI,CAcxE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC,CAuBf;AA2BD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,kBAAkB,EAC/B,GAAG,CAAC,EAAE,QAAQ,GACb,IAAI,CAON;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,kBAAkB,EAC/B,GAAG,CAAC,EAAE,QAAQ,GACb,IAAI,CAgBN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,GAChD,kBAAkB,CAMpB;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,kBAAkB,EAC/B,GAAG,CAAC,EAAE,QAAQ,GACb,IAAI,CAGN;AAED;;;GAGG;AACH,KAAK,cAAc,GACf,MAAM,GACN,OAAO,GACP,KAAK,GACL,UAAU,GACV,SAAS,GACT,SAAS,CAAC;AAEd,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,OAAO,CAAC,EAAE;YACR,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;YACnD,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;SAChB,CAAC;KACH;CACF"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/utils/analytics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAQvD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,IAAI,CAcxE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC,CAuBf;AA2BD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,kBAAkB,EAC/B,GAAG,CAAC,EAAE,QAAQ,GACb,IAAI,CAON;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,kBAAkB,EAC/B,GAAG,CAAC,EAAE,QAAQ,GACb,IAAI,CAiBN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,GAChD,kBAAkB,CAMpB;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,kBAAkB,EAC/B,GAAG,CAAC,EAAE,QAAQ,GACb,IAAI,CAGN"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Minimal HTML sanitiser for notification content.
3
+ *
4
+ * Allows only a strict allowlist of inline formatting tags that are expected
5
+ * in notification messages (strong, em, b, i, br, span). All other tags and
6
+ * attributes are stripped, preventing XSS even if the backend is compromised.
7
+ *
8
+ * This intentionally avoids a runtime dependency on DOMPurify or similar
9
+ * libraries — the allowlist is narrow enough to be maintained safely here.
10
+ */
11
+ /**
12
+ * Strips all HTML tags except those in ALLOWED_TAGS.
13
+ * Attributes are removed from all allowed tags to prevent event-handler
14
+ * injection (e.g. `<span onclick="...">`).
15
+ * Dangerous tags (script, style, iframe, …) have their entire content removed.
16
+ *
17
+ * Returns the sanitised HTML string, safe to pass to `dangerouslySetInnerHTML`.
18
+ */
19
+ export declare function sanitizeHtml(input: string): string;
20
+ /**
21
+ * Returns true when the string contains at least one HTML tag,
22
+ * indicating it should be rendered as rich text.
23
+ */
24
+ export declare function containsHtml(input: string): boolean;
25
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/utils/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAaH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAelD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD"}
package/dist/testing.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./LiveStateProvider-BAkFYuZ2.cjs`);let t=require(`react`);t=e.s(t,1);let n=require(`swr`),r=require(`react/jsx-runtime`);function i(e={}){return{context:`awareness`,type:`info`,title:`Mock notification`,message:`This is a mock notification for testing.`,cta:{label:`Learn more`,url:`/mock`,type:`redirect`},...e}}var a=t.default.createContext(null);function o({children:i,liveState:o=null,isLoading:s=!1,isError:c=!1,onEvent:l}){let u=t.default.useCallback(async()=>{if(s)return new Promise(()=>{});if(c)throw Error(`Mock fetch error`);return o},[o,s,c]),d=t.default.useMemo(()=>e.o(void 0),[]),f=t.default.useMemo(()=>({fetcher:u,onEvent:l,disabled:!0,log:d}),[u,l,d]);return(0,r.jsx)(n.SWRConfig,{value:{provider:()=>new Map,dedupingInterval:0},children:(0,r.jsx)(a.Provider,{value:f,children:i})})}function s(e={}){return{liveState:e.liveState??null,isLoading:e.isLoading??!1,isError:e.isError??!1,error:e.error??null,refresh:e.refresh??(()=>Promise.resolve())}}exports.MockLiveStateProvider=o,exports.createMockLiveState=i,exports.mockUseLiveState=s,exports.useLiveStateContext=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./LiveStateProvider-vcADappI.cjs`);let t=require(`react`);t=e.s(t,1);let n=require(`swr`),r=require(`react/jsx-runtime`);function i(e={}){return{context:`awareness`,type:`info`,title:`Mock notification`,message:`This is a mock notification for testing.`,cta:{label:`Learn more`,url:`/mock`,type:`redirect`},...e}}var a=t.default.createContext(null);function o({children:i,liveState:o=null,isLoading:s=!1,isError:c=!1,onEvent:l}){let u=t.default.useCallback(async()=>{if(s)return new Promise(()=>{});if(c)throw Error(`Mock fetch error`);return o},[o,s,c]),d=t.default.useMemo(()=>e.o(void 0),[]),f=t.default.useMemo(()=>({fetcher:u,onEvent:l,disabled:!0,log:d}),[u,l,d]);return(0,r.jsx)(n.SWRConfig,{value:{provider:()=>new Map,dedupingInterval:0},children:(0,r.jsx)(a.Provider,{value:f,children:i})})}function s(e={}){return{liveState:e.liveState??null,isLoading:e.isLoading??!1,isError:e.isError??!1,error:e.error??null,refresh:e.refresh??(()=>Promise.resolve())}}exports.MockLiveStateProvider=o,exports.createMockLiveState=i,exports.mockUseLiveState=s,exports.useLiveStateContext=e.n;
2
2
  //# sourceMappingURL=testing.cjs.map
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as e, o as t } from "./LiveStateProvider-BH31kAuo.js";
1
+ import { n as e, o as t } from "./LiveStateProvider-DeRIGqmT.js";
2
2
  import n from "react";
3
3
  import { SWRConfig as r } from "swr";
4
4
  import { jsx as i } from "react/jsx-runtime";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiendanube/live-state",
3
- "version": "1.0.0-beta.13",
3
+ "version": "1.0.0-beta.16",
4
4
  "description": "Generic React library for rendering live state notifications",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -17,6 +17,11 @@
17
17
  "types": "./dist/testing.d.ts"
18
18
  }
19
19
  },
20
+ "typesVersions": {
21
+ "*": {
22
+ "testing": ["./dist/testing.d.ts"]
23
+ }
24
+ },
20
25
  "files": [
21
26
  "dist",
22
27
  "README.md",
@@ -1,2 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`);c=s(c,1);let l=require(`swr`),u=require(`@amplitude/analytics-browser`);u=s(u,1);let d=require(`react/jsx-runtime`);var f=(e,t,n)=>{let r=`[LiveState]`;e===`error`?console.error(r,t,...n?[n]:[]):console.warn(r,t,...n?[n]:[])};function p(e){return(t,n,r)=>{e?e(t,n,r):f(t,n,r)}}var m=u.createInstance();function h(e,t){let n=p(t);try{if(!e){n(`warn`,`Amplitude API key not provided`);return}m.init(e,{defaultTracking:!1})}catch(e){n(`error`,`Failed to initialize Amplitude`,{error:e})}}async function g(e,t){let n=p(t);try{if(window.clarity){n(`info`,`Clarity already initialized by client`);return}let t=e||_();if(!t){n(`warn`,`Clarity project ID not provided`);return}(await import(`@microsoft/clarity`)).default.init(t)}catch(e){n(`error`,`Failed to initialize Clarity`,{error:e})}}function _(){try{let e=document.querySelector(`script[src*="clarity.ms"]`);if(e)return e.getAttribute(`src`)?.match(/\/([a-z0-9]+)$/)?.[1];let t=window.clarity?.q?.[0]?.[1];if(typeof t==`string`)return t}catch{}}function v(e,t,n){let r=p(n);try{m.track(e,t)}catch(t){r(`error`,`Failed to track Amplitude event`,{eventName:e,error:t})}}function y(e,t,n){let r=p(n);try{window.clarity&&(window.clarity(`event`,e),t&&Object.entries(t).forEach(([e,t])=>{window.clarity?.(`set`,e,String(t))}))}catch(t){r(`error`,`Failed to track Clarity event`,{eventName:e,error:t})}}function b(e,t,n,r){return`${e}${[t,n,r].filter(Boolean).join(`_`)}`}function x(e){return Object.fromEntries(Object.entries(e).filter(e=>e[1]!==void 0))}function S(e,t,n){v(e,t,n),y(e,t,n)}var C=(0,c.createContext)(null);function w({children:e,fetcher:t,analytics:n,onEvent:r,onLog:i,disabled:a=!1}){let o=(0,c.useMemo)(()=>p(i),[i]);(0,c.useEffect)(()=>{a||(n?.amplitudeKey&&h(n.amplitudeKey,o),n?.clarityProjectId&&g(n.clarityProjectId,o))},[n,a,o]);let s=(0,c.useMemo)(()=>({fetcher:t,analytics:n,onEvent:r,disabled:a,log:o}),[t,n,r,a,o]);return(0,d.jsx)(l.SWRConfig,{value:{provider:()=>new Map},children:(0,d.jsx)(C.Provider,{value:s,children:e})})}function T(){let e=(0,c.useContext)(C);if(!e)throw Error(`useLiveState must be used within LiveStateProvider`);return e}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return w}});
2
- //# sourceMappingURL=LiveStateProvider-BAkFYuZ2.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LiveStateProvider-BAkFYuZ2.cjs","names":[],"sources":["../src/utils/logger.ts","../src/utils/analytics.ts","../src/providers/LiveStateProvider.tsx"],"sourcesContent":["import type { LogLevel } from '../types';\n\n/**\n * Logger function type — matches the signature of LiveStateProviderProps.onLog.\n */\nexport type LoggerFn = (\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n) => void;\n\n/**\n * Default logger — falls back to console.* when no onLog is provided.\n * Uses console.warn for 'info' and 'warn', console.error for 'error'.\n */\nconst defaultLogger: LoggerFn = (level, message, context) => {\n const prefix = '[LiveState]';\n if (level === 'error') {\n console.error(prefix, message, ...(context ? [context] : []));\n } else {\n console.warn(prefix, message, ...(context ? [context] : []));\n }\n};\n\n/**\n * Creates a logger bound to an optional consumer-provided onLog callback.\n * When onLog is provided it is called exclusively — the default console.*\n * fallback does NOT run, giving the consumer full control over output.\n *\n * @example\n * const log = createLogger(onLog);\n * log('warn', 'Amplitude API key not provided');\n * log('error', 'Failed to fetch', { url: '/v1/live-state', status: 500 });\n */\nexport function createLogger(onLog?: LoggerFn): LoggerFn {\n return (level, message, context) => {\n if (onLog) {\n onLog(level, message, context);\n } else {\n defaultLogger(level, message, context);\n }\n };\n}\n","import * as amplitude from '@amplitude/analytics-browser';\nimport type { TrackingProperties, TrackingValue } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Isolated Amplitude instance for the lib — avoids conflicts with the\n * consumer app's own Amplitude instance when both coexist on the same page.\n */\nconst amplitudeInstance = amplitude.createInstance();\n\n/**\n * Initialize Amplitude (fail silently)\n */\nexport function initializeAmplitude(apiKey: string, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n if (!apiKey) {\n logger('warn', 'Amplitude API key not provided');\n return;\n }\n\n amplitudeInstance.init(apiKey, {\n defaultTracking: false,\n });\n } catch (error) {\n logger('error', 'Failed to initialize Amplitude', { error });\n }\n}\n\n/**\n * Initialize Clarity (fail silently)\n */\nexport async function initializeClarity(\n projectId?: string,\n log?: LoggerFn,\n): Promise<void> {\n const logger = createLogger(log);\n try {\n // Check if Clarity is already loaded by the client\n if (window.clarity) {\n logger('info', 'Clarity already initialized by client');\n return;\n }\n\n // If client provided project ID, use it\n const finalProjectId = projectId || getClientClarityProjectId();\n\n if (!finalProjectId) {\n logger('warn', 'Clarity project ID not provided');\n return;\n }\n\n // Initialize Clarity with project ID\n const Clarity = (await import('@microsoft/clarity')).default;\n Clarity.init(finalProjectId);\n } catch (error) {\n logger('error', 'Failed to initialize Clarity', { error });\n }\n}\n\n/**\n * Try to extract Clarity project ID from client's installation\n */\nfunction getClientClarityProjectId(): string | undefined {\n try {\n // Check if Clarity script tag exists\n const clarityScript = document.querySelector('script[src*=\"clarity.ms\"]');\n if (clarityScript) {\n const src = clarityScript.getAttribute('src');\n const match = src?.match(/\\/([a-z0-9]+)$/);\n return match?.[1];\n }\n\n // Check global clarity config: shape is clarity.q = [['init', projectId, ...]]\n const projectId = window.clarity?.q?.[0]?.[1];\n if (typeof projectId === 'string') {\n return projectId;\n }\n } catch {\n // Fail silently\n }\n\n return undefined;\n}\n\n/**\n * Track event with Amplitude (fail silently)\n */\nexport function trackAmplitudeEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n amplitudeInstance.track(eventName, properties);\n } catch (error) {\n logger('error', 'Failed to track Amplitude event', { eventName, error });\n }\n}\n\n/**\n * Track event with Clarity (fail silently)\n */\nexport function trackClarityEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n if (window.clarity) {\n window.clarity('event', eventName);\n\n // Set custom tags for properties\n if (properties) {\n Object.entries(properties).forEach(([key, value]) => {\n window.clarity?.('set', key, String(value));\n });\n }\n }\n } catch (error) {\n logger('error', 'Failed to track Clarity event', { eventName, error });\n }\n}\n\n/**\n * Builds a tracking event name from prefix, page and event segments.\n * The prefix is expected to already include a trailing separator (e.g. 'nuvempago_lending_').\n * Page is optional — when absent the segment is omitted from the name.\n *\n * @example\n * buildEventName('nuvempago_lending_', 'dashboard', 'charge', 'view')\n * // → 'nuvempago_lending_dashboard_charge_view'\n *\n * buildEventName('nuvempago_lending_', undefined, 'charge', 'view')\n * // → 'nuvempago_lending_charge_view'\n */\nexport function buildEventName(\n prefix: string,\n page: string | undefined,\n context: string,\n event: string,\n): string {\n // Prefix already contains its trailing separator — join only the remaining segments\n const segments = [page, context, event].filter(Boolean);\n return `${prefix}${segments.join('_')}`;\n}\n\n/**\n * Builds a TrackingProperties object from a loose record, dropping entries\n * whose value is undefined so callers don't need to guard optional fields.\n */\nexport function buildTrackingProperties(\n source: Record<string, TrackingValue | undefined>,\n): TrackingProperties {\n return Object.fromEntries(\n Object.entries(source).filter(\n (entry): entry is [string, TrackingValue] => entry[1] !== undefined,\n ),\n );\n}\n\n/**\n * Track event with both Amplitude and Clarity (fail silently)\n */\nexport function trackEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n trackAmplitudeEvent(eventName, properties, log);\n trackClarityEvent(eventName, properties, log);\n}\n\n/**\n * Clarity command API shape as documented in the Clarity SDK\n * https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api\n */\ntype ClarityCommand =\n | 'init'\n | 'event'\n | 'set'\n | 'identify'\n | 'consent'\n | 'upgrade';\n\ndeclare global {\n interface Window {\n clarity?: {\n (command: ClarityCommand, ...args: string[]): void;\n q?: string[][];\n };\n }\n}\n","import React, { createContext, useContext, useEffect, useMemo } from 'react';\nimport { SWRConfig } from 'swr';\nimport type {\n LiveStateFetcher,\n AnalyticsConfig,\n TrackingProperties,\n} from '../types';\nimport { initializeAmplitude, initializeClarity } from '../utils/analytics';\nimport { createLogger, type LoggerFn } from '../utils/logger';\n\ninterface LiveStateContextValue {\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n disabled?: boolean;\n log: LoggerFn;\n}\n\nconst LiveStateContext = createContext<LiveStateContextValue | null>(null);\n\nexport interface LiveStateProviderProps {\n children: React.ReactNode;\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n onLog?: LoggerFn;\n /**\n * When true, disables all analytics SDKs (Amplitude and Clarity).\n * Useful in development and test environments to suppress SDK warnings\n * and avoid polluting analytics data without needing to mock anything.\n * The `onEvent` and `onLog` callbacks still fire when disabled.\n *\n * @example\n * <LiveStateProvider disabled={process.env.NODE_ENV !== 'production'} />\n */\n disabled?: boolean;\n}\n\n/**\n * LiveStateProvider\n *\n * Root-level provider that configures fetcher function and analytics.\n * Initializes Amplitude and Clarity on mount.\n *\n * The optional `onEvent` callback is fired on every tracking event before\n * sending to Amplitude/Clarity — useful for simulators and debugging tools.\n *\n * The optional `onLog` callback receives all internal lib logs — route them\n * to your APM (Datadog, Sentry, etc.) to avoid relying on console.*.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateProvider({\n children,\n fetcher,\n analytics,\n onEvent,\n onLog,\n disabled = false,\n}: LiveStateProviderProps) {\n const log = useMemo(() => createLogger(onLog), [onLog]);\n\n useEffect(() => {\n if (disabled) return;\n\n if (analytics?.amplitudeKey) {\n initializeAmplitude(analytics.amplitudeKey, log);\n }\n\n if (analytics?.clarityProjectId) {\n initializeClarity(analytics.clarityProjectId, log);\n }\n }, [analytics, disabled, log]);\n\n const contextValue = useMemo(\n () => ({ fetcher, analytics, onEvent, disabled, log }),\n [fetcher, analytics, onEvent, disabled, log],\n );\n\n return (\n <SWRConfig value={{ provider: () => new Map() }}>\n <LiveStateContext.Provider value={contextValue}>\n {children}\n </LiveStateContext.Provider>\n </SWRConfig>\n );\n}\n\n/**\n * useLiveStateContext\n *\n * Internal hook to access LiveStateContext\n */\nexport function useLiveStateContext() {\n const context = useContext(LiveStateContext);\n\n if (!context) {\n throw new Error('useLiveState must be used within LiveStateProvider');\n }\n\n return context;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"LiveStateProvider-BH31kAuo.js","names":[],"sources":["../src/utils/logger.ts","../src/utils/analytics.ts","../src/providers/LiveStateProvider.tsx"],"sourcesContent":["import type { LogLevel } from '../types';\n\n/**\n * Logger function type — matches the signature of LiveStateProviderProps.onLog.\n */\nexport type LoggerFn = (\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n) => void;\n\n/**\n * Default logger — falls back to console.* when no onLog is provided.\n * Uses console.warn for 'info' and 'warn', console.error for 'error'.\n */\nconst defaultLogger: LoggerFn = (level, message, context) => {\n const prefix = '[LiveState]';\n if (level === 'error') {\n console.error(prefix, message, ...(context ? [context] : []));\n } else {\n console.warn(prefix, message, ...(context ? [context] : []));\n }\n};\n\n/**\n * Creates a logger bound to an optional consumer-provided onLog callback.\n * When onLog is provided it is called exclusively — the default console.*\n * fallback does NOT run, giving the consumer full control over output.\n *\n * @example\n * const log = createLogger(onLog);\n * log('warn', 'Amplitude API key not provided');\n * log('error', 'Failed to fetch', { url: '/v1/live-state', status: 500 });\n */\nexport function createLogger(onLog?: LoggerFn): LoggerFn {\n return (level, message, context) => {\n if (onLog) {\n onLog(level, message, context);\n } else {\n defaultLogger(level, message, context);\n }\n };\n}\n","import * as amplitude from '@amplitude/analytics-browser';\nimport type { TrackingProperties, TrackingValue } from '../types';\nimport { createLogger, type LoggerFn } from './logger';\n\n/**\n * Isolated Amplitude instance for the lib — avoids conflicts with the\n * consumer app's own Amplitude instance when both coexist on the same page.\n */\nconst amplitudeInstance = amplitude.createInstance();\n\n/**\n * Initialize Amplitude (fail silently)\n */\nexport function initializeAmplitude(apiKey: string, log?: LoggerFn): void {\n const logger = createLogger(log);\n try {\n if (!apiKey) {\n logger('warn', 'Amplitude API key not provided');\n return;\n }\n\n amplitudeInstance.init(apiKey, {\n defaultTracking: false,\n });\n } catch (error) {\n logger('error', 'Failed to initialize Amplitude', { error });\n }\n}\n\n/**\n * Initialize Clarity (fail silently)\n */\nexport async function initializeClarity(\n projectId?: string,\n log?: LoggerFn,\n): Promise<void> {\n const logger = createLogger(log);\n try {\n // Check if Clarity is already loaded by the client\n if (window.clarity) {\n logger('info', 'Clarity already initialized by client');\n return;\n }\n\n // If client provided project ID, use it\n const finalProjectId = projectId || getClientClarityProjectId();\n\n if (!finalProjectId) {\n logger('warn', 'Clarity project ID not provided');\n return;\n }\n\n // Initialize Clarity with project ID\n const Clarity = (await import('@microsoft/clarity')).default;\n Clarity.init(finalProjectId);\n } catch (error) {\n logger('error', 'Failed to initialize Clarity', { error });\n }\n}\n\n/**\n * Try to extract Clarity project ID from client's installation\n */\nfunction getClientClarityProjectId(): string | undefined {\n try {\n // Check if Clarity script tag exists\n const clarityScript = document.querySelector('script[src*=\"clarity.ms\"]');\n if (clarityScript) {\n const src = clarityScript.getAttribute('src');\n const match = src?.match(/\\/([a-z0-9]+)$/);\n return match?.[1];\n }\n\n // Check global clarity config: shape is clarity.q = [['init', projectId, ...]]\n const projectId = window.clarity?.q?.[0]?.[1];\n if (typeof projectId === 'string') {\n return projectId;\n }\n } catch {\n // Fail silently\n }\n\n return undefined;\n}\n\n/**\n * Track event with Amplitude (fail silently)\n */\nexport function trackAmplitudeEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n amplitudeInstance.track(eventName, properties);\n } catch (error) {\n logger('error', 'Failed to track Amplitude event', { eventName, error });\n }\n}\n\n/**\n * Track event with Clarity (fail silently)\n */\nexport function trackClarityEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n const logger = createLogger(log);\n try {\n if (window.clarity) {\n window.clarity('event', eventName);\n\n // Set custom tags for properties\n if (properties) {\n Object.entries(properties).forEach(([key, value]) => {\n window.clarity?.('set', key, String(value));\n });\n }\n }\n } catch (error) {\n logger('error', 'Failed to track Clarity event', { eventName, error });\n }\n}\n\n/**\n * Builds a tracking event name from prefix, page and event segments.\n * The prefix is expected to already include a trailing separator (e.g. 'nuvempago_lending_').\n * Page is optional — when absent the segment is omitted from the name.\n *\n * @example\n * buildEventName('nuvempago_lending_', 'dashboard', 'charge', 'view')\n * // → 'nuvempago_lending_dashboard_charge_view'\n *\n * buildEventName('nuvempago_lending_', undefined, 'charge', 'view')\n * // → 'nuvempago_lending_charge_view'\n */\nexport function buildEventName(\n prefix: string,\n page: string | undefined,\n context: string,\n event: string,\n): string {\n // Prefix already contains its trailing separator — join only the remaining segments\n const segments = [page, context, event].filter(Boolean);\n return `${prefix}${segments.join('_')}`;\n}\n\n/**\n * Builds a TrackingProperties object from a loose record, dropping entries\n * whose value is undefined so callers don't need to guard optional fields.\n */\nexport function buildTrackingProperties(\n source: Record<string, TrackingValue | undefined>,\n): TrackingProperties {\n return Object.fromEntries(\n Object.entries(source).filter(\n (entry): entry is [string, TrackingValue] => entry[1] !== undefined,\n ),\n );\n}\n\n/**\n * Track event with both Amplitude and Clarity (fail silently)\n */\nexport function trackEvent(\n eventName: string,\n properties?: TrackingProperties,\n log?: LoggerFn,\n): void {\n trackAmplitudeEvent(eventName, properties, log);\n trackClarityEvent(eventName, properties, log);\n}\n\n/**\n * Clarity command API shape as documented in the Clarity SDK\n * https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api\n */\ntype ClarityCommand =\n | 'init'\n | 'event'\n | 'set'\n | 'identify'\n | 'consent'\n | 'upgrade';\n\ndeclare global {\n interface Window {\n clarity?: {\n (command: ClarityCommand, ...args: string[]): void;\n q?: string[][];\n };\n }\n}\n","import React, { createContext, useContext, useEffect, useMemo } from 'react';\nimport { SWRConfig } from 'swr';\nimport type {\n LiveStateFetcher,\n AnalyticsConfig,\n TrackingProperties,\n} from '../types';\nimport { initializeAmplitude, initializeClarity } from '../utils/analytics';\nimport { createLogger, type LoggerFn } from '../utils/logger';\n\ninterface LiveStateContextValue {\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n disabled?: boolean;\n log: LoggerFn;\n}\n\nconst LiveStateContext = createContext<LiveStateContextValue | null>(null);\n\nexport interface LiveStateProviderProps {\n children: React.ReactNode;\n fetcher: LiveStateFetcher;\n analytics?: AnalyticsConfig;\n onEvent?: (eventName: string, properties?: TrackingProperties) => void;\n onLog?: LoggerFn;\n /**\n * When true, disables all analytics SDKs (Amplitude and Clarity).\n * Useful in development and test environments to suppress SDK warnings\n * and avoid polluting analytics data without needing to mock anything.\n * The `onEvent` and `onLog` callbacks still fire when disabled.\n *\n * @example\n * <LiveStateProvider disabled={process.env.NODE_ENV !== 'production'} />\n */\n disabled?: boolean;\n}\n\n/**\n * LiveStateProvider\n *\n * Root-level provider that configures fetcher function and analytics.\n * Initializes Amplitude and Clarity on mount.\n *\n * The optional `onEvent` callback is fired on every tracking event before\n * sending to Amplitude/Clarity — useful for simulators and debugging tools.\n *\n * The optional `onLog` callback receives all internal lib logs — route them\n * to your APM (Datadog, Sentry, etc.) to avoid relying on console.*.\n *\n * See examples/ directory for usage examples.\n */\nexport function LiveStateProvider({\n children,\n fetcher,\n analytics,\n onEvent,\n onLog,\n disabled = false,\n}: LiveStateProviderProps) {\n const log = useMemo(() => createLogger(onLog), [onLog]);\n\n useEffect(() => {\n if (disabled) return;\n\n if (analytics?.amplitudeKey) {\n initializeAmplitude(analytics.amplitudeKey, log);\n }\n\n if (analytics?.clarityProjectId) {\n initializeClarity(analytics.clarityProjectId, log);\n }\n }, [analytics, disabled, log]);\n\n const contextValue = useMemo(\n () => ({ fetcher, analytics, onEvent, disabled, log }),\n [fetcher, analytics, onEvent, disabled, log],\n );\n\n return (\n <SWRConfig value={{ provider: () => new Map() }}>\n <LiveStateContext.Provider value={contextValue}>\n {children}\n </LiveStateContext.Provider>\n </SWRConfig>\n );\n}\n\n/**\n * useLiveStateContext\n *\n * Internal hook to access LiveStateContext\n */\nexport function useLiveStateContext() {\n const context = useContext(LiveStateContext);\n\n if (!context) {\n throw new Error('useLiveState must be used within LiveStateProvider');\n }\n\n return context;\n}\n"],"mappings":"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"}