@kelviq/react-sdk 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(c,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],g):(c=typeof globalThis<"u"?globalThis:c||self,g(c.KelviqReactSDK={},c.jsxRuntime,c.React))})(this,function(c,g,d){"use strict";const Y="GET";function $(n){const{method:s=Y,headers:o={},body:t,timeout:p=5e3,maxRetries:L=3,backoffBaseDelay:q=1e3,queryParams:S,accessToken:a}=n;let{url:m}=n,w=0;return new Promise((T,U)=>{if(S){const l=new URLSearchParams;for(const h in S)S[h]!==void 0&&l.append(h,String(S[h]));l.toString()&&(m+=(m.includes("?")?"&":"?")+l.toString())}function M(){const l=new XMLHttpRequest;l.open(s,m,!0),l.timeout=p;const h={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...o};s!=="GET"&&t&&typeof t=="object"&&!(t instanceof FormData)&&(h["Content-Type"]||(h["Content-Type"]="application/json")),a&&(h.Authorization=`Bearer ${a}`);for(const[i,E]of Object.entries(h))l.setRequestHeader(i,E);l.onload=function(){const{status:i,statusText:E,responseText:D}=l;if(i>=200&&i<300)try{const K=D?JSON.parse(D):{};T({status:i,statusText:E,data:K})}catch(K){const b=K instanceof Error?K:new Error(String(K));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${m}. Error: ${b.message}. Response: ${D}`),U(new Error(`Invalid JSON response: ${b.message}`))}else v(`Request to ${m} failed with status ${i} ${E}. Response: ${D}`)},l.onerror=()=>v(`Network error for URL ${m}. The request could not be completed.`),l.ontimeout=()=>v(`Request to ${m} timed out.`);function v(i){if(w<L){w++;const E=Math.min(q*Math.pow(2,w-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${m}. Attempt ${w}/${L}. Error: ${i}. Retrying in ${E}ms.`),setTimeout(M,E)}else U(new Error(`${i} (Max retries ${L} reached)`))}try{let i=t;t&&typeof t=="object"&&!(t instanceof FormData)&&h["Content-Type"]==="application/json"&&(i=JSON.stringify(t)),l.send(i)}catch(i){const E=i instanceof Error?i:new Error(String(i));console.error(`Kelviq SDK (apiRequest): Error sending request to ${m}.`,E),U(new Error(`Failed to send request: ${E.message}`))}}M()})}function Z(n,s){const o=s[0].featureType;if(o==="METER"){let t=0,p=0,L=!1;for(const S of s){const a=S,m=typeof a.usageLimit=="number"?a.usageLimit:null,w=typeof a.currentUsage=="number"?a.currentUsage:0;m===null&&(t=null),t!==null&&(t+=m),p+=w,a.hardLimit&&(L=!0)}const q=t!==null?t-p:null;return{featureId:n,featureType:o,hasAccess:q===null||q>0,currentUsage:p,usageLimit:t,remaining:q,hardLimit:L,items:s}}if(o==="CUSTOMIZABLE"){const t=s[0];return{featureId:n,featureType:o,hasAccess:s.some(p=>p.hasAccess),currentUsage:typeof t.currentUsage=="number"?t.currentUsage:0,usageLimit:typeof t.usageLimit=="number"?t.usageLimit:null,remaining:typeof t.remaining=="number"?t.remaining:null,hardLimit:t.hardLimit===!0,items:s}}return{featureId:n,featureType:o,hasAccess:s.some(t=>t.hasAccess),currentUsage:0,usageLimit:null,remaining:null,hardLimit:!1,items:s}}function Q(n){const s={};if(!n||!Array.isArray(n.entitlements))return console.warn("Kelviq SDK: Invalid or empty entitlements array in API response.",n),s;const o={};for(const t of n.entitlements){if(!t||typeof t.featureId!="string"||typeof t.featureType!="string"){console.warn("Kelviq SDK: Skipping invalid raw entitlement object:",t);continue}o[t.featureId]||(o[t.featureId]=[]),o[t.featureId].push(t)}for(const t in o)s[t]=Z(t,o[t]);return s}const R={allEntitlements:{data:null,isLoading:!0,error:null},refreshAllEntitlements:()=>(console.warn("refreshAllEntitlements called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),getEntitlements:()=>({}),getEntitlement:()=>null,getRawEntitlements:()=>null,getRawEntitlement:()=>null,hasAccess:()=>!1,updateEntitlement:()=>{console.warn("updateEntitlement called on default KelviqContext. Ensure KelviqProvider is properly set up.")},subscriptions:{data:null,isLoading:!1,error:null},refreshSubscriptions:()=>(console.warn("refreshSubscriptions called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),customer:{data:null,isLoading:!1,error:null},refreshCustomer:()=>(console.warn("refreshCustomer called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),isLoading:!0,error:null,apiUrl:"",environment:"production",entitlementsPath:"",customerId:""},_=d.createContext(R),ee="https://edge.api.kelviq.com/api/v1/",te="https://edge.sandboxapi.kelviq.com/api/v1/",re="https://api.kelviq.com/api/v1/",ne="https://sandboxapi.kelviq.com/api/v1/",se="entitlements/",oe=({children:n,apiUrl:s,entitlementsPath:o=se,customerId:t,environment:p="production",accessToken:L,config:q={}})=>{const{onError:S,maxRetries:a,timeout:m,backoffBaseDelay:w,fetchEntitlementsOnMount:T=!0,fetchSubscriptionsOnMount:U=!1,fetchCustomerOnMount:M=!1}=q,l=s||(p==="sandbox"?te:ee),h=p==="sandbox"?ne:re,[v,i]=d.useState({data:null,isLoading:!!l&&T,error:null}),[E,D]=d.useState(null),[K,b]=d.useState({data:null,isLoading:!!l&&U,error:null}),[N,I]=d.useState({data:null,isLoading:!!l&&M,error:null}),[k,P]=d.useState(!!l&&T),[W,B]=d.useState(null),A=d.useCallback((u,e,r)=>{const f=new Error(`Kelviq SDK (${e}): ${u}`);console.error(f.message,r||""),B(y=>y||f),S&&S(f)},[S]),O=d.useCallback(()=>{if(!l){const r="API URL not configured. Cannot fetch entitlements.";return i({data:null,isLoading:!1,error:new Error(r)}),A(r,"fetchAllEntitlements"),P(!1),Promise.reject(new Error(r))}if(!t){const r="CustomerId must be provided as props to KelviqProvider.";return i(()=>({data:null,isLoading:!1,error:new Error(r)})),A(r,"fetchAllEntitlements"),P(!1),Promise.reject(new Error(r))}i(r=>({...r,isLoading:!0,error:null})),P(!0),B(null);const u=`${l.replace(/\/$/,"")}/${o.replace(/^\//,"")}`;return $({url:u,method:"GET",timeout:m,maxRetries:a,backoffBaseDelay:w,accessToken:L,queryParams:{customer_id:t}}).then(r=>{if(r&&r.data&&Array.isArray(r.data.entitlements)){D(r.data);const f=Q(r.data);i({data:f,isLoading:!1,error:null})}else{const f=new Error("Received empty, malformed, or invalid data structure from entitlements API.");throw i({data:null,isLoading:!1,error:f}),A(f.message,"fetchAllEntitlements",r),f}}).catch(r=>{const f=r instanceof Error?r:new Error(String(r));return i(y=>({...y,isLoading:!1,error:f})),A(f.message,"fetchAllEntitlements",r),Promise.reject(f)}).finally(()=>{P(!1)})},[l,t,o,m,a,w,L,A]),j=d.useCallback(()=>{if(!h){const e="Main API URL not configured. Cannot fetch subscriptions.";return b({data:null,isLoading:!1,error:new Error(e)}),A(e,"fetchSubscriptions"),Promise.reject(new Error(e))}if(!t){const e="CustomerId must be provided as props to KelviqProvider.";return b({data:null,isLoading:!1,error:new Error(e)}),A(e,"fetchSubscriptions"),Promise.reject(new Error(e))}b(e=>({...e,isLoading:!0,error:null}));const u=`${h.replace(/\/$/,"")}/subscriptions/`;return $({url:u,method:"GET",timeout:m,maxRetries:a,backoffBaseDelay:w,accessToken:L,queryParams:{customer_id:t}}).then(e=>{if(e&&e.data&&Array.isArray(e.data.results))b({data:e.data.results,isLoading:!1,error:null});else{const r=new Error("Received empty, malformed, or invalid data structure from subscriptions API.");throw b({data:null,isLoading:!1,error:r}),A(r.message,"fetchSubscriptions",e),r}}).catch(e=>{const r=e instanceof Error?e:new Error(String(e));return b(f=>({...f,isLoading:!1,error:r})),A(r.message,"fetchSubscriptions",e),Promise.reject(r)})},[h,t,m,a,w,L,A]),x=d.useCallback(()=>{if(!h){const e="Main API URL not configured. Cannot fetch customer.";return I({data:null,isLoading:!1,error:new Error(e)}),A(e,"fetchCustomer"),Promise.reject(new Error(e))}if(!t){const e="CustomerId must be provided as props to KelviqProvider.";return I({data:null,isLoading:!1,error:new Error(e)}),A(e,"fetchCustomer"),Promise.reject(new Error(e))}I(e=>({...e,isLoading:!0,error:null}));const u=`${h.replace(/\/$/,"")}/customers/${t}/`;return $({url:u,method:"GET",timeout:m,maxRetries:a,backoffBaseDelay:w,accessToken:L}).then(e=>{if(e&&e.data)I({data:e.data,isLoading:!1,error:null});else{const r=new Error("Received empty or invalid data from customer API.");throw I({data:null,isLoading:!1,error:r}),A(r.message,"fetchCustomer",e),r}}).catch(e=>{const r=e instanceof Error?e:new Error(String(e));return I(f=>({...f,isLoading:!1,error:r})),A(r.message,"fetchCustomer",e),Promise.reject(r)})},[h,t,m,a,w,L,A]);d.useEffect(()=>{let u=!0;if(!l){if(u){const e=new Error("KelviqProvider: `apiBaseUrl` must be provided in config.");B(e),P(!1),i(r=>({...r,isLoading:!1,error:e})),q.onError&&q.onError(e)}return}return T?O().catch(e=>{u&&k&&P(!1),console.error("Kelviq SDK: Initial entitlement fetch failed.",e)}):u&&(i(e=>({...e,isLoading:!1,data:null,error:null})),P(!1)),U&&j().catch(e=>{console.error("Kelviq SDK: Initial subscriptions fetch failed.",e)}),M&&x().catch(e=>{console.error("Kelviq SDK: Initial customer fetch failed.",e)}),()=>{u=!1}},[l,T,U,M,q.onError]);const G=d.useCallback(()=>v.data?v.data:{},[v.data]),H=d.useCallback(()=>E?{customerId:E.customerId,entitlements:E.entitlements}:null,[E]),X=d.useCallback(u=>E?{customerId:E.customerId,entitlements:E.entitlements.filter(e=>e.featureId===u)}:null,[E]),z=d.useCallback(u=>v.data?v.data[u]??null:null,[v.data]),J=d.useCallback((u,e)=>{i(r=>{if(!r.data)return r;const f=r.data[u];if(!f)return r;const y={...f,...e};return("usageLimit"in e||"currentUsage"in e)&&(y.remaining=y.usageLimit!==null?y.usageLimit-y.currentUsage:null),{...r,data:{...r.data,[u]:y}}})},[]),V=d.useCallback(u=>{if(!v.data)return!1;const e=v.data[u];return e?e.hasAccess:!1},[v.data]),ge=d.useMemo(()=>({allEntitlements:v,refreshAllEntitlements:O,getEntitlements:G,getEntitlement:z,getRawEntitlements:H,getRawEntitlement:X,hasAccess:V,updateEntitlement:J,subscriptions:K,refreshSubscriptions:j,customer:N,refreshCustomer:x,isLoading:k,error:W,environment:p,apiUrl:l,entitlementsPath:o,customerId:t}),[v,O,G,z,H,X,V,J,K,j,N,x,k,W,p,l,o,t]);return g.jsx(_.Provider,{value:ge,children:n})},C=()=>{const n=d.useContext(_);if(n===void 0||n===R)throw new Error("useKelviq must be used within an initialized KelviqProvider.");return n},F=({featureId:n,children:s,fallback:o=null,loadingComponent:t,condition:p})=>{const{getEntitlement:L,isLoading:q,error:S}=C(),a=L(n);return q&&!a?g.jsx(g.Fragment,{children:t!==void 0?t:o}):S&&!a?g.jsx(g.Fragment,{children:o}):!a||!a.hasAccess||p&&!p(a)?g.jsx(g.Fragment,{children:o}):typeof s=="function"?g.jsx(g.Fragment,{children:s(a)}):g.jsx(g.Fragment,{children:s})},ie=n=>g.jsx(F,{...n}),le=n=>{const s=o=>o.hasAccess&&(o.remaining===null||o.remaining>0);return g.jsx(F,{...n,condition:n.condition||s})},ae=n=>g.jsx(F,{...n}),ue=()=>{const{allEntitlements:n}=C();return n},ce=n=>{const{getEntitlement:s}=C();return s(n)},de=n=>{const{getEntitlement:s}=C();return s(n)},fe=n=>{const{getEntitlement:s}=C();return s(n)},me=()=>{const{subscriptions:n}=C();return n},Ee=()=>{const{customer:n}=C();return n};c.KelviqContext=_,c.KelviqProvider=oe,c.ShowWhenBooleanEntitled=ae,c.ShowWhenCustomizableEntitled=ie,c.ShowWhenMeteredEntitled=le,c.defaultKelviqContextValue=R,c.useAllEntitlements=ue,c.useBooleanEntitlement=ce,c.useCustomer=Ee,c.useCustomizableEntitlement=de,c.useKelviq=C,c.useMeteredEntitlement=fe,c.useSubscriptions=me,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
1
+ (function(f,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],i):(f=typeof globalThis<"u"?globalThis:f||self,i(f.KelviqReactSDK={},f.jsxRuntime,f.React))})(this,function(f,i,g){"use strict";const ie="GET";function D(n){const{method:o=ie,headers:l={},body:r,timeout:E=5e3,maxRetries:c=3,backoffBaseDelay:u=1e3,queryParams:A,accessToken:s}=n;let{url:a}=n,L=0;return new Promise((q,K)=>{if(A){const y=new URLSearchParams;for(const b in A)A[b]!==void 0&&y.append(b,String(A[b]));y.toString()&&(a+=(a.includes("?")?"&":"?")+y.toString())}function C(){const y=new XMLHttpRequest;y.open(o,a,!0),y.timeout=E;const b={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...l};o!=="GET"&&r&&typeof r=="object"&&!(r instanceof FormData)&&(b["Content-Type"]||(b["Content-Type"]="application/json")),s&&(b.Authorization=`Bearer ${s}`);for(const[d,h]of Object.entries(b))y.setRequestHeader(d,h);y.onload=function(){const{status:d,statusText:h,responseText:w}=y;if(d>=200&&d<300)try{const S=w?JSON.parse(w):{};q({status:d,statusText:h,data:S})}catch(S){const _=S instanceof Error?S:new Error(String(S));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${a}. Error: ${_.message}. Response: ${w}`),K(new Error(`Invalid JSON response: ${_.message}`))}else P(`Request to ${a} failed with status ${d} ${h}. Response: ${w}`)},y.onerror=()=>P(`Network error for URL ${a}. The request could not be completed.`),y.ontimeout=()=>P(`Request to ${a} timed out.`);function P(d){if(L<c){L++;const h=Math.min(u*Math.pow(2,L-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${a}. Attempt ${L}/${c}. Error: ${d}. Retrying in ${h}ms.`),setTimeout(C,h)}else K(new Error(`${d} (Max retries ${c} reached)`))}try{let d=r;r&&typeof r=="object"&&!(r instanceof FormData)&&b["Content-Type"]==="application/json"&&(d=JSON.stringify(r)),y.send(d)}catch(d){const h=d instanceof Error?d:new Error(String(d));console.error(`Kelviq SDK (apiRequest): Error sending request to ${a}.`,h),K(new Error(`Failed to send request: ${h.message}`))}}C()})}function oe(n,o){const l=o[0].featureType;if(l==="METER"){let r=0,E=0,c=!1;for(const A of o){const s=A,a=typeof s.usageLimit=="number"?s.usageLimit:null,L=typeof s.currentUsage=="number"?s.currentUsage:0;a===null&&(r=null),r!==null&&(r+=a),E+=L,s.hardLimit&&(c=!0)}const u=r!==null?r-E:null;return{featureId:n,featureType:l,hasAccess:u===null||u>0,currentUsage:E,usageLimit:r,remaining:u,hardLimit:c,items:o}}if(l==="CUSTOMIZABLE"){const r=o[0];return{featureId:n,featureType:l,hasAccess:o.some(E=>E.hasAccess),currentUsage:typeof r.currentUsage=="number"?r.currentUsage:0,usageLimit:typeof r.usageLimit=="number"?r.usageLimit:null,remaining:typeof r.remaining=="number"?r.remaining:null,hardLimit:r.hardLimit===!0,items:o}}return{featureId:n,featureType:l,hasAccess:o.some(r=>r.hasAccess),currentUsage:0,usageLimit:null,remaining:null,hardLimit:!1,items:o}}function se(n){const o={};if(!n||!Array.isArray(n.entitlements))return console.warn("Kelviq SDK: Invalid or empty entitlements array in API response.",n),o;const l={};for(const r of n.entitlements){if(!r||typeof r.featureId!="string"||typeof r.featureType!="string"){console.warn("Kelviq SDK: Skipping invalid raw entitlement object:",r);continue}l[r.featureId]||(l[r.featureId]=[]),l[r.featureId].push(r)}for(const r in l)o[r]=oe(r,l[r]);return o}const O={allEntitlements:{data:null,isLoading:!0,error:null},refreshAllEntitlements:()=>(console.warn("refreshAllEntitlements called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),getEntitlements:()=>({}),getEntitlement:()=>null,getRawEntitlements:()=>null,getRawEntitlement:()=>null,hasAccess:()=>!1,updateEntitlement:()=>{console.warn("updateEntitlement called on default KelviqContext. Ensure KelviqProvider is properly set up.")},subscriptions:{data:null,isLoading:!1,error:null},refreshSubscriptions:()=>(console.warn("refreshSubscriptions called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),customer:{data:null,isLoading:!1,error:null},refreshCustomer:()=>(console.warn("refreshCustomer called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),pricing:{data:null,isLoading:!1,error:null},refreshPricing:()=>(console.warn("refreshPricing called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),isLoading:!0,error:null,apiUrl:"",environment:"production",entitlementsPath:"",customerId:""},k=g.createContext(O),ae="https://edge.api.kelviq.com/api/v1/",le="https://edge.sandboxapi.kelviq.com/api/v1/",ce="https://api.kelviq.com/api/v1/",ue="https://sandboxapi.kelviq.com/api/v1/",de="entitlements/",fe="monetization/product-offering/",me=({children:n,apiUrl:o,entitlementsPath:l=de,customerId:r,environment:E="production",accessToken:c,productId:u,config:A={}})=>{const{onError:s,maxRetries:a,timeout:L,backoffBaseDelay:q,fetchEntitlementsOnMount:K=!0,fetchSubscriptionsOnMount:C=!1,fetchCustomerOnMount:y=!1,fetchPricingOnMount:b=!1}=A,P=o||(E==="sandbox"?le:ae),d=E==="sandbox"?ue:ce,[h,w]=g.useState({data:null,isLoading:!!P&&K,error:null}),[S,_]=g.useState(null),[J,I]=g.useState({data:null,isLoading:!!P&&C,error:null}),[Q,M]=g.useState({data:null,isLoading:!!P&&y,error:null}),[V,$]=g.useState({data:null,isLoading:!!P&&b,error:null}),[N,F]=g.useState(!!P&&K),[Y,j]=g.useState(null),v=g.useCallback((m,e,t)=>{const p=new Error(`Kelviq SDK (${e}): ${m}`);console.error(p.message,t||""),j(T=>T||p),s&&s(p)},[s]),G=g.useCallback(()=>{if(!P){const t="API URL not configured. Cannot fetch entitlements.";return w({data:null,isLoading:!1,error:new Error(t)}),v(t,"fetchAllEntitlements"),F(!1),Promise.reject(new Error(t))}if(!r){const t="CustomerId must be provided as props to KelviqProvider.";return w(()=>({data:null,isLoading:!1,error:new Error(t)})),v(t,"fetchAllEntitlements"),F(!1),Promise.reject(new Error(t))}w(t=>({...t,isLoading:!0,error:null})),F(!0),j(null);const m=`${P.replace(/\/$/,"")}/${l.replace(/^\//,"")}`;return D({url:m,method:"GET",timeout:L,maxRetries:a,backoffBaseDelay:q,accessToken:c,queryParams:{customer_id:r}}).then(t=>{if(t&&t.data&&Array.isArray(t.data.entitlements)){_(t.data);const p=se(t.data);w({data:p,isLoading:!1,error:null})}else{const p=new Error("Received empty, malformed, or invalid data structure from entitlements API.");throw w({data:null,isLoading:!1,error:p}),v(p.message,"fetchAllEntitlements",t),p}}).catch(t=>{const p=t instanceof Error?t:new Error(String(t));return w(T=>({...T,isLoading:!1,error:p})),v(p.message,"fetchAllEntitlements",t),Promise.reject(p)}).finally(()=>{F(!1)})},[P,r,l,L,a,q,c,v]),H=g.useCallback(()=>{if(!d){const e="Main API URL not configured. Cannot fetch subscriptions.";return I({data:null,isLoading:!1,error:new Error(e)}),v(e,"fetchSubscriptions"),Promise.reject(new Error(e))}if(!r){const e="CustomerId must be provided as props to KelviqProvider.";return I({data:null,isLoading:!1,error:new Error(e)}),v(e,"fetchSubscriptions"),Promise.reject(new Error(e))}I(e=>({...e,isLoading:!0,error:null}));const m=`${d.replace(/\/$/,"")}/subscriptions/`;return D({url:m,method:"GET",timeout:L,maxRetries:a,backoffBaseDelay:q,accessToken:c,queryParams:{customer_id:r}}).then(e=>{if(e&&e.data&&Array.isArray(e.data.results))I({data:e.data.results,isLoading:!1,error:null});else{const t=new Error("Received empty, malformed, or invalid data structure from subscriptions API.");throw I({data:null,isLoading:!1,error:t}),v(t.message,"fetchSubscriptions",e),t}}).catch(e=>{const t=e instanceof Error?e:new Error(String(e));return I(p=>({...p,isLoading:!1,error:t})),v(t.message,"fetchSubscriptions",e),Promise.reject(t)})},[d,r,L,a,q,c,v]),W=g.useCallback(()=>{if(!d){const e="Main API URL not configured. Cannot fetch customer.";return M({data:null,isLoading:!1,error:new Error(e)}),v(e,"fetchCustomer"),Promise.reject(new Error(e))}if(!r){const e="CustomerId must be provided as props to KelviqProvider.";return M({data:null,isLoading:!1,error:new Error(e)}),v(e,"fetchCustomer"),Promise.reject(new Error(e))}M(e=>({...e,isLoading:!0,error:null}));const m=`${d.replace(/\/$/,"")}/customers/${r}/`;return D({url:m,method:"GET",timeout:L,maxRetries:a,backoffBaseDelay:q,accessToken:c}).then(e=>{if(e&&e.data)M({data:e.data,isLoading:!1,error:null});else{const t=new Error("Received empty or invalid data from customer API.");throw M({data:null,isLoading:!1,error:t}),v(t.message,"fetchCustomer",e),t}}).catch(e=>{const t=e instanceof Error?e:new Error(String(e));return M(p=>({...p,isLoading:!1,error:t})),v(t.message,"fetchCustomer",e),Promise.reject(t)})},[d,r,L,a,q,c,v]),z=g.useCallback(()=>{if(!d){const e="Main API URL not configured. Cannot fetch pricing.";return $({data:null,isLoading:!1,error:new Error(e)}),v(e,"fetchPricing"),Promise.reject(new Error(e))}if(!u){const e="productId must be provided as a prop to KelviqProvider to fetch pricing.";return $({data:null,isLoading:!1,error:new Error(e)}),v(e,"fetchPricing"),Promise.reject(new Error(e))}$(e=>({...e,isLoading:!0,error:null}));const m=`${d.replace(/\/$/,"")}/${fe.replace(/^\//,"")}${u}/`;return D({url:m,method:"GET",timeout:L,maxRetries:a,backoffBaseDelay:q,accessToken:c}).then(e=>{if(e&&e.data&&Array.isArray(e.data.plans))$({data:e.data,isLoading:!1,error:null});else{const t=new Error("Received empty, malformed, or invalid data structure from pricing API.");throw $({data:null,isLoading:!1,error:t}),v(t.message,"fetchPricing",e),t}}).catch(e=>{const t=e instanceof Error?e:new Error(String(e));return $(p=>({...p,isLoading:!1,error:t})),v(t.message,"fetchPricing",e),Promise.reject(t)})},[d,u,L,a,q,c,v]);g.useEffect(()=>{let m=!0;if(!P){if(m){const e=new Error("KelviqProvider: `apiBaseUrl` must be provided in config.");j(e),F(!1),w(t=>({...t,isLoading:!1,error:e})),A.onError&&A.onError(e)}return}return K?G().catch(e=>{m&&N&&F(!1),console.error("Kelviq SDK: Initial entitlement fetch failed.",e)}):m&&(w(e=>({...e,isLoading:!1,data:null,error:null})),F(!1)),C&&H().catch(e=>{console.error("Kelviq SDK: Initial subscriptions fetch failed.",e)}),y&&W().catch(e=>{console.error("Kelviq SDK: Initial customer fetch failed.",e)}),b&&z().catch(e=>{console.error("Kelviq SDK: Initial pricing fetch failed.",e)}),()=>{m=!1}},[P,K,C,y,b]);const Z=g.useCallback(()=>h.data?h.data:{},[h.data]),x=g.useCallback(()=>S?{customerId:S.customerId,entitlements:S.entitlements}:null,[S]),ee=g.useCallback(m=>S?{customerId:S.customerId,entitlements:S.entitlements.filter(e=>e.featureId===m)}:null,[S]),re=g.useCallback(m=>h.data?h.data[m]??null:null,[h.data]),te=g.useCallback((m,e)=>{w(t=>{if(!t.data)return t;const p=t.data[m];if(!p)return t;const T={...p,...e};return("usageLimit"in e||"currentUsage"in e)&&(T.remaining=T.usageLimit!==null?T.usageLimit-T.currentUsage:null),{...t,data:{...t.data,[m]:T}}})},[]),ne=g.useCallback(m=>{if(!h.data)return!1;const e=h.data[m];return e?e.hasAccess:!1},[h.data]),qe=g.useMemo(()=>({allEntitlements:h,refreshAllEntitlements:G,getEntitlements:Z,getEntitlement:re,getRawEntitlements:x,getRawEntitlement:ee,hasAccess:ne,updateEntitlement:te,subscriptions:J,refreshSubscriptions:H,customer:Q,refreshCustomer:W,pricing:V,refreshPricing:z,isLoading:N,error:Y,environment:E,apiUrl:P,entitlementsPath:l,customerId:r}),[h,G,Z,re,x,ee,ne,te,J,H,Q,W,V,z,N,Y,E,P,l,r]);return i.jsx(k.Provider,{value:qe,children:n})},U=()=>{const n=g.useContext(k);if(n===void 0||n===O)throw new Error("useKelviq must be used within an initialized KelviqProvider.");return n},B=({featureId:n,children:o,fallback:l=null,loadingComponent:r,condition:E})=>{const{getEntitlement:c,isLoading:u,error:A}=U(),s=c(n);return u&&!s?i.jsx(i.Fragment,{children:r!==void 0?r:l}):A&&!s?i.jsx(i.Fragment,{children:l}):!s||!s.hasAccess||E&&!E(s)?i.jsx(i.Fragment,{children:l}):typeof o=="function"?i.jsx(i.Fragment,{children:o(s)}):i.jsx(i.Fragment,{children:o})},ge=n=>i.jsx(B,{...n}),Ee=n=>{const o=l=>l.hasAccess&&(l.remaining===null||l.remaining>0);return i.jsx(B,{...n,condition:n.condition||o})},he=n=>i.jsx(B,{...n}),pe=()=>{const{allEntitlements:n}=U();return n},Le=n=>{const{getEntitlement:o}=U();return o(n)},ve=n=>{const{getEntitlement:o}=U();return o(n)},Ae=n=>{const{getEntitlement:o}=U();return o(n)},ye=()=>{const{subscriptions:n}=U();return n},Pe=()=>{const{customer:n}=U();return n},R=()=>{const{pricing:n}=U();return n},X=(n,o,l={})=>{if(typeof n!="number")return"";const{compact:r=!1,locale:E=l.pricingLocale||"en-US",includeCurrencySymbol:c=!0}=l;try{const u={notation:r?"compact":"standard",compactDisplay:"short"},s=new Intl.NumberFormat(E,u).format(n);return c?`${o}${s}`:s}catch{const u=n.toString();return c?`${o}${u}`:u}},we=({planIdentifier:n,billingPeriod:o,children:l,formatOptions:r,loadingComponent:E=null,fallback:c=null})=>{const{data:u,isLoading:A}=R();if(A)return i.jsx(i.Fragment,{children:E});if(!u||!Array.isArray(u.plans))return i.jsx(i.Fragment,{children:c});const s=u.plans.find(C=>C.identifier===n&&C.enabled);if(!s)return i.jsx(i.Fragment,{children:c});const a=s.price.charges.find(C=>C.chargePeriod===o)??null,L=a?a.priceData.amount:0,q=s.price.priceType==="FREE",K=q?"Free":X(L,u.currencySymbol,{...r,pricingLocale:u.pricingLocale});return i.jsx(i.Fragment,{children:l({plan:s,charge:a,amount:L,formattedPrice:K,currencySymbol:u.currencySymbol,currencyCode:u.currencyCode,pricingLocale:u.pricingLocale,isFree:q,hasFreeTrial:s.price.freeTrial,trialPeriod:s.price.trialPeriod})})},Se=({planIdentifier:n,children:o,loadingComponent:l=null,fallback:r=null,featureType:E})=>{const{data:c,isLoading:u}=R();if(u)return i.jsx(i.Fragment,{children:l});if(!c||!Array.isArray(c.plans))return i.jsx(i.Fragment,{children:r});const A=c.plans.find(a=>a.identifier===n&&a.enabled);if(!A)return i.jsx(i.Fragment,{children:r});let s=A.features.filter(a=>a.enabled);return E&&(s=s.filter(a=>a.featureType===E)),s.length===0?i.jsx(i.Fragment,{children:r}):i.jsx(i.Fragment,{children:s.map((a,L)=>o({feature:a,plan:A,index:L}))})};f.KQFeatureList=Se,f.KQPrice=we,f.KelviqContext=k,f.KelviqProvider=me,f.ShowWhenBooleanEntitled=he,f.ShowWhenCustomizableEntitled=ge,f.ShowWhenMeteredEntitled=Ee,f.defaultKelviqContextValue=O,f.kqFormatPrice=X,f.useAllEntitlements=pe,f.useBooleanEntitlement=Le,f.useCustomer=Pe,f.useCustomizableEntitlement=ve,f.useKelviq=U,f.useMeteredEntitlement=Ae,f.usePricing=R,f.useSubscriptions=ye,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})});
@@ -107,4 +107,99 @@ export interface RawCustomerApiResponse {
107
107
  createdOn: string;
108
108
  modifiedOn: string;
109
109
  }
110
+ export interface RawPricingFeature {
111
+ identifier: string;
112
+ displayName: string;
113
+ displayTooltipDescription: boolean;
114
+ enabled: boolean;
115
+ id: string;
116
+ name: string;
117
+ description: string;
118
+ featureType: 'BOOLEAN' | 'METER';
119
+ details: Record<string, unknown>;
120
+ isArchived: boolean;
121
+ metadata: Record<string, unknown>;
122
+ modifiedOn: string;
123
+ meter: Record<string, unknown>;
124
+ featureDetails: {
125
+ units?: {
126
+ plural: string;
127
+ singular: string;
128
+ };
129
+ featureSubType?: string;
130
+ };
131
+ value: boolean | number;
132
+ reset?: string;
133
+ rollover?: {
134
+ limit: number;
135
+ enabled: boolean;
136
+ percentage: number;
137
+ };
138
+ hardLimit?: boolean;
139
+ resetTime?: string;
140
+ isInherited?: boolean;
141
+ isValueOverridden?: boolean;
142
+ }
143
+ export interface RawPricingCharge {
144
+ id: string;
145
+ chargePeriod: string;
146
+ tiers: unknown[];
147
+ priceData: {
148
+ amount: number;
149
+ };
150
+ details: Record<string, unknown>;
151
+ advanced: Record<string, unknown>;
152
+ featureQuantityPrice: number;
153
+ }
154
+ export interface RawPricingPrice {
155
+ id: string;
156
+ priceType: 'FREE' | 'PAID';
157
+ freeTrial: boolean;
158
+ trialPeriod: number;
159
+ currency: string;
160
+ enabled: boolean;
161
+ charges: RawPricingCharge[];
162
+ }
163
+ export interface RawPricingPlan {
164
+ name: string;
165
+ description: string;
166
+ identifier: string;
167
+ details: Record<string, unknown>;
168
+ isVisible: boolean;
169
+ metadata: Record<string, unknown>;
170
+ basePlan: string | null;
171
+ price: RawPricingPrice;
172
+ addons: unknown[];
173
+ originalPrice: Record<string, number>;
174
+ features: RawPricingFeature[];
175
+ shouldHighlight: boolean;
176
+ enabled: boolean;
177
+ displayName: string;
178
+ displayDescription: string;
179
+ includes: string;
180
+ }
181
+ export interface RawPricingBillingPeriod {
182
+ chargePeriod: string;
183
+ enabled: boolean;
184
+ displayName: string;
185
+ promoCaption: string;
186
+ defaultSelected: boolean;
187
+ }
188
+ /** Raw pricing API response with localized currency. */
189
+ export interface RawPricingApiResponse {
190
+ organizationId: number;
191
+ countryCode: string;
192
+ pricingLocale: string;
193
+ currencyCode: string;
194
+ currencySymbol: string;
195
+ currencyConversionRate: number;
196
+ baseCurrencyCode: string;
197
+ baseCurrencySymbol: string;
198
+ plans: RawPricingPlan[];
199
+ subscription: {
200
+ hasPreviousSubscription: boolean;
201
+ };
202
+ isCustomerExists: boolean;
203
+ billingPeriods: RawPricingBillingPeriod[];
204
+ }
110
205
  export {};
@@ -7,3 +7,5 @@ export interface AsyncState<T> {
7
7
  isLoading: boolean;
8
8
  error: Error | null;
9
9
  }
10
+ export type * from './sdk.types';
11
+ export type * from './api.types';
@@ -73,4 +73,10 @@ export interface KelviqApiBehaviorOptions {
73
73
  * Defaults to false. When enabled, data is available via `useCustomer()`.
74
74
  */
75
75
  fetchCustomerOnMount?: boolean;
76
+ /**
77
+ * If true, fetches pricing and plan data when the provider mounts.
78
+ * Defaults to false. When enabled, data is available via `usePricing()`.
79
+ * Supports localized currency based on user location.
80
+ */
81
+ fetchPricingOnMount?: boolean;
76
82
  }
@@ -0,0 +1,11 @@
1
+ export interface KQFormatPriceOptions {
2
+ /** Use compact notation (e.g. "1.2K" instead of "1,200"). Defaults to false. */
3
+ compact?: boolean;
4
+ /** Locale for number formatting. Defaults to the pricingLocale from the API. */
5
+ locale?: string;
6
+ /** Whether to include the currency symbol in the formatted string. Defaults to true. */
7
+ includeCurrencySymbol?: boolean;
8
+ }
9
+ export declare const kqFormatPrice: (amount: number, currencySymbol: string, options?: KQFormatPriceOptions & {
10
+ pricingLocale?: string;
11
+ }) => string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kelviq/react-sdk",
3
3
  "private": false,
4
- "version": "2.0.2",
4
+ "version": "2.1.0",
5
5
  "type": "module",
6
6
  "main": "dist/kelviq-react-sdk.js",
7
7
  "types": "dist/index.d.ts",