@tiendanube/live-state 1.0.0-beta.14 → 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/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
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})}}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
@@ -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"}
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiendanube/live-state",
3
- "version": "1.0.0-beta.14",
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",