@consenti/api 0.1.0 → 0.3.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.
- package/README.md +917 -207
- package/dist/chunk-IY4L6MNB.js +2 -0
- package/dist/chunk-IY4L6MNB.js.map +1 -0
- package/dist/chunk-O5UG4OYI.js +2 -0
- package/dist/chunk-O5UG4OYI.js.map +1 -0
- package/dist/dashboard/assets/ApiConfig-CE-Noelz.js +1 -0
- package/dist/dashboard/assets/ArchivedProfileList-Bw-fx-BD.js +1 -0
- package/dist/dashboard/assets/AuditLog-D6HiTQ1o.js +1 -0
- package/dist/dashboard/assets/ChangePassword-D2rPkzD1.js +1 -0
- package/dist/dashboard/assets/ConfirmDialog-DTtw7VJ-.js +1 -0
- package/dist/dashboard/assets/ConsentList-BWOvPOMG.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateEditor-BKIEnLmA.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateList-DCys2nM3.js +1 -0
- package/dist/dashboard/assets/HeadingTagInput-BTjrdzlS.js +1 -0
- package/dist/dashboard/assets/HowItWorks-AeyjfQGO.js +1 -0
- package/dist/dashboard/assets/PermissionGate-Bhhf7SMh.js +1 -0
- package/dist/dashboard/assets/PreviewPane-Dq5K1h2L.js +35 -0
- package/dist/dashboard/assets/ProfileEditor-BXtaE0tL.js +2 -0
- package/dist/dashboard/assets/ProfileList-CWKSAgu0.js +1 -0
- package/dist/dashboard/assets/ProfileVersionHistory-1h8sUgog.js +3 -0
- package/dist/dashboard/assets/RecordDetailModal-FaFhzQXi.js +1 -0
- package/dist/dashboard/assets/Reports-DWEjQOKh.js +1 -0
- package/dist/dashboard/assets/RoleList-BSGoqlzp.js +1 -0
- package/dist/dashboard/assets/Select-DjJNwlbY.js +1 -0
- package/dist/dashboard/assets/Settings-CVYrlyLm.js +1 -0
- package/dist/dashboard/assets/SetupWizard-4NTb5bZj.js +1 -0
- package/dist/dashboard/assets/Table-DH_jh8_N.js +1 -0
- package/dist/dashboard/assets/TenantList-5AwFtoHf.js +1 -0
- package/dist/dashboard/assets/UITemplateEditor-9A55scBe.js +1 -0
- package/dist/dashboard/assets/UITemplateList-BS7X4u4F.js +1 -0
- package/dist/dashboard/assets/UserList-SPkMyFRy.js +1 -0
- package/dist/dashboard/assets/VendorList-C5fPhr6U.js +1 -0
- package/dist/dashboard/assets/VisitorList-COP1SyNd.js +1 -0
- package/dist/dashboard/assets/check-o5cp1s2J.js +1 -0
- package/dist/dashboard/assets/client-ymc9nwWr.js +1 -0
- package/dist/dashboard/assets/copy-C01KoUUH.js +1 -0
- package/dist/dashboard/assets/createLucideIcon-MdjI3m3u.js +1 -0
- package/dist/dashboard/assets/de-DET92Jb5.js +5 -0
- package/dist/dashboard/assets/debounce-DlvAAT2V.js +1 -0
- package/dist/dashboard/assets/dist-DlhLroYC.js +1 -0
- package/dist/dashboard/assets/es-CJNunrnU.js +5 -0
- package/dist/dashboard/assets/external-link-DVe00ZBK.js +1 -0
- package/dist/dashboard/assets/fr-akfTuf5x.js +5 -0
- package/dist/dashboard/assets/history-CHTts_m2.js +1 -0
- package/dist/dashboard/assets/index-DW3qOrry.css +2 -0
- package/dist/dashboard/assets/index-H_Zm0X-u.js +6 -0
- package/dist/dashboard/assets/ja-B0nLWkOd.js +5 -0
- package/dist/dashboard/assets/profiles-DmrdGv7t.js +1 -0
- package/dist/dashboard/assets/roles-C0dJPFl4.js +1 -0
- package/dist/dashboard/assets/search-Bd2RdDRh.js +1 -0
- package/dist/dashboard/assets/templates-B2xiivh4.js +1 -0
- package/dist/dashboard/assets/templates-CnMoLavN.js +1 -0
- package/dist/dashboard/assets/trash-2-BBnkdgm_.js +1 -0
- package/dist/dashboard/assets/trash-Dj1jhzHJ.js +1 -0
- package/dist/dashboard/assets/useHashQuery-B5ytMIMp.js +1 -0
- package/dist/dashboard/assets/users-czK8LRVh.js +1 -0
- package/dist/dashboard/assets/x-BRuyBEQP.js +1 -0
- package/dist/dashboard/assets/zh-CN-Dxg3G-BL.js +5 -0
- package/dist/dashboard/index.html +4 -2
- package/dist/dist-Z5RVTVBT.js +2 -0
- package/dist/dist-Z5RVTVBT.js.map +1 -0
- package/dist/index.cjs +129 -9089
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +963 -254
- package/dist/index.d.ts +963 -254
- package/dist/index.js +129 -9043
- package/dist/index.js.map +1 -1
- package/dist/profiles-XTYPWLVC.js +2 -0
- package/dist/profiles-XTYPWLVC.js.map +1 -0
- package/dist/widget/widget.css +1 -0
- package/dist/widget/widget.js +1 -2304
- package/package.json +19 -12
- package/dist/dashboard/assets/index-BBbhHyc_.js +0 -2637
- package/dist/dashboard/assets/index-DYK73WL5.css +0 -1
|
@@ -1,2637 +0,0 @@
|
|
|
1
|
-
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const i of s)if(i.type==="childList")for(const a of i.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function o(s){const i={};return s.integrity&&(i.integrity=s.integrity),s.referrerPolicy&&(i.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?i.credentials="include":s.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(s){if(s.ep)return;s.ep=!0;const i=o(s);fetch(s.href,i)}})();var Ke,z,bt,ie,Mn,xt,vt,Qe,Oe,Te,_t,mn,rn,sn,kt,Ue={},je=[],bo=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Ie=Array.isArray;function te(n,t){for(var o in t)n[o]=t[o];return n}function fn(n){n&&n.parentNode&&n.parentNode.removeChild(n)}function le(n,t,o){var r,s,i,a={};for(i in t)i=="key"?r=t[i]:i=="ref"?s=t[i]:a[i]=t[i];if(arguments.length>2&&(a.children=arguments.length>3?Ke.call(arguments,2):o),typeof n=="function"&&n.defaultProps!=null)for(i in n.defaultProps)a[i]===void 0&&(a[i]=n.defaultProps[i]);return ze(n,a,r,s,null)}function ze(n,t,o,r,s){var i={type:n,props:t,key:o,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:s??++bt,__i:-1,__u:0};return s==null&&z.vnode!=null&&z.vnode(i),i}function Z(n){return n.children}function oe(n,t){this.props=n,this.context=t}function xe(n,t){if(t==null)return n.__?xe(n.__,n.__i+1):null;for(var o;t<n.__k.length;t++)if((o=n.__k[t])!=null&&o.__e!=null)return o.__e;return typeof n.type=="function"?xe(n):null}function xo(n){if(n.__P&&n.__d){var t=n.__v,o=t.__e,r=[],s=[],i=te({},t);i.__v=t.__v+1,z.vnode&&z.vnode(i),yn(n.__P,i,t,n.__n,n.__P.namespaceURI,32&t.__u?[o]:null,r,o??xe(t),!!(32&t.__u),s),i.__v=t.__v,i.__.__k[i.__i]=i,Tt(r,i,s),t.__e=t.__=null,i.__e!=o&&wt(i)}}function wt(n){if((n=n.__)!=null&&n.__c!=null)return n.__e=n.__c.base=null,n.__k.some(function(t){if(t!=null&&t.__e!=null)return n.__e=n.__c.base=t.__e}),wt(n)}function an(n){(!n.__d&&(n.__d=!0)&&ie.push(n)&&!Ve.__r++||Mn!=z.debounceRendering)&&((Mn=z.debounceRendering)||xt)(Ve)}function Ve(){try{for(var n,t=1;ie.length;)ie.length>t&&ie.sort(vt),n=ie.shift(),t=ie.length,xo(n)}finally{ie.length=Ve.__r=0}}function Ct(n,t,o,r,s,i,a,c,l,p,d){var u,h,m,k,y,b,v,w=r&&r.__k||je,N=t.length;for(l=vo(o,t,w,l,N),u=0;u<N;u++)(m=o.__k[u])!=null&&(h=m.__i!=-1&&w[m.__i]||Ue,m.__i=u,b=yn(n,m,h,s,i,a,c,l,p,d),k=m.__e,m.ref&&h.ref!=m.ref&&(h.ref&&bn(h.ref,null,m),d.push(m.ref,m.__c||k,m)),y==null&&k!=null&&(y=k),(v=!!(4&m.__u))||h.__k===m.__k?(l=St(m,l,n,v),v&&h.__e&&(h.__e=null)):typeof m.type=="function"&&b!==void 0?l=b:k&&(l=k.nextSibling),m.__u&=-7);return o.__e=y,l}function vo(n,t,o,r,s){var i,a,c,l,p,d=o.length,u=d,h=0;for(n.__k=new Array(s),i=0;i<s;i++)(a=t[i])!=null&&typeof a!="boolean"&&typeof a!="function"?(typeof a=="string"||typeof a=="number"||typeof a=="bigint"||a.constructor==String?a=n.__k[i]=ze(null,a,null,null,null):Ie(a)?a=n.__k[i]=ze(Z,{children:a},null,null,null):a.constructor===void 0&&a.__b>0?a=n.__k[i]=ze(a.type,a.props,a.key,a.ref?a.ref:null,a.__v):n.__k[i]=a,l=i+h,a.__=n,a.__b=n.__b+1,c=null,(p=a.__i=_o(a,o,l,u))!=-1&&(u--,(c=o[p])&&(c.__u|=2)),c==null||c.__v==null?(p==-1&&(s>d?h--:s<d&&h++),typeof a.type!="function"&&(a.__u|=4)):p!=l&&(p==l-1?h--:p==l+1?h++:(p>l?h--:h++,a.__u|=4))):n.__k[i]=null;if(u)for(i=0;i<d;i++)(c=o[i])!=null&&(2&c.__u)==0&&(c.__e==r&&(r=xe(c)),Mt(c,c));return r}function St(n,t,o,r){var s,i;if(typeof n.type=="function"){for(s=n.__k,i=0;s&&i<s.length;i++)s[i]&&(s[i].__=n,t=St(s[i],t,o,r));return t}n.__e!=t&&(r&&(t&&n.type&&!t.parentNode&&(t=xe(n)),o.insertBefore(n.__e,t||null)),t=n.__e);do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function He(n,t){return t=t||[],n==null||typeof n=="boolean"||(Ie(n)?n.some(function(o){He(o,t)}):t.push(n)),t}function _o(n,t,o,r){var s,i,a,c=n.key,l=n.type,p=t[o],d=p!=null&&(2&p.__u)==0;if(p===null&&c==null||d&&c==p.key&&l==p.type)return o;if(r>(d?1:0)){for(s=o-1,i=o+1;s>=0||i<t.length;)if((p=t[a=s>=0?s--:i++])!=null&&(2&p.__u)==0&&c==p.key&&l==p.type)return a}return-1}function Pn(n,t,o){t[0]=="-"?n.setProperty(t,o??""):n[t]=o==null?"":typeof o!="number"||bo.test(t)?o:o+"px"}function Le(n,t,o,r,s){var i,a;e:if(t=="style")if(typeof o=="string")n.style.cssText=o;else{if(typeof r=="string"&&(n.style.cssText=r=""),r)for(t in r)o&&t in o||Pn(n.style,t,"");if(o)for(t in o)r&&o[t]==r[t]||Pn(n.style,t,o[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(_t,"$1")),a=t.toLowerCase(),t=a in n||t=="onFocusOut"||t=="onFocusIn"?a.slice(2):t.slice(2),n.l||(n.l={}),n.l[t+i]=o,o?r?o[Te]=r[Te]:(o[Te]=mn,n.addEventListener(t,i?sn:rn,i)):n.removeEventListener(t,i?sn:rn,i);else{if(s=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in n)try{n[t]=o??"";break e}catch{}typeof o=="function"||(o==null||o===!1&&t[4]!="-"?n.removeAttribute(t):n.setAttribute(t,t=="popover"&&o==1?"":o))}}function En(n){return function(t){if(this.l){var o=this.l[t.type+n];if(t[Oe]==null)t[Oe]=mn++;else if(t[Oe]<o[Te])return;return o(z.event?z.event(t):t)}}}function yn(n,t,o,r,s,i,a,c,l,p){var d,u,h,m,k,y,b,v,w,N,I,x,$,D,T,_,R=t.type;if(t.constructor!==void 0)return null;128&o.__u&&(l=!!(32&o.__u),i=[c=t.__e=o.__e]),(d=z.__b)&&d(t);e:if(typeof R=="function"){u=a.length;try{if(w=t.props,N=R.prototype&&R.prototype.render,I=(d=R.contextType)&&r[d.__c],x=d?I?I.props.value:d.__:r,o.__c?v=(h=t.__c=o.__c).__=h.__E:(N?t.__c=h=new R(w,x):(t.__c=h=new oe(w,x),h.constructor=R,h.render=wo),I&&I.sub(h),h.state||(h.state={}),h.__n=r,m=h.__d=!0,h.__h=[],h._sb=[]),N&&h.__s==null&&(h.__s=h.state),N&&R.getDerivedStateFromProps!=null&&(h.__s==h.state&&(h.__s=te({},h.__s)),te(h.__s,R.getDerivedStateFromProps(w,h.__s))),k=h.props,y=h.state,h.__v=t,m)N&&R.getDerivedStateFromProps==null&&h.componentWillMount!=null&&h.componentWillMount(),N&&h.componentDidMount!=null&&h.__h.push(h.componentDidMount);else{if(N&&R.getDerivedStateFromProps==null&&w!==k&&h.componentWillReceiveProps!=null&&h.componentWillReceiveProps(w,x),t.__v==o.__v||!h.__e&&h.shouldComponentUpdate!=null&&h.shouldComponentUpdate(w,h.__s,x)===!1){t.__v!=o.__v&&(h.props=w,h.state=h.__s,h.__d=!1),t.__e=o.__e,t.__k=o.__k,t.__k.some(function(U){U&&(U.__=t)}),je.push.apply(h.__h,h._sb),h._sb=[],h.__h.length&&a.push(h);break e}h.componentWillUpdate!=null&&h.componentWillUpdate(w,h.__s,x),N&&h.componentDidUpdate!=null&&h.__h.push(function(){h.componentDidUpdate(k,y,b)})}if(h.context=x,h.props=w,h.__P=n,h.__e=!1,$=z.__r,D=0,N)h.state=h.__s,h.__d=!1,$&&$(t),d=h.render(h.props,h.state,h.context),je.push.apply(h.__h,h._sb),h._sb=[];else do h.__d=!1,$&&$(t),d=h.render(h.props,h.state,h.context),h.state=h.__s;while(h.__d&&++D<25);h.state=h.__s,h.getChildContext!=null&&(r=te(te({},r),h.getChildContext())),N&&!m&&h.getSnapshotBeforeUpdate!=null&&(b=h.getSnapshotBeforeUpdate(k,y)),T=d!=null&&d.type===Z&&d.key==null?It(d.props.children):d,c=Ct(n,Ie(T)?T:[T],t,o,r,s,i,a,c,l,p),h.base=t.__e,t.__u&=-161,h.__h.length&&a.push(h),v&&(h.__E=h.__=null)}catch(U){if(a.length=u,t.__v=null,l||i!=null)if(U.then){for(t.__u|=l?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;i!=null&&(i[i.indexOf(c)]=null),t.__e=c}else{if(i!=null)for(_=i.length;_--;)fn(i[_]);ln(t)}else t.__e=o.__e,!t.__k&&o.__k&&(t.__k=o.__k),U.then||ln(t);z.__e(U,t,o)}}else i==null&&t.__v==o.__v?(t.__k=o.__k,t.__e=o.__e):c=t.__e=ko(o.__e,t,o,r,s,i,a,l,p);return(d=z.diffed)&&d(t),128&t.__u?void 0:c}function ln(n){n&&(n.__c&&(n.__c.__e=!0),n.__k&&n.__k.some(ln))}function Tt(n,t,o){for(var r=0;r<o.length;r++)bn(o[r],o[++r],o[++r]);z.__c&&z.__c(t,n),n.some(function(s){try{n=s.__h,s.__h=[],n.some(function(i){i.call(s)})}catch(i){z.__e(i,s.__v)}})}function It(n){return typeof n!="object"||n==null||n.__b>0?n:Ie(n)?n.map(It):n.constructor!==void 0?null:te({},n)}function ko(n,t,o,r,s,i,a,c,l){var p,d,u,h,m,k,y,b=o.props||Ue,v=t.props,w=t.type;if(w=="svg"?s="http://www.w3.org/2000/svg":w=="math"?s="http://www.w3.org/1998/Math/MathML":s||(s="http://www.w3.org/1999/xhtml"),i!=null){for(p=0;p<i.length;p++)if((m=i[p])&&"setAttribute"in m==!!w&&(w?m.localName==w:m.nodeType==3)){n=m,i[p]=null;break}}if(n==null){if(w==null)return document.createTextNode(v);n=document.createElementNS(s,w,v.is&&v),c&&(z.__m&&z.__m(t,i),c=!1),i=null}if(w==null)b===v||c&&n.data==v||(n.data=v);else{if(i=w=="textarea"&&v.defaultValue!=null?null:i&&Ke.call(n.childNodes),!c&&i!=null)for(b={},p=0;p<n.attributes.length;p++)b[(m=n.attributes[p]).name]=m.value;for(p in b)m=b[p],p=="dangerouslySetInnerHTML"?u=m:p=="children"||p in v||p=="value"&&"defaultValue"in v||p=="checked"&&"defaultChecked"in v||Le(n,p,null,m,s);for(p in v)m=v[p],p=="children"?h=m:p=="dangerouslySetInnerHTML"?d=m:p=="value"?k=m:p=="checked"?y=m:c&&typeof m!="function"||b[p]===m||Le(n,p,m,b[p],s);if(d)c||u&&(d.__html==u.__html||d.__html==n.innerHTML)||(n.innerHTML=d.__html),t.__k=[];else if(u&&(n.innerHTML=""),Ct(t.type=="template"?n.content:n,Ie(h)?h:[h],t,o,r,w=="foreignObject"?"http://www.w3.org/1999/xhtml":s,i,a,i?i[0]:o.__k&&xe(o,0),c,l),i!=null)for(p=i.length;p--;)fn(i[p]);c&&w!="textarea"||(p="value",w=="progress"&&k==null?n.removeAttribute("value"):k!=null&&(k!==n[p]||w=="progress"&&!k||w=="option"&&k!=b[p])&&Le(n,p,k,b[p],s),p="checked",y!=null&&y!=n[p]&&Le(n,p,y,b[p],s))}return n}function bn(n,t,o){try{if(typeof n=="function"){var r=typeof n.__u=="function";r&&n.__u(),r&&t==null||(n.__u=n(t))}else n.current=t}catch(s){z.__e(s,o)}}function Mt(n,t,o){var r,s;if(z.unmount&&z.unmount(n),(r=n.ref)&&(r.current&&r.current!=n.__e||bn(r,null,t)),(r=n.__c)!=null){if(r.componentWillUnmount)try{r.componentWillUnmount()}catch(i){z.__e(i,t)}r.base=r.__P=r.__n=null}if(r=n.__k)for(s=0;s<r.length;s++)r[s]&&Mt(r[s],t,o||typeof n.type!="function");o||fn(n.__e),n.__c=n.__=n.__e=void 0}function wo(n,t,o){return this.constructor(n,o)}function cn(n,t,o){var r,s,i,a;t==document&&(t=document.documentElement),z.__&&z.__(n,t),s=(r=!1)?null:t.__k,i=[],a=[],yn(t,n=t.__k=le(Z,null,[n]),s||Ue,Ue,t.namespaceURI,s?null:t.firstChild?Ke.call(t.childNodes):null,i,s?s.__e:t.firstChild,r,a),Tt(i,n,a),n.props.children=null}function Pt(n){function t(o){var r,s;return this.getChildContext||(r=new Set,(s={})[t.__c]=this,this.getChildContext=function(){return s},this.componentWillUnmount=function(){r=null},this.shouldComponentUpdate=function(i){this.props.value!=i.value&&r.forEach(function(a){a.__e=!0,an(a)})},this.sub=function(i){r.add(i);var a=i.componentWillUnmount;i.componentWillUnmount=function(){r&&r.delete(i),a&&a.call(i)}}),o.children}return t.__c="__cC"+kt++,t.__=n,t.Provider=t.__l=(t.Consumer=function(o,r){return o.children(r)}).contextType=t,t}Ke=je.slice,z={__e:function(n,t,o,r){for(var s,i,a;t=t.__;)if((s=t.__c)&&!s.__)try{if((i=s.constructor)&&i.getDerivedStateFromError!=null&&(s.setState(i.getDerivedStateFromError(n)),a=s.__d),s.componentDidCatch!=null&&(s.componentDidCatch(n,r||{}),a=s.__d),a)return s.__E=s}catch(c){n=c}throw n}},bt=0,oe.prototype.setState=function(n,t){var o;o=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=te({},this.state),typeof n=="function"&&(n=n(te({},o),this.props)),n&&te(o,n),n!=null&&this.__v&&(t&&this._sb.push(t),an(this))},oe.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),an(this))},oe.prototype.render=Z,ie=[],xt=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,vt=function(n,t){return n.__v.__b-t.__v.__b},Ve.__r=0,Qe=Math.random().toString(8),Oe="__d"+Qe,Te="__a"+Qe,_t=/(PointerCapture)$|Capture$/i,mn=0,rn=En(!1),sn=En(!0),kt=0;var Co=0;function e(n,t,o,r,s,i){t||(t={});var a,c,l=t;if("ref"in l)for(c in l={},t)c=="ref"?a=t[c]:l[c]=t[c];var p={type:n,props:l,key:o,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--Co,__i:-1,__u:0,__source:s,__self:i};if(typeof n=="function"&&(a=n.defaultProps))for(c in a)l[c]===void 0&&(l[c]=a[c]);return z.vnode&&z.vnode(p),p}var ve,G,en,Ln,Ge=0,Et=[],q=z,An=q.__b,$n=q.__r,Bn=q.diffed,Nn=q.__c,Dn=q.unmount,Rn=q.__;function We(n,t){q.__h&&q.__h(G,n,Ge||t),Ge=0;var o=G.__H||(G.__H={__:[],__h:[]});return n>=o.__.length&&o.__.push({}),o.__[n]}function g(n){return Ge=1,So(Bt,n)}function So(n,t,o){var r=We(ve++,2);if(r.t=n,!r.__c&&(r.__=[o?o(t):Bt(void 0,t),function(c){var l=r.__N?r.__N[0]:r.__[0],p=r.t(l,c);l!==p&&(r.__N=[p,r.__[1]],r.__c.setState({}))}],r.__c=G,!G.__f)){var s=function(c,l,p){if(!r.__c.__H)return!0;var d=!1,u=r.__c.props!==c;if(r.__c.__H.__.some(function(m){if(m.__N){d=!0;var k=m.__[0];m.__=m.__N,m.__N=void 0,k!==m.__[0]&&(u=!0)}}),i){var h=i.call(this,c,l,p);return d?h||u:h}return!d||u};G.__f=!0;var i=G.shouldComponentUpdate,a=G.componentWillUpdate;G.componentWillUpdate=function(c,l,p){if(this.__e){var d=i;i=void 0,s(c,l,p),i=d}a&&a.call(this,c,l,p)},G.shouldComponentUpdate=s}return r.__N||r.__}function j(n,t){var o=We(ve++,3);!q.__s&&$t(o.__H,t)&&(o.__=n,o.u=t,G.__H.__h.push(o))}function Y(n){return Ge=5,To(function(){return{current:n}},[])}function To(n,t){var o=We(ve++,7);return $t(o.__H,t)&&(o.__=n(),o.__H=t,o.__h=n),o.__}function Lt(n){var t=G.context[n.__c],o=We(ve++,9);return o.c=n,t?(o.__==null&&(o.__=!0,t.sub(G)),t.props.value):n.__}function On(){for(var n;n=Et.shift();){var t=n.__H;if(n.__P&&t)try{t.__h.some(dn),t.__h.some(At),t.__h=[]}catch(o){t.__h=[],q.__e(o,n.__v)}}}q.__b=function(n){G=null,An&&An(n)},q.__=function(n,t){n&&t.__k&&t.__k.__m&&(n.__m=t.__k.__m),Rn&&Rn(n,t)},q.__r=function(n){$n&&$n(n),ve=0;var t=(G=n.__c).__H;t&&(en===G?(t.__h=[],G.__h=[],t.__.some(function(o){o.__N&&(o.__=o.__N),o.u=o.__N=void 0})):(t.__h.length&&On(),ve=0)),en=G},q.diffed=function(n){Bn&&Bn(n);var t=n.__c;t&&t.__H&&(t.__H.__h.length&&(Et.push(t)!==1&&Ln===q.requestAnimationFrame||((Ln=q.requestAnimationFrame)||Io)(On)),t.__H.__.some(function(o){o.u&&(o.__H=o.u,o.u=void 0)})),en=G=null},q.__c=function(n,t){t.some(function(o){try{o.__h.some(dn),o.__h=o.__h.filter(function(r){return!r.__||At(r)})}catch(r){t.some(function(s){s.__h&&(s.__h=[])}),t=[],q.__e(r,o.__v)}}),Nn&&Nn(n,t)},q.unmount=function(n){Dn&&Dn(n);var t,o=n.__c;o&&o.__H&&(o.__H.__.some(function(r){try{dn(r)}catch(s){t=s}}),o.__H=void 0,t&&q.__e(t,o.__v))};var zn=typeof requestAnimationFrame=="function";function Io(n){var t,o=function(){clearTimeout(r),zn&&cancelAnimationFrame(t),setTimeout(n)},r=setTimeout(o,35);zn&&(t=requestAnimationFrame(o))}function dn(n){var t=G,o=n.__c;typeof o=="function"&&(n.__c=void 0,o()),G=t}function At(n){var t=G;n.__c=n.__(),G=t}function $t(n,t){return!n||n.length!==t.length||t.some(function(o,r){return o!==n[r]})}function Bt(n,t){return typeof t=="function"?t(n):t}const Ae="consenti_token";function Un(n){if(!n)return null;try{const t=n.split(".")[1];if(!t)return null;const o=JSON.parse(atob(t.replace(/-/g,"+").replace(/_/g,"/"))),r=o.exp;return r&&r<Math.floor(Date.now()/1e3)?null:{sub:o.sub,email:o.email,roles:o.roles??[],permissions:o.permissions??[]}}catch{return null}}const Nt=Pt({user:null,token:null,login:()=>{},logout:()=>{}});function Mo({children:n}){const[t,o]=g(()=>localStorage.getItem(Ae)),[r,s]=g(()=>Un(localStorage.getItem(Ae))),i=c=>{localStorage.setItem(Ae,c),o(c),s(Un(c))},a=()=>{localStorage.removeItem(Ae),o(null),s(null),window.location.hash="#/login"};return e(Nt.Provider,{value:{user:r,token:t,login:i,logout:a},children:n})}function pe(){return Lt(Nt)}const Dt=Pt({theme:"light",toggle:()=>{}});function Po({children:n}){const[t,o]=g(()=>{try{return localStorage.getItem("dashboard-theme")??"light"}catch{return"light"}});j(()=>{document.documentElement.classList.toggle("dark",t==="dark");try{localStorage.setItem("dashboard-theme",t)}catch{}},[t]);const r=()=>o(s=>s==="dark"?"light":"dark");return e(Dt.Provider,{value:{theme:t,toggle:r},children:n})}const Rt=()=>Lt(Dt),Ot="/consenti/admin";function zt(){return localStorage.getItem("consenti_token")}class jn extends Error{constructor(t,o){super(o),this.status=t}}async function P(n,t){const o=zt(),r=await fetch(`${Ot}${n}`,{...t,headers:{"Content-Type":"application/json",...o?{Authorization:`Bearer ${o}`}:{},...t==null?void 0:t.headers}});if(r.status===401)throw localStorage.removeItem("consenti_token"),window.location.hash="#/login",new jn(401,"Unauthorized");if(!r.ok){const s=await r.text().catch(()=>r.statusText);throw new jn(r.status,s)}if(r.status!==204)return r.json()}function fe(n){const t=zt(),o=`${Ot}${n}`,r=document.createElement("a");r.href=t?`${o}${o.includes("?")?"&":"?"}_t=${encodeURIComponent(t)}`:o,r.style.display="none",document.body.appendChild(r),r.click(),document.body.removeChild(r)}function Eo(){const{login:n}=pe(),[t,o]=g(""),[r,s]=g(""),[i,a]=g(""),[c,l]=g(!1),p=async(u,h)=>{a(""),l(!0);try{const{token:m}=await P("/auth/login",{method:"POST",body:JSON.stringify({email:u,password:h})});n(m),window.location.hash="#/"}catch{a("Invalid email or password")}finally{l(!1)}};return e("div",{class:"min-h-screen bg-gray-50 flex items-center justify-center p-4",children:e("div",{class:"bg-white rounded-xl shadow-sm border border-gray-200 p-8 w-full max-w-sm",children:[e("div",{class:"mb-6 text-center",children:[e("h1",{class:"text-2xl font-bold text-gray-900",children:"Consenti Admin"}),e("p",{class:"text-sm text-gray-500 mt-1",children:"Sign in to your account"})]}),e("form",{onSubmit:u=>{u.preventDefault(),p(t,r)},class:"space-y-4",children:[e("div",{children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:"Email"}),e("input",{type:"email",required:!0,value:t,onInput:u=>o(u.target.value),class:"w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500",placeholder:"admin@example.com"})]}),e("div",{children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:"Password"}),e("input",{type:"password",required:!0,value:r,onInput:u=>s(u.target.value),class:"w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"})]}),i&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2",children:i}),e("button",{type:"submit",disabled:c,class:"w-full bg-blue-600 text-white rounded-lg px-4 py-2.5 text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors",children:c?"Signing in…":"Sign in"})]}),!1]})})}function Ut(n,t){for(var o in t)n[o]=t[o];return n}function Vn(n,t){for(var o in n)if(o!=="__source"&&!(o in t))return!0;for(var r in t)if(r!=="__source"&&n[r]!==t[r])return!0;return!1}function Hn(n,t){this.props=n,this.context=t}(Hn.prototype=new oe).isPureReactComponent=!0,Hn.prototype.shouldComponentUpdate=function(n,t){return Vn(this.props,n)||Vn(this.state,t)};var Gn=z.__b;z.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),Gn&&Gn(n)};var Lo=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911;function jt(n){function t(o){var r=Ut({},o);return delete r.ref,n(r,o.ref||null)}return t.$$typeof=Lo,t.render=n,t.prototype.isReactComponent=t.__f=!0,t.displayName="ForwardRef("+(n.displayName||n.name)+")",t}var Ao=z.__e;z.__e=function(n,t,o,r){if(n.then){for(var s,i=t;i=i.__;)if((s=i.__c)&&s.__c)return t.__e==null&&(t.__e=o.__e,t.__k=o.__k),s.__c(n,t)}Ao(n,t,o,r)};var qn=z.unmount;function Vt(n,t,o){return n&&(n.__c&&n.__c.__H&&(n.__c.__H.__.forEach(function(r){typeof r.__c=="function"&&r.__c()}),n.__c.__H=null),(n=Ut({},n)).__c!=null&&(n.__c.__P===o&&(n.__c.__P=t),n.__c.__e=!0,n.__c=null),n.__k=n.__k&&n.__k.map(function(r){return Vt(r,t,o)})),n}function Ht(n,t,o){return n&&o&&(n.__v=null,n.__k=n.__k&&n.__k.map(function(r){return Ht(r,t,o)}),n.__c&&n.__c.__P===t&&(n.__e&&o.appendChild(n.__e),n.__c.__e=!0,n.__c.__P=o)),n}function nn(){this.__u=0,this.o=null,this.__b=null}function Gt(n){var t=n.__&&n.__.__c;return t&&t.__a&&t.__a(n)}function $e(){this.i=null,this.l=null}z.unmount=function(n){var t=n.__c;t&&(t.__z=!0),t&&t.__R&&t.__R(),t&&32&n.__u&&(n.type=null),qn&&qn(n)},(nn.prototype=new oe).__c=function(n,t){var o=t.__c,r=this;r.o==null&&(r.o=[]),r.o.push(o);var s=Gt(r.__v),i=!1,a=function(){i||r.__z||(i=!0,o.__R=null,s?s(l):l())};o.__R=a;var c=o.__P;o.__P=null;var l=function(){if(!--r.__u){if(r.state.__a){var p=r.state.__a;r.__v.__k[0]=Ht(p,p.__c.__P,p.__c.__O)}var d;for(r.setState({__a:r.__b=null});d=r.o.pop();)d.__P=c,d.forceUpdate()}};r.__u++||32&t.__u||r.setState({__a:r.__b=r.__v.__k[0]}),n.then(a,a)},nn.prototype.componentWillUnmount=function(){this.o=[]},nn.prototype.render=function(n,t){var o=this.__v;if(!o.__m){for(var r=o;r.__;)r=r.__;r=r.__m||(r.__m=[0,0]),o.__m=[r[1]++,0]}if(this.__b){if(o.__k){var s=document.createElement("div"),i=o.__k[0].__c;o.__k[0]=Vt(this.__b,s,i.__O=i.__P)}this.__b=null}var a=t.__a&&le(Z,null,n.fallback);return a&&(a.__u&=-33),[le(Z,null,t.__a?null:n.children),a]};var Fn=function(n,t,o){if(++o[1]===o[0]&&n.l.delete(t),n.props.revealOrder&&(n.props.revealOrder[0]!=="t"||!n.l.size))for(o=n.i;o;){for(;o.length>3;)o.pop()();if(o[1]<o[0])break;n.i=o=o[2]}};function $o(n){return this.getChildContext=function(){return n.context},n.children}function Bo(n){var t=this,o=n.v;if(t.componentWillUnmount=function(){cn(null,t.h),t.h=null,t.v=null},t.v&&t.v!==o&&t.componentWillUnmount(),!t.h){for(var r=t.__v;r!==null&&!r.__m&&r.__!==null;)r=r.__;t.v=o,t.h={nodeType:1,parentNode:o,childNodes:[],__k:{__m:r.__m},contains:function(){return!0},namespaceURI:o.namespaceURI,insertBefore:function(s,i){this.childNodes.push(s),t.v.insertBefore(s,i)},removeChild:function(s){this.childNodes.splice(this.childNodes.indexOf(s)>>>1,1),t.v.removeChild(s)}}}cn(le($o,{context:t.context},n.__v),t.h)}function qt(n,t){var o=le(Bo,{__v:n,v:t});return o.containerInfo=t,o}($e.prototype=new oe).__a=function(n){var t=this,o=Gt(t.__v),r=t.l.get(n);return r[0]++,function(s){var i=function(){t.props.revealOrder?(r.push(s),Fn(t,n,r)):s()};o?o(i):i()}},$e.prototype.render=function(n){this.i=null,this.l=new Map;var t=He(n.children);n.revealOrder&&n.revealOrder[0]==="b"&&t.reverse();for(var o=t.length;o--;)this.l.set(t[o],this.i=[1,0,this.i]);return n.children},$e.prototype.componentDidUpdate=$e.prototype.componentDidMount=function(){var n=this;this.l.forEach(function(t,o){Fn(n,o,t)})};var No=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.element")||60103,Do=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,Ro=/^on(Ani|Tra|Tou|BeforeInp|Compo)/,Oo=/[A-Z0-9]/g,zo=typeof document<"u",Uo=function(n){return(typeof Symbol<"u"&&typeof Symbol()=="symbol"?/fil|che|rad/:/fil|che|ra/).test(n)};oe.prototype.isReactComponent=!0,["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(n){Object.defineProperty(oe.prototype,n,{configurable:!0,get:function(){return this["UNSAFE_"+n]},set:function(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t})}})});var Kn=z.event;z.event=function(n){return Kn&&(n=Kn(n)),n.persist=function(){},n.isPropagationStopped=function(){return this.cancelBubble},n.isDefaultPrevented=function(){return this.defaultPrevented},n.nativeEvent=n};var jo={configurable:!0,get:function(){return this.class}},Wn=z.vnode;z.vnode=function(n){typeof n.type=="string"&&(function(t){var o=t.props,r=t.type,s={},i=r.indexOf("-")==-1;for(var a in o){var c=o[a];if(!(a==="value"&&"defaultValue"in o&&c==null||zo&&a==="children"&&r==="noscript"||a==="class"||a==="className")){var l=a.toLowerCase();a==="defaultValue"&&"value"in o&&o.value==null?a="value":a==="download"&&c===!0?c="":l==="translate"&&c==="no"?c=!1:l[0]==="o"&&l[1]==="n"?l==="ondoubleclick"?a="ondblclick":l!=="onchange"||r!=="input"&&r!=="textarea"||Uo(o.type)?l==="onfocus"?a="onfocusin":l==="onblur"?a="onfocusout":Ro.test(a)&&(a=l):l=a="oninput":i&&Do.test(a)?a=a.replace(Oo,"-$&").toLowerCase():c===null&&(c=void 0),l==="oninput"&&s[a=l]&&(a="oninputCapture"),s[a]=c}}r=="select"&&(s.multiple&&Array.isArray(s.value)&&(s.value=He(o.children).forEach(function(p){p.props.selected=s.value.indexOf(p.props.value)!=-1})),s.defaultValue!=null&&(s.value=He(o.children).forEach(function(p){p.props.selected=s.multiple?s.defaultValue.indexOf(p.props.value)!=-1:s.defaultValue==p.props.value}))),o.class&&!o.className?(s.class=o.class,Object.defineProperty(s,"className",jo)):o.className&&(s.class=s.className=o.className),t.props=s})(n),n.$$typeof=No,Wn&&Wn(n)};var Jn=z.__r;z.__r=function(n){Jn&&Jn(n),n.__c};var Yn=z.diffed;z.diffed=function(n){Yn&&Yn(n);var t=n.props,o=n.__e;o!=null&&n.type==="textarea"&&"value"in t&&t.value!==o.value&&(o.value=t.value==null?"":t.value)};/**
|
|
2
|
-
* @license lucide-react v0.511.0 - ISC
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the ISC license.
|
|
5
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/const Vo=n=>n.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Ho=n=>n.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,o,r)=>r?r.toUpperCase():o.toLowerCase()),Zn=n=>{const t=Ho(n);return t.charAt(0).toUpperCase()+t.slice(1)},Ft=(...n)=>n.filter((t,o,r)=>!!t&&t.trim()!==""&&r.indexOf(t)===o).join(" ").trim(),Go=n=>{for(const t in n)if(t.startsWith("aria-")||t==="role"||t==="title")return!0};/**
|
|
7
|
-
* @license lucide-react v0.511.0 - ISC
|
|
8
|
-
*
|
|
9
|
-
* This source code is licensed under the ISC license.
|
|
10
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
-
*/var qo={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
|
|
12
|
-
* @license lucide-react v0.511.0 - ISC
|
|
13
|
-
*
|
|
14
|
-
* This source code is licensed under the ISC license.
|
|
15
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
16
|
-
*/const Fo=jt(({color:n="currentColor",size:t=24,strokeWidth:o=2,absoluteStrokeWidth:r,className:s="",children:i,iconNode:a,...c},l)=>le("svg",{ref:l,...qo,width:t,height:t,stroke:n,strokeWidth:r?Number(o)*24/Number(t):o,className:Ft("lucide",s),...!i&&!Go(c)&&{"aria-hidden":"true"},...c},[...a.map(([p,d])=>le(p,d)),...Array.isArray(i)?i:[i]]));/**
|
|
17
|
-
* @license lucide-react v0.511.0 - ISC
|
|
18
|
-
*
|
|
19
|
-
* This source code is licensed under the ISC license.
|
|
20
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
21
|
-
*/const E=(n,t)=>{const o=jt(({className:r,...s},i)=>le(Fo,{ref:i,iconNode:t,className:Ft(`lucide-${Vo(Zn(n))}`,`lucide-${n}`,r),...s}));return o.displayName=Zn(n),o};/**
|
|
22
|
-
* @license lucide-react v0.511.0 - ISC
|
|
23
|
-
*
|
|
24
|
-
* This source code is licensed under the ISC license.
|
|
25
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
26
|
-
*/const Ko=[["path",{d:"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8",key:"mg9rjx"}]],Wo=E("bold",Ko);/**
|
|
27
|
-
* @license lucide-react v0.511.0 - ISC
|
|
28
|
-
*
|
|
29
|
-
* This source code is licensed under the ISC license.
|
|
30
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
31
|
-
*/const Jo=[["path",{d:"M2 16V4a2 2 0 0 1 2-2h11",key:"spzkk5"}],["path",{d:"M22 18H11a2 2 0 1 0 0 4h10.5a.5.5 0 0 0 .5-.5v-15a.5.5 0 0 0-.5-.5H11a2 2 0 0 0-2 2v12",key:"1wz07i"}],["path",{d:"M5 14H4a2 2 0 1 0 0 4h1",key:"16gqf9"}]],Yo=E("book-copy",Jo);/**
|
|
32
|
-
* @license lucide-react v0.511.0 - ISC
|
|
33
|
-
*
|
|
34
|
-
* This source code is licensed under the ISC license.
|
|
35
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
36
|
-
*/const Zo=[["path",{d:"M12 7v14",key:"1akyts"}],["path",{d:"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",key:"ruj8y"}]],Xo=E("book-open",Zo);/**
|
|
37
|
-
* @license lucide-react v0.511.0 - ISC
|
|
38
|
-
*
|
|
39
|
-
* This source code is licensed under the ISC license.
|
|
40
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
41
|
-
*/const Qo=[["path",{d:"M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z",key:"1b4qmf"}],["path",{d:"M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2",key:"i71pzd"}],["path",{d:"M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2",key:"10jefs"}],["path",{d:"M10 6h4",key:"1itunk"}],["path",{d:"M10 10h4",key:"tcdvrf"}],["path",{d:"M10 14h4",key:"kelpxr"}],["path",{d:"M10 18h4",key:"1ulq68"}]],er=E("building-2",Qo);/**
|
|
42
|
-
* @license lucide-react v0.511.0 - ISC
|
|
43
|
-
*
|
|
44
|
-
* This source code is licensed under the ISC license.
|
|
45
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
46
|
-
*/const nr=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]],tr=E("calendar",nr);/**
|
|
47
|
-
* @license lucide-react v0.511.0 - ISC
|
|
48
|
-
*
|
|
49
|
-
* This source code is licensed under the ISC license.
|
|
50
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
51
|
-
*/const or=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],_e=E("check",or);/**
|
|
52
|
-
* @license lucide-react v0.511.0 - ISC
|
|
53
|
-
*
|
|
54
|
-
* This source code is licensed under the ISC license.
|
|
55
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
56
|
-
*/const rr=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],Kt=E("chevron-down",rr);/**
|
|
57
|
-
* @license lucide-react v0.511.0 - ISC
|
|
58
|
-
*
|
|
59
|
-
* This source code is licensed under the ISC license.
|
|
60
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
61
|
-
*/const ir=[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]],qe=E("chevron-left",ir);/**
|
|
62
|
-
* @license lucide-react v0.511.0 - ISC
|
|
63
|
-
*
|
|
64
|
-
* This source code is licensed under the ISC license.
|
|
65
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
66
|
-
*/const sr=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],Fe=E("chevron-right",sr);/**
|
|
67
|
-
* @license lucide-react v0.511.0 - ISC
|
|
68
|
-
*
|
|
69
|
-
* This source code is licensed under the ISC license.
|
|
70
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
71
|
-
*/const ar=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],lr=E("chevron-up",ar);/**
|
|
72
|
-
* @license lucide-react v0.511.0 - ISC
|
|
73
|
-
*
|
|
74
|
-
* This source code is licensed under the ISC license.
|
|
75
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
76
|
-
*/const cr=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],dr=E("circle-alert",cr);/**
|
|
77
|
-
* @license lucide-react v0.511.0 - ISC
|
|
78
|
-
*
|
|
79
|
-
* This source code is licensed under the ISC license.
|
|
80
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
81
|
-
*/const hr=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1",key:"tgr4d6"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2",key:"116196"}],["path",{d:"M12 11h4",key:"1jrz19"}],["path",{d:"M12 16h4",key:"n85exb"}],["path",{d:"M8 11h.01",key:"1dfujw"}],["path",{d:"M8 16h.01",key:"18s6g9"}]],pr=E("clipboard-list",hr);/**
|
|
82
|
-
* @license lucide-react v0.511.0 - ISC
|
|
83
|
-
*
|
|
84
|
-
* This source code is licensed under the ISC license.
|
|
85
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
86
|
-
*/const ur=[["path",{d:"m18 16 4-4-4-4",key:"1inbqp"}],["path",{d:"m6 8-4 4 4 4",key:"15zrgr"}],["path",{d:"m14.5 4-5 16",key:"e7oirm"}]],gr=E("code-xml",ur);/**
|
|
87
|
-
* @license lucide-react v0.511.0 - ISC
|
|
88
|
-
*
|
|
89
|
-
* This source code is licensed under the ISC license.
|
|
90
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
91
|
-
*/const mr=[["path",{d:"M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5",key:"laymnq"}],["path",{d:"M8.5 8.5v.01",key:"ue8clq"}],["path",{d:"M16 15.5v.01",key:"14dtrp"}],["path",{d:"M12 12v.01",key:"u5ubse"}],["path",{d:"M11 17v.01",key:"1hyl5a"}],["path",{d:"M7 14v.01",key:"uct60s"}]],fr=E("cookie",mr);/**
|
|
92
|
-
* @license lucide-react v0.511.0 - ISC
|
|
93
|
-
*
|
|
94
|
-
* This source code is licensed under the ISC license.
|
|
95
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
96
|
-
*/const yr=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],Je=E("copy",yr);/**
|
|
97
|
-
* @license lucide-react v0.511.0 - ISC
|
|
98
|
-
*
|
|
99
|
-
* This source code is licensed under the ISC license.
|
|
100
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
101
|
-
*/const br=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],Xn=E("download",br);/**
|
|
102
|
-
* @license lucide-react v0.511.0 - ISC
|
|
103
|
-
*
|
|
104
|
-
* This source code is licensed under the ISC license.
|
|
105
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
106
|
-
*/const xr=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],vr=E("external-link",xr);/**
|
|
107
|
-
* @license lucide-react v0.511.0 - ISC
|
|
108
|
-
*
|
|
109
|
-
* This source code is licensed under the ISC license.
|
|
110
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
111
|
-
*/const _r=[["path",{d:"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",key:"ct8e1f"}],["path",{d:"M14.084 14.158a3 3 0 0 1-4.242-4.242",key:"151rxh"}],["path",{d:"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",key:"13bj9a"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]],hn=E("eye-off",_r);/**
|
|
112
|
-
* @license lucide-react v0.511.0 - ISC
|
|
113
|
-
*
|
|
114
|
-
* This source code is licensed under the ISC license.
|
|
115
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
116
|
-
*/const kr=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],pn=E("eye",kr);/**
|
|
117
|
-
* @license lucide-react v0.511.0 - ISC
|
|
118
|
-
*
|
|
119
|
-
* This source code is licensed under the ISC license.
|
|
120
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
121
|
-
*/const wr=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],Wt=E("globe",wr);/**
|
|
122
|
-
* @license lucide-react v0.511.0 - ISC
|
|
123
|
-
*
|
|
124
|
-
* This source code is licensed under the ISC license.
|
|
125
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
126
|
-
*/const Cr=[["line",{x1:"19",x2:"10",y1:"4",y2:"4",key:"15jd3p"}],["line",{x1:"14",x2:"5",y1:"20",y2:"20",key:"bu0au3"}],["line",{x1:"15",x2:"9",y1:"4",y2:"20",key:"uljnxc"}]],Sr=E("italic",Cr);/**
|
|
127
|
-
* @license lucide-react v0.511.0 - ISC
|
|
128
|
-
*
|
|
129
|
-
* This source code is licensed under the ISC license.
|
|
130
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
131
|
-
*/const Tr=[["path",{d:"m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4",key:"g0fldk"}],["path",{d:"m21 2-9.6 9.6",key:"1j0ho8"}],["circle",{cx:"7.5",cy:"15.5",r:"5.5",key:"yqb3hr"}]],Ir=E("key",Tr);/**
|
|
132
|
-
* @license lucide-react v0.511.0 - ISC
|
|
133
|
-
*
|
|
134
|
-
* This source code is licensed under the ISC license.
|
|
135
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
136
|
-
*/const Mr=[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]],Pr=E("layout-dashboard",Mr);/**
|
|
137
|
-
* @license lucide-react v0.511.0 - ISC
|
|
138
|
-
*
|
|
139
|
-
* This source code is licensed under the ISC license.
|
|
140
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
141
|
-
*/const Er=[["path",{d:"M9 17H7A5 5 0 0 1 7 7h2",key:"8i5ue5"}],["path",{d:"M15 7h2a5 5 0 1 1 0 10h-2",key:"1b9ql8"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12",key:"1jonct"}]],Lr=E("link-2",Er);/**
|
|
142
|
-
* @license lucide-react v0.511.0 - ISC
|
|
143
|
-
*
|
|
144
|
-
* This source code is licensed under the ISC license.
|
|
145
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
146
|
-
*/const Ar=[["path",{d:"M10 12h11",key:"6m4ad9"}],["path",{d:"M10 18h11",key:"11hvi2"}],["path",{d:"M10 6h11",key:"c7qv1k"}],["path",{d:"M4 10h2",key:"16xx2s"}],["path",{d:"M4 6h1v4",key:"cnovpq"}],["path",{d:"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1",key:"m9a95d"}]],$r=E("list-ordered",Ar);/**
|
|
147
|
-
* @license lucide-react v0.511.0 - ISC
|
|
148
|
-
*
|
|
149
|
-
* This source code is licensed under the ISC license.
|
|
150
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
151
|
-
*/const Br=[["path",{d:"M3 12h.01",key:"nlz23k"}],["path",{d:"M3 18h.01",key:"1tta3j"}],["path",{d:"M3 6h.01",key:"1rqtza"}],["path",{d:"M8 12h13",key:"1za7za"}],["path",{d:"M8 18h13",key:"1lx6n3"}],["path",{d:"M8 6h13",key:"ik3vkj"}]],Nr=E("list",Br);/**
|
|
152
|
-
* @license lucide-react v0.511.0 - ISC
|
|
153
|
-
*
|
|
154
|
-
* This source code is licensed under the ISC license.
|
|
155
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
156
|
-
*/const Dr=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],Rr=E("lock",Dr);/**
|
|
157
|
-
* @license lucide-react v0.511.0 - ISC
|
|
158
|
-
*
|
|
159
|
-
* This source code is licensed under the ISC license.
|
|
160
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
161
|
-
*/const Or=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],Jt=E("log-out",Or);/**
|
|
162
|
-
* @license lucide-react v0.511.0 - ISC
|
|
163
|
-
*
|
|
164
|
-
* This source code is licensed under the ISC license.
|
|
165
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
166
|
-
*/const zr=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"m21 3-7 7",key:"1l2asr"}],["path",{d:"m3 21 7-7",key:"tjx5ai"}],["path",{d:"M9 21H3v-6",key:"wtvkvv"}]],Ur=E("maximize-2",zr);/**
|
|
167
|
-
* @license lucide-react v0.511.0 - ISC
|
|
168
|
-
*
|
|
169
|
-
* This source code is licensed under the ISC license.
|
|
170
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
171
|
-
*/const jr=[["path",{d:"m14 10 7-7",key:"oa77jy"}],["path",{d:"M20 10h-6V4",key:"mjg0md"}],["path",{d:"m3 21 7-7",key:"tjx5ai"}],["path",{d:"M4 14h6v6",key:"rmj7iw"}]],Vr=E("minimize-2",jr);/**
|
|
172
|
-
* @license lucide-react v0.511.0 - ISC
|
|
173
|
-
*
|
|
174
|
-
* This source code is licensed under the ISC license.
|
|
175
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
176
|
-
*/const Hr=[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]],Gr=E("moon",Hr);/**
|
|
177
|
-
* @license lucide-react v0.511.0 - ISC
|
|
178
|
-
*
|
|
179
|
-
* This source code is licensed under the ISC license.
|
|
180
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
181
|
-
*/const qr=[["path",{d:"M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z",key:"e79jfc"}],["circle",{cx:"13.5",cy:"6.5",r:".5",fill:"currentColor",key:"1okk4w"}],["circle",{cx:"17.5",cy:"10.5",r:".5",fill:"currentColor",key:"f64h9f"}],["circle",{cx:"6.5",cy:"12.5",r:".5",fill:"currentColor",key:"qy21gx"}],["circle",{cx:"8.5",cy:"7.5",r:".5",fill:"currentColor",key:"fotxhn"}]],Fr=E("palette",qr);/**
|
|
182
|
-
* @license lucide-react v0.511.0 - ISC
|
|
183
|
-
*
|
|
184
|
-
* This source code is licensed under the ISC license.
|
|
185
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
186
|
-
*/const Kr=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]],Wr=E("pencil",Kr);/**
|
|
187
|
-
* @license lucide-react v0.511.0 - ISC
|
|
188
|
-
*
|
|
189
|
-
* This source code is licensed under the ISC license.
|
|
190
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
191
|
-
*/const Jr=[["path",{d:"M12 22v-5",key:"1ega77"}],["path",{d:"M9 8V2",key:"14iosj"}],["path",{d:"M15 8V2",key:"18g5xt"}],["path",{d:"M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z",key:"osxo6l"}]],Yr=E("plug",Jr);/**
|
|
192
|
-
* @license lucide-react v0.511.0 - ISC
|
|
193
|
-
*
|
|
194
|
-
* This source code is licensed under the ISC license.
|
|
195
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
196
|
-
*/const Zr=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],Xr=E("plus",Zr);/**
|
|
197
|
-
* @license lucide-react v0.511.0 - ISC
|
|
198
|
-
*
|
|
199
|
-
* This source code is licensed under the ISC license.
|
|
200
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
201
|
-
*/const Qr=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],ei=E("refresh-cw",Qr);/**
|
|
202
|
-
* @license lucide-react v0.511.0 - ISC
|
|
203
|
-
*
|
|
204
|
-
* This source code is licensed under the ISC license.
|
|
205
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
206
|
-
*/const ni=[["path",{d:"M4 10a7.31 7.31 0 0 0 10 10Z",key:"1fzpp3"}],["path",{d:"m9 15 3-3",key:"88sc13"}],["path",{d:"M17 13a6 6 0 0 0-6-6",key:"15cc6u"}],["path",{d:"M21 13A10 10 0 0 0 11 3",key:"11nf8s"}]],ti=E("satellite-dish",ni);/**
|
|
207
|
-
* @license lucide-react v0.511.0 - ISC
|
|
208
|
-
*
|
|
209
|
-
* This source code is licensed under the ISC license.
|
|
210
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
211
|
-
*/const oi=[["path",{d:"m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z",key:"7g6ntu"}],["path",{d:"m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z",key:"ijws7r"}],["path",{d:"M7 21h10",key:"1b0cd5"}],["path",{d:"M12 3v18",key:"108xh3"}],["path",{d:"M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2",key:"3gwbw2"}]],ri=E("scale",oi);/**
|
|
212
|
-
* @license lucide-react v0.511.0 - ISC
|
|
213
|
-
*
|
|
214
|
-
* This source code is licensed under the ISC license.
|
|
215
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
216
|
-
*/const ii=[["path",{d:"M15 12h-5",key:"r7krc0"}],["path",{d:"M15 8h-5",key:"1khuty"}],["path",{d:"M19 17V5a2 2 0 0 0-2-2H4",key:"zz82l3"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3",key:"1ph1d7"}]],si=E("scroll-text",ii);/**
|
|
217
|
-
* @license lucide-react v0.511.0 - ISC
|
|
218
|
-
*
|
|
219
|
-
* This source code is licensed under the ISC license.
|
|
220
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
221
|
-
*/const ai=[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],li=E("settings",ai);/**
|
|
222
|
-
* @license lucide-react v0.511.0 - ISC
|
|
223
|
-
*
|
|
224
|
-
* This source code is licensed under the ISC license.
|
|
225
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
226
|
-
*/const ci=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Yt=E("shield-check",ci);/**
|
|
227
|
-
* @license lucide-react v0.511.0 - ISC
|
|
228
|
-
*
|
|
229
|
-
* This source code is licensed under the ISC license.
|
|
230
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
231
|
-
*/const di=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],hi=E("shield",di);/**
|
|
232
|
-
* @license lucide-react v0.511.0 - ISC
|
|
233
|
-
*
|
|
234
|
-
* This source code is licensed under the ISC license.
|
|
235
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
236
|
-
*/const pi=[["line",{x1:"21",x2:"14",y1:"4",y2:"4",key:"obuewd"}],["line",{x1:"10",x2:"3",y1:"4",y2:"4",key:"1q6298"}],["line",{x1:"21",x2:"12",y1:"12",y2:"12",key:"1iu8h1"}],["line",{x1:"8",x2:"3",y1:"12",y2:"12",key:"ntss68"}],["line",{x1:"21",x2:"16",y1:"20",y2:"20",key:"14d8ph"}],["line",{x1:"12",x2:"3",y1:"20",y2:"20",key:"m0wm8r"}],["line",{x1:"14",x2:"14",y1:"2",y2:"6",key:"14e1ph"}],["line",{x1:"8",x2:"8",y1:"10",y2:"14",key:"1i6ji0"}],["line",{x1:"16",x2:"16",y1:"18",y2:"22",key:"1lctlv"}]],ui=E("sliders-horizontal",pi);/**
|
|
237
|
-
* @license lucide-react v0.511.0 - ISC
|
|
238
|
-
*
|
|
239
|
-
* This source code is licensed under the ISC license.
|
|
240
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
241
|
-
*/const gi=[["path",{d:"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7",key:"1m0v6g"}],["path",{d:"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z",key:"ohrbg2"}]],xn=E("square-pen",gi);/**
|
|
242
|
-
* @license lucide-react v0.511.0 - ISC
|
|
243
|
-
*
|
|
244
|
-
* This source code is licensed under the ISC license.
|
|
245
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
246
|
-
*/const mi=[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]],fi=E("sun",mi);/**
|
|
247
|
-
* @license lucide-react v0.511.0 - ISC
|
|
248
|
-
*
|
|
249
|
-
* This source code is licensed under the ISC license.
|
|
250
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
251
|
-
*/const yi=[["path",{d:"M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",key:"vktsd0"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor",key:"kqv944"}]],bi=E("tag",yi);/**
|
|
252
|
-
* @license lucide-react v0.511.0 - ISC
|
|
253
|
-
*
|
|
254
|
-
* This source code is licensed under the ISC license.
|
|
255
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
256
|
-
*/const xi=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z",key:"qn84l0"}],["path",{d:"m9.5 14.5 5-5",key:"qviqfa"}]],vi=E("ticket-slash",xi);/**
|
|
257
|
-
* @license lucide-react v0.511.0 - ISC
|
|
258
|
-
*
|
|
259
|
-
* This source code is licensed under the ISC license.
|
|
260
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
261
|
-
*/const _i=[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]],Zt=E("trash-2",_i);/**
|
|
262
|
-
* @license lucide-react v0.511.0 - ISC
|
|
263
|
-
*
|
|
264
|
-
* This source code is licensed under the ISC license.
|
|
265
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
266
|
-
*/const ki=[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}]],vn=E("trash",ki);/**
|
|
267
|
-
* @license lucide-react v0.511.0 - ISC
|
|
268
|
-
*
|
|
269
|
-
* This source code is licensed under the ISC license.
|
|
270
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
271
|
-
*/const wi=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],Ci=E("triangle-alert",wi);/**
|
|
272
|
-
* @license lucide-react v0.511.0 - ISC
|
|
273
|
-
*
|
|
274
|
-
* This source code is licensed under the ISC license.
|
|
275
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
276
|
-
*/const Si=[["path",{d:"m18.84 12.25 1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71",key:"yqzxt4"}],["path",{d:"m5.17 11.75-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71",key:"4qinb0"}],["line",{x1:"8",x2:"8",y1:"2",y2:"5",key:"1041cp"}],["line",{x1:"2",x2:"5",y1:"8",y2:"8",key:"14m1p5"}],["line",{x1:"16",x2:"16",y1:"19",y2:"22",key:"rzdirn"}],["line",{x1:"19",x2:"22",y1:"16",y2:"16",key:"ox905f"}]],Ti=E("unlink",Si);/**
|
|
277
|
-
* @license lucide-react v0.511.0 - ISC
|
|
278
|
-
*
|
|
279
|
-
* This source code is licensed under the ISC license.
|
|
280
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
281
|
-
*/const Ii=[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]],Mi=E("upload",Ii);/**
|
|
282
|
-
* @license lucide-react v0.511.0 - ISC
|
|
283
|
-
*
|
|
284
|
-
* This source code is licensed under the ISC license.
|
|
285
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
286
|
-
*/const Pi=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]],Ei=E("users",Pi);/**
|
|
287
|
-
* @license lucide-react v0.511.0 - ISC
|
|
288
|
-
*
|
|
289
|
-
* This source code is licensed under the ISC license.
|
|
290
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
291
|
-
*/const Li=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Ye=E("x",Li),Qn=[{label:"Dashboard",hash:"#/",Icon:Pr},{type:"group",label:"Consent Banners",baseHash:"#/banners",Icon:vi,children:[{label:"Cookie Templates",hash:"#/banners/cookie-templates",Icon:fr},{label:"UI Templates",hash:"#/banners/ui-templates",Icon:Fr},{label:"Profiles",hash:"#/banners/profiles",Icon:pr}]},{label:"Consents",hash:"#/consents",Icon:Yt},{label:"Visitors",hash:"#/visitors",Icon:Ei},{label:"Users",hash:"#/users",Icon:Ir,perm:"user:view"},{label:"Roles",hash:"#/roles",Icon:hi,perm:"role:view"},{label:"Sites",hash:"#/tenants",Icon:Wt,perm:"settings:update"},{label:"TCF Vendors",hash:"#/vendors",Icon:ti,perm:"consent:view"},{label:"Audit Log",hash:"#/audit",Icon:si,perm:"audit:view"},{type:"group",label:"API",baseHash:"#/api",Icon:Yr,children:[{label:"Config",hash:"#/api/config",Icon:ui},{label:"Endpoint Docs",hash:"#/api/docs",Icon:Xo}]},{label:"Settings",hash:"#/settings",Icon:li}];function Ai({current:n,collapsed:t,onToggle:o}){const{user:r,logout:s}=pe(),[i,a]=g(()=>{const u=new Set;for(const h of Qn)"type"in h&&h.type==="group"&&n.startsWith(h.baseHash)&&u.add(h.baseHash);return u}),c=u=>{a(h=>{const m=new Set(h);return m.has(u)?m.delete(u):m.add(u),m})},l=u=>n===u||u!=="#/"&&n.startsWith(u),p=u=>n.startsWith(u.baseHash),d=u=>!u||(r==null?void 0:r.permissions.includes(u));return e("aside",{class:`${t?"w-14":"w-56"} min-h-screen bg-gray-900 text-gray-100 flex flex-col transition-[width] duration-200 ease-in-out overflow-hidden shrink-0 border-r border-gray-700`,children:[e("div",{class:"px-3 py-5 border-b border-gray-700 flex items-center gap-2",children:t?e("div",{class:"w-full flex justify-center",children:e("img",{src:"/icon.svg",alt:"Consenti",class:"h-7 w-7"})}):e("div",{class:"min-w-0",children:[e("img",{src:"/logo-dark.svg",alt:"Consenti",class:"h-7 w-auto"}),e("p",{class:"text-xs text-gray-400 mt-0.5",children:"Admin Dashboard"})]})}),e("nav",{class:"flex-1 py-4 overflow-y-auto",children:Qn.filter(u=>d(u.perm)).map(u=>{if("type"in u&&u.type==="group"){const k=!t&&i.has(u.baseHash),y=p(u),b=u.children.filter(v=>d(v.perm));return b.length===0?null:e("div",{children:[e("button",{type:"button",onClick:()=>{t?window.location.hash=b[0].hash:c(u.baseHash)},title:t?u.label:void 0,class:`w-full flex items-center gap-3 px-3 py-2.5 text-sm transition-colors ${t?"justify-center":"justify-between"} ${y?"text-white bg-gray-800":"text-gray-300 hover:bg-gray-800 hover:text-white"}`,children:[e("div",{class:`flex items-center gap-3 ${t?"":"min-w-0"}`,children:[e(u.Icon,{size:16,className:"shrink-0"}),!t&&e("span",{class:"truncate",children:u.label})]}),!t&&e(Kt,{size:13,className:`shrink-0 text-gray-500 transition-transform ${k?"rotate-180":""}`})]}),k&&e("div",{class:"bg-gray-950/30",children:b.map(v=>{const w=l(v.hash);return e("a",{href:v.hash,class:`flex items-center gap-3 pl-8 pr-3 py-2 text-sm transition-colors ${w?"bg-blue-600 text-white":"text-gray-400 hover:bg-gray-800 hover:text-white"}`,children:[e(v.Icon,{size:14,className:"shrink-0"}),v.label]},v.hash)})})]},u.baseHash)}const h=u,m=l(h.hash);return e("a",{href:h.hash,title:t?h.label:void 0,class:`flex items-center gap-3 px-3 py-2.5 text-sm transition-colors ${t?"justify-center":""} ${m?"bg-blue-600 text-white":"text-gray-300 hover:bg-gray-800 hover:text-white"}`,children:[e(h.Icon,{size:16,className:"shrink-0"}),!t&&h.label]},h.hash)})}),e("div",{class:`px-3 py-4 border-t border-gray-700 ${t?"flex justify-center":""}`,children:[!t&&e(Z,{children:e("p",{class:"text-xs text-gray-400 truncate",children:r==null?void 0:r.email})}),t&&e("button",{onClick:s,title:"Sign out",class:"w-8 h-8 flex items-center justify-center rounded hover:bg-gray-700 text-gray-400 hover:text-white transition-colors",children:e(Jt,{size:16})})]})]})}const et=Gr,nt=fi;function $i({collapsed:n}){const{theme:t,toggle:o}=Rt(),r=t==="dark";return n?e("button",{onClick:o,title:r?"Switch to light mode":"Switch to dark mode",class:"w-8 h-8 flex items-center justify-center rounded hover:bg-gray-700 text-gray-400 hover:text-white transition-colors",children:r?e(et,{size:16,className:"text-cyan-400"}):e(nt,{size:16,className:"text-gray-400"})}):e("button",{onClick:o,title:r?"Switch to light mode":"Switch to dark mode",class:`relative flex items-center w-12 h-6 rounded-full px-0.5 transition-all duration-300 ${r?"bg-gray-900 ring-2 ring-cyan-400":"bg-gray-600"}`,children:e("span",{class:`w-5 h-5 rounded-full flex items-center justify-center transition-all duration-300 ${r?"translate-x-6 bg-gray-800":"translate-x-0 bg-white shadow"}`,children:r?e(et,{size:11,className:"text-cyan-400"}):e(nt,{size:11,className:"text-amber-500"})})})}const Bi=Jt,Ni=Fe,Di=qe;function F({children:n,title:t,current:o}){const[r,s]=g(!1),i=()=>s(c=>!c),{logout:a}=pe();return e("div",{class:"flex min-h-screen dark:bg-gray-900",children:[e("div",{class:"relative shrink-0",children:[e(Ai,{current:o,collapsed:r,onToggle:i}),e("button",{onClick:i,title:r?"Expand sidebar":"Collapse sidebar",class:"absolute top-5 -right-3.5 z-20 w-7 h-7 flex items-center justify-center bg-gray-800 border border-gray-600 rounded-full text-gray-300 hover:text-white hover:bg-gray-700 shadow-md transition-colors",children:r?e(Ni,{size:13}):e(Di,{size:13})})]}),e("main",{class:"flex-1 overflow-auto min-w-0",children:[e("div",{class:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 flex items-center justify-between min-h-[56px]",children:[t?e("h1",{class:"text-xl font-semibold text-gray-800 dark:text-gray-100",children:t}):e("span",{}),e("div",{class:"flex items-center gap-3",children:[e($i,{}),e("button",{onClick:a,title:"Sign out",class:"flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white transition-colors",children:[e(Bi,{size:14}),"Sign out"]})]})]}),e("div",{class:"p-6",children:n})]})]})}const Be={overview:()=>P("/stats/overview"),categories:()=>P("/stats/categories"),timeline:(n=30)=>P(`/stats/timeline?days=${n}`),countries:()=>P("/stats/countries"),gpc:()=>P("/stats/gpc")};function Ne({label:n,value:t,sub:o}){return e("div",{class:"bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5",children:[e("p",{class:"text-sm text-gray-500 dark:text-gray-400",children:n}),e("p",{class:"text-3xl font-bold text-gray-900 dark:text-gray-100 mt-1",children:t}),o&&e("p",{class:"text-xs text-gray-400 dark:text-gray-500 mt-1",children:o})]})}function Ri({data:n}){if(n.length===0)return e("p",{class:"text-sm text-gray-400",children:"No data."});const t=Math.max(...n.map(u=>u.count),1),o=600,r=120,s=24,i=o-s*2,a=r-s*2,c=i/Math.max(n.length-1,1),l=n.map((u,h)=>({x:s+h*c,y:s+a-u.count/t*a,label:u.date.slice(5),count:u.count})),p=l.map((u,h)=>`${h===0?"M":"L"} ${u.x} ${u.y}`).join(" "),d=`${p} L ${l[l.length-1].x} ${s+a} L ${l[0].x} ${s+a} Z`;return e("svg",{viewBox:`0 0 ${o} ${r}`,class:"w-full",role:"img","aria-label":"Consent timeline chart",children:[e("defs",{children:e("linearGradient",{id:"tl-grad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e("stop",{offset:"0%","stop-color":"#6366f1","stop-opacity":"0.3"}),e("stop",{offset:"100%","stop-color":"#6366f1","stop-opacity":"0.02"})]})}),e("path",{d,fill:"url(#tl-grad)"}),e("path",{d:p,fill:"none",stroke:"#6366f1","stroke-width":"2","stroke-linejoin":"round"}),l.map((u,h)=>e("g",{children:[e("circle",{cx:u.x,cy:u.y,r:"3",fill:"#6366f1"}),h%Math.max(Math.floor(n.length/6),1)===0&&e("text",{x:u.x,y:r-4,"text-anchor":"middle","font-size":"8",fill:"#9ca3af",children:u.label})]},h))]})}function Oi({data:n}){if(n.length===0)return e("p",{class:"text-sm text-gray-400",children:"No country data."});const t=n.slice(0,8),o=Math.max(...t.map(r=>r.count),1);return e("div",{class:"space-y-2",children:t.map(r=>e("div",{class:"flex items-center gap-2",children:[e("span",{class:"text-xs text-gray-500 w-8 text-right",children:r.country}),e("div",{class:"flex-1 bg-gray-100 rounded h-3 overflow-hidden",children:e("div",{class:"h-full bg-indigo-500 rounded",style:{width:`${r.count/o*100}%`}})}),e("span",{class:"text-xs text-gray-600 w-8",children:r.count})]},r.country))})}function zi({data:n}){const o=2*Math.PI*36,r=n.rate/100*o;return e("div",{class:"flex items-center gap-4",children:[e("svg",{width:"88",height:"88",viewBox:"0 0 88 88",children:[e("circle",{cx:"44",cy:"44",r:36,fill:"none",stroke:"#e5e7eb","stroke-width":"8"}),e("circle",{cx:"44",cy:"44",r:36,fill:"none",stroke:"#6366f1","stroke-width":"8","stroke-dasharray":`${r} ${o}`,"stroke-linecap":"round",transform:"rotate(-90 44 44)"}),e("text",{x:"44",y:"49","text-anchor":"middle","font-size":"16","font-weight":"bold",fill:"currentColor",class:"text-gray-800 dark:text-gray-100",children:[n.rate,"%"]})]}),e("div",{children:[e("p",{class:"text-sm font-medium text-gray-700",children:"GPC Detection Rate"}),e("p",{class:"text-xs text-gray-500",children:[n.detected," of ",n.total," consents"]})]})]})}function tt({current:n}){const[t,o]=g(null),[r,s]=g([]),[i,a]=g([]),[c,l]=g(null),[p,d]=g(!0);return j(()=>{Promise.all([Be.overview().then(o).catch(()=>{}),Be.timeline(30).then(s).catch(()=>{}),Be.countries().then(a).catch(()=>{}),Be.gpc().then(l).catch(()=>{})]).finally(()=>d(!1))},[]),e(F,{title:"Dashboard",current:n,children:p?e("p",{class:"text-gray-400 text-sm",children:"Loading…"}):e("div",{class:"space-y-6",children:[t&&e("div",{class:"grid grid-cols-2 lg:grid-cols-4 gap-4",children:[e(Ne,{label:"Total Consents",value:t.totalConsents}),e(Ne,{label:"Total Visitors",value:t.totalVisitors}),e(Ne,{label:"Accepted",value:`${t.acceptedPct}%`,sub:"≥80% cookies granted"}),e(Ne,{label:"Rejected",value:`${t.rejectedPct}%`,sub:"≤20% cookies granted"})]}),e("div",{class:"grid grid-cols-1 lg:grid-cols-2 gap-6",children:[e("div",{class:"bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5",children:[e("p",{class:"text-sm font-medium text-gray-700 dark:text-gray-300 mb-3",children:"Consents (last 30 days)"}),e(Ri,{data:r})]}),e("div",{class:"bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5",children:[e("p",{class:"text-sm font-medium text-gray-700 dark:text-gray-300 mb-3",children:"Top Countries"}),e(Oi,{data:i})]})]}),c&&e("div",{class:"bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5",children:e(zi,{data:c})})]})})}function ke({columns:n,rows:t,keyFn:o,loading:r,emptyText:s="No data"}){return r?e("div",{class:"flex items-center justify-center py-12 text-gray-400 dark:text-gray-500 text-sm",children:"Loading…"}):e("div",{class:"overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800",children:e("table",{class:"w-full text-sm",children:[e("thead",{children:e("tr",{class:"bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700",children:n.map(i=>e("th",{class:"px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide",children:i.label},i.key))})}),e("tbody",{children:t.length===0?e("tr",{children:e("td",{colSpan:n.length,class:"px-4 py-8 text-center text-gray-400 dark:text-gray-500",children:s})}):t.map(i=>e("tr",{class:"border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/40 transition-colors",children:n.map(a=>e("td",{class:"px-4 py-3 text-gray-700 dark:text-gray-300",children:a.render?a.render(i):String(i[a.key]??"")},a.key))},o(i)))})]})})}function be({perm:n,children:t,fallback:o=null}){const{user:r}=pe();return r!=null&&r.permissions.includes(n)?e(Z,{children:t}):e(Z,{children:o})}const ot="confirm-dialog-title";function Ui({open:n,title:t,message:o,confirmLabel:r="Confirm",cancelLabel:s="Cancel",danger:i=!0,onConfirm:a,onCancel:c}){const l=Y(null),p=Y(null);return j(()=>{var u;if(!n)return;(u=l.current)==null||u.focus();const d=h=>{if(h.key==="Escape"){c();return}if(h.key!=="Tab")return;const m=p.current;if(!m)return;const k=Array.from(m.querySelectorAll('button, [href], input, [tabindex]:not([tabindex="-1"])')).filter(v=>!v.hasAttribute("disabled"));if(!k.length)return;const y=k[0],b=k[k.length-1];h.shiftKey?document.activeElement===y&&(h.preventDefault(),b.focus()):document.activeElement===b&&(h.preventDefault(),y.focus())};return document.addEventListener("keydown",d),()=>document.removeEventListener("keydown",d)},[n,c]),n?qt(e("div",{class:"fixed inset-0 z-50 flex items-center justify-center",children:[e("div",{class:"absolute inset-0 bg-black/40",onClick:c,"aria-hidden":"true"}),e("div",{ref:p,role:"dialog","aria-modal":"true","aria-labelledby":ot,class:"relative bg-white rounded-xl shadow-2xl p-6 w-full max-w-sm mx-4",children:[e("div",{class:"flex items-start gap-4",children:[i&&e("div",{class:"shrink-0 w-10 h-10 rounded-full bg-red-100 flex items-center justify-center","aria-hidden":"true",children:e(Ci,{size:20,className:"text-red-600"})}),e("div",{class:"flex-1 min-w-0",children:[e("h3",{id:ot,class:"text-sm font-semibold text-gray-900",children:t}),o&&e("p",{class:"text-sm text-gray-500 mt-1",children:o})]})]}),e("div",{class:"flex justify-end gap-3 mt-5",children:[e("button",{ref:l,type:"button",onClick:c,class:"px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",children:s}),e("button",{type:"button",onClick:a,class:`px-4 py-2 text-sm font-medium rounded-lg transition-colors text-white ${i?"bg-red-600 hover:bg-red-700":"bg-blue-600 hover:bg-blue-700"}`,children:r})]})]})]}),document.body):null}function ue(){const[n,t]=g(null),o=a=>new Promise(c=>t({opts:a,resolve:c})),r=()=>{n==null||n.resolve(!0),t(null)},s=()=>{n==null||n.resolve(!1),t(null)},i=e(Ui,{open:!!n,title:(n==null?void 0:n.opts.title)??"",message:n==null?void 0:n.opts.message,confirmLabel:n==null?void 0:n.opts.confirmLabel,cancelLabel:n==null?void 0:n.opts.cancelLabel,danger:n==null?void 0:n.opts.danger,onConfirm:r,onCancel:s});return{requestConfirm:o,dialog:i}}const ae={list:()=>P("/profiles"),get:n=>P(`/profiles/${n}`),create:n=>P("/profiles",{method:"POST",body:JSON.stringify(n)}),update:(n,t)=>P(`/profiles/${n}`,{method:"PUT",body:JSON.stringify(t)}),delete:n=>P(`/profiles/${n}`,{method:"DELETE"}),previewUrl:n=>`/consenti/api/profiles/preview?draft=${encodeURIComponent(JSON.stringify(n))}`};function ji({current:n}){const[t,o]=g([]),[r,s]=g(!0),[i,a]=g(null),[c,l]=g(null),p=y=>{navigator.clipboard.writeText(y).then(()=>{l(y),setTimeout(()=>l(b=>b===y?null:b),1500)}).catch(()=>{})},{requestConfirm:d,dialog:u}=ue(),h=()=>{ae.list().then(o).catch(()=>{}).finally(()=>s(!1))};j(()=>{h()},[]);const m=async(y,b)=>{await d({title:`Delete "${b}"?`,message:"This profile and all its locale content will be permanently removed."})&&(await ae.delete(y).catch(()=>{}),h())},k=async y=>{if(await d({title:`Copy "${y.name}"?`,message:"This profile and all its locale content will be duplicated."})){a(y.id);try{await ae.create({name:`Copy of ${y.name}`,defaultLocale:y.defaultLocale,profileJson:y.profileJson}),h()}catch{}finally{a(null)}}};return e(F,{title:"DashboardProfiles",current:n,children:[u,e("div",{class:"flex justify-between items-center mb-4",children:[e("p",{class:"text-sm text-gray-500",children:[t.length," profile",t.length!==1?"s":""]}),e(be,{perm:"profile:create",children:e("a",{href:"#/banners/profiles/new",class:"bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors",children:"+ New DashboardProfile"})})]}),e(ke,{loading:r,keyFn:y=>y.id,rows:t,columns:[{key:"name",label:"Name",render:y=>{const b=y;return e("div",{children:[e("div",{class:"font-medium text-sm text-gray-900",children:b.name}),e("div",{class:"flex items-center gap-1 mt-0.5",children:[e("span",{class:"text-xs text-gray-400 font-mono",children:b.id}),e("button",{onClick:v=>{v.stopPropagation(),p(b.id)},class:"text-gray-400 hover:text-gray-600 transition-colors",title:"Copy profile ID",children:c===b.id?e(_e,{size:11,className:"text-green-500"}):e(Je,{size:11})})]})]})}},{key:"defaultLocale",label:"Default Locale"},{key:"version",label:"Version"},{key:"templates",label:"Templates",render:y=>{const b=y.profileJson;return e("div",{class:"text-xs text-gray-500 space-y-0.5",children:[b.cookieTemplateId&&e("div",{children:["Cookie: ",e("span",{class:"font-mono",children:b.cookieTemplateId})]}),b.uiTemplateId&&e("div",{children:["UI: ",e("span",{class:"font-mono",children:b.uiTemplateId})]})]})}},{key:"createdAt",label:"Created",render:y=>new Date(y.createdAt).toLocaleDateString()},{key:"actions",label:"",render:y=>{const b=y;return e("div",{class:"flex gap-3",children:[e("a",{href:`#/profiles/${b.id}`,class:"text-blue-600 hover:underline text-xs",title:"Edit",children:e(xn,{size:14})}),e(be,{perm:"profile:create",children:e("button",{onClick:()=>k(b),disabled:i===b.id,class:"text-gray-500 hover:text-blue-600 hover:underline text-xs disabled:opacity-50 px-2",title:"Copy/Duplicate",children:i===b.id?"Copying…":e(Yo,{size:14})})}),e(be,{perm:"profile:delete",children:e("button",{onClick:()=>m(b.id,b.name),class:"text-red-500 hover:underline text-xs",title:"Delete",children:e(vn,{size:14})})})]})}}],emptyText:"No profiles yet. Create one to get started."})]})}function Vi({locales:n,defaultLocale:t,activeLocale:o,onSelect:r,onAdd:s}){const[i,a]=g(!1),[c,l]=g(""),p=()=>{const d=c.trim().toLowerCase();d&&!n.includes(d)&&(s(d),l(""),a(!1))};return e("div",{class:"flex flex-wrap items-center gap-1 border-b border-gray-200 mb-4 pb-0",children:[n.map(d=>e("button",{onClick:()=>r(d),class:`px-3 py-2 text-sm font-medium border-b-2 transition-colors -mb-px ${d===o?"border-blue-500 text-blue-600":"border-transparent text-gray-500 hover:text-gray-700"}`,children:[d.toUpperCase(),d===t&&e("span",{class:"ml-1 text-xs text-gray-400",children:"(default)"})]},d)),i?e("div",{class:"flex items-center gap-1 ml-2",children:[e("input",{class:"border border-gray-300 rounded px-2 py-1 text-xs w-20",placeholder:"e.g. fr",value:c,onInput:d=>l(d.target.value),onKeyDown:d=>d.key==="Enter"&&p(),autoFocus:!0}),e("button",{onClick:p,class:"text-xs text-blue-600 hover:underline",children:"Add"}),e("button",{onClick:()=>a(!1),class:"text-xs text-gray-400 hover:underline",children:"Cancel"})]}):e("button",{onClick:()=>a(!0),class:"px-2 py-2 text-xs text-gray-400 hover:text-blue-600 transition-colors -mb-px border-b-2 border-transparent",children:"+ Add locale"})]})}function he(n){if(!n||!n.trim())return[];const t=n.trim();if(t.startsWith("["))try{return JSON.parse(t)}catch{}return _n(n)}function Xt(n){return n.length?JSON.stringify(n):""}function ye(n){return n.map(un).join("")}function un(n){return n.t==="text"?Gi(n.v):n.t==="br"?"<br>":n.t==="a"?`<a href="${qi(n.href)}">${n.c.map(un).join("")}</a>`:`<${n.t}>${n.c.map(un).join("")}</${n.t}>`}function _n(n){if(!n||!n.trim())return[];const t=document.createElement("div");return t.innerHTML=n,Qt([...t.childNodes])}function Qt(n){const t=[];for(const o of n)if(o.nodeType===Node.TEXT_NODE){const r=o.textContent??"";r&&t.push({t:"text",v:r})}else o.nodeType===Node.ELEMENT_NODE&&t.push(...Hi(o));return t}function Hi(n){var r;const t=n.tagName.toLowerCase(),o=Qt([...n.childNodes]);switch(t){case"br":return[{t:"br"}];case"b":case"strong":return[{t:"b",c:o}];case"i":case"em":return[{t:"i",c:o}];case"u":return[{t:"u",c:o}];case"ul":return[{t:"ul",c:o}];case"ol":return[{t:"ol",c:o}];case"li":return[{t:"li",c:o}];case"a":return[{t:"a",href:n.getAttribute("href")??"",c:o}];case"p":case"div":case"h1":case"h2":case"h3":case"h4":case"h5":case"h6":return o.length===1&&"c"in o[0]&&(o[0].t==="p"||o[0].t==="ul"||o[0].t==="ol")?o:[{t:"p",c:o}];case"span":{const s=n.style;let i=o;return(r=s.textDecoration)!=null&&r.includes("underline")&&(i=[{t:"u",c:i}]),s.fontStyle==="italic"&&(i=[{t:"i",c:i}]),(s.fontWeight==="bold"||s.fontWeight>="700")&&(i=[{t:"b",c:i}]),i}default:return o}}function Gi(n){return n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function qi(n){return n.replace(/"/g,""").replace(/</g,"<")}function De({value:n,onChange:t,placeholder:o,rows:r=4,label:s,required:i,hint:a}){const[c,l]=g("rich"),p=Y(null),d=Y(n),[u,h]=g(()=>ye(he(n))),m=I=>ye(he(I)),k=I=>{const x=Xt(_n(I));d.current=x,t(x)};j(()=>{if(n===d.current)return;d.current=n;const I=m(n);c==="rich"&&p.current&&(p.current.innerHTML=I),h(I)},[n]),j(()=>{c==="rich"&&p.current&&(p.current.innerHTML=u)},[c]);const y=()=>{if(p.current){const I=p.current.innerHTML;k(I),h(I)}l("code")},b=()=>l("rich"),v=(I,x)=>{var $;($=p.current)==null||$.focus(),document.execCommand(I,!1,x??""),p.current&&k(p.current.innerHTML)},w=()=>{const I=window.prompt("Enter URL:");I&&v("createLink",I)},N=!n||!he(n).length;return e("div",{children:[s&&e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:[s,i&&e("span",{class:"text-red-500 ml-0.5",children:"*"}),a&&e("span",{class:"ml-1.5 text-gray-400 font-normal",children:a})]}),e("div",{class:"border border-gray-300 rounded-lg overflow-hidden focus-within:border-blue-400 focus-within:ring-1 focus-within:ring-blue-400 transition-shadow",children:[e("div",{class:"flex items-center border-b border-gray-200 bg-gray-50 min-h-[33px]",children:[e("div",{class:"flex shrink-0 border-r border-gray-200",children:[e(rt,{active:c==="rich",onClick:b,children:"Text"}),e(rt,{active:c==="code",onClick:y,icon:e(gr,{size:11}),children:"Code"})]}),c==="rich"&&e("div",{class:"flex items-center gap-px px-1.5",children:[e(me,{title:"Bold",onAct:()=>v("bold"),children:e(Wo,{size:12})}),e(me,{title:"Italic",onAct:()=>v("italic"),children:e(Sr,{size:12})}),e(it,{}),e(me,{title:"Bullet list",onAct:()=>v("insertUnorderedList"),children:e(Nr,{size:12})}),e(me,{title:"Numbered list",onAct:()=>v("insertOrderedList"),children:e($r,{size:12})}),e(it,{}),e(me,{title:"Add link",onAct:w,children:e(Lr,{size:12})}),e(me,{title:"Remove link",onAct:()=>v("unlink"),children:e(Ti,{size:12})})]})]}),e("div",{ref:p,contentEditable:!0,class:`px-3 py-2.5 text-sm text-gray-800 bg-white focus:outline-none prose prose-sm max-w-none ${c==="code"?"hidden":""}`,style:{minHeight:`${r*1.625}rem`},onInput:()=>p.current&&k(p.current.innerHTML),"data-placeholder":N&&c==="rich"?o??"":""}),c==="code"&&e("textarea",{class:"w-full px-3 py-2 text-sm font-mono resize-y focus:outline-none bg-white",style:{minHeight:`${r*1.625}rem`},value:u,onInput:I=>{const x=I.target.value;h(x),k(x)},placeholder:o,spellcheck:!1})]}),i&&N&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"This field is required"})]})}function rt({active:n,onClick:t,children:o,icon:r}){return e("button",{type:"button",onClick:t,class:`flex items-center gap-1 text-xs px-3 py-1.5 transition-colors ${n?"bg-white text-gray-800 font-medium border-b-2 border-blue-500 -mb-px":"text-gray-500 hover:text-gray-700"}`,children:[r,o]})}function me({title:n,onAct:t,children:o}){return e("button",{type:"button",title:n,onMouseDown:r=>{r.preventDefault(),t()},class:"p-1 text-gray-500 hover:text-gray-800 hover:bg-gray-200 rounded transition-colors",children:o})}function it(){return e("div",{class:"w-px h-3.5 bg-gray-200 mx-0.5"})}function Fi(n){const t=n.trim().toLowerCase();try{if(t.startsWith("http://")||t.startsWith("https://")){const o=new URL(t);return o.port?`${o.hostname}:${o.port}`:o.hostname}}catch{}return t}function Ki(n){const t=n.startsWith("*.")?n.slice(2):n;if(/^localhost(:\d{1,5})?$/.test(t))return!0;const[o,r]=t.split(":");return r!==void 0&&!/^\d{1,5}$/.test(r)?!1:/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/.test(o??"")}function Wi(n){return n.split(/[\s,\n]+/).map(Fi).filter(t=>t.length>0)}function Ji({value:n,onChange:t,placeholder:o,disabled:r}){const[s,i]=g(""),[a,c]=g(!1),l=u=>{const h=Wi(u);if(h.filter(y=>!Ki(y)).length>0){c(!0);return}c(!1);const k=[...new Set([...n,...h])];t(k),i("")},p=u=>t(n.filter(h=>h!==u)),d=u=>{i(u),c(!1)};return e("div",{class:"space-y-1",children:[e("div",{class:`flex flex-wrap gap-1.5 border rounded px-3 py-2 min-h-[42px] focus-within:ring-1 transition-colors ${a?"border-red-400 focus-within:ring-red-400":"border-gray-300 focus-within:ring-blue-500 focus-within:border-blue-500"} ${r?"bg-gray-50":"bg-white"}`,children:[n.map(u=>e("span",{class:"inline-flex items-center gap-1 bg-blue-50 text-blue-700 text-xs px-2 py-0.5 rounded-full border border-blue-200 font-mono",children:[u,!r&&e("button",{type:"button",onClick:()=>p(u),class:"text-blue-400 hover:text-blue-700 leading-none ml-0.5","aria-label":`Remove ${u}`,children:e(Ye,{size:10})})]},u)),!r&&e("input",{type:"text",value:s,class:"outline-none flex-1 min-w-[200px] text-sm bg-transparent",placeholder:n.length===0?o??"example.com, *.staging.example.com":"",onInput:u=>d(u.target.value),onKeyDown:u=>{u.key==="Enter"||u.key===","?(u.preventDefault(),s.trim()&&l(s)):u.key==="Backspace"&&!s&&n.length>0&&t(n.slice(0,-1))},onBlur:()=>{s.trim()&&l(s)},onPaste:u=>{var m;u.preventDefault();const h=((m=u.clipboardData)==null?void 0:m.getData("text"))??"";h.trim()&&l(h)}})]}),a&&e("p",{class:"flex items-center gap-1 text-xs text-red-600",children:[e(dr,{size:12}),"Invalid domain. Use ",e("code",{class:"font-mono bg-red-50 px-1 rounded",children:"example.com"})," or ",e("code",{class:"font-mono bg-red-50 px-1 rounded",children:"*.example.com"}),". Full URLs are accepted and normalized automatically."]})]})}const Yi=`"use strict";
|
|
292
|
-
var Consenti = (() => {
|
|
293
|
-
var __defProp = Object.defineProperty;
|
|
294
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
295
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
296
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
297
|
-
var __esm = (fn, res) => function __init() {
|
|
298
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
299
|
-
};
|
|
300
|
-
var __export = (target, all) => {
|
|
301
|
-
for (var name in all)
|
|
302
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
303
|
-
};
|
|
304
|
-
var __copyProps = (to, from, except, desc) => {
|
|
305
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
306
|
-
for (let key of __getOwnPropNames(from))
|
|
307
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
308
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
309
|
-
}
|
|
310
|
-
return to;
|
|
311
|
-
};
|
|
312
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
313
|
-
|
|
314
|
-
// src/utils/hmac.ts
|
|
315
|
-
var hmac_exports = {};
|
|
316
|
-
__export(hmac_exports, {
|
|
317
|
-
signValue: () => signValue,
|
|
318
|
-
verifyValue: () => verifyValue
|
|
319
|
-
});
|
|
320
|
-
async function importHmacKey(rawKey) {
|
|
321
|
-
const keyData = new TextEncoder().encode(rawKey);
|
|
322
|
-
return crypto.subtle.importKey(
|
|
323
|
-
"raw",
|
|
324
|
-
keyData,
|
|
325
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
326
|
-
false,
|
|
327
|
-
["sign", "verify"]
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
async function signValue(value, key) {
|
|
331
|
-
const cryptoKey = await importHmacKey(key);
|
|
332
|
-
const data = new TextEncoder().encode(value);
|
|
333
|
-
const signature = await crypto.subtle.sign("HMAC", cryptoKey, data);
|
|
334
|
-
return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
335
|
-
}
|
|
336
|
-
async function verifyValue(value, signature, key) {
|
|
337
|
-
const expected = await signValue(value, key);
|
|
338
|
-
return expected === signature;
|
|
339
|
-
}
|
|
340
|
-
var init_hmac = __esm({
|
|
341
|
-
"src/utils/hmac.ts"() {
|
|
342
|
-
"use strict";
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// src/utils/http.ts
|
|
347
|
-
var http_exports = {};
|
|
348
|
-
__export(http_exports, {
|
|
349
|
-
httpRequest: () => httpRequest
|
|
350
|
-
});
|
|
351
|
-
async function httpRequest(url, options = {}, authToken) {
|
|
352
|
-
const headers = {
|
|
353
|
-
"Content-Type": "application/json",
|
|
354
|
-
...options.headers
|
|
355
|
-
};
|
|
356
|
-
if (authToken) {
|
|
357
|
-
headers["Authorization"] = \`Bearer \${authToken}\`;
|
|
358
|
-
}
|
|
359
|
-
const res = await fetch(url, { ...options, headers });
|
|
360
|
-
if (!res.ok) {
|
|
361
|
-
throw new Error(\`[Consenti] HTTP \${res.status} \${res.statusText} \\u2014 \${url}\`);
|
|
362
|
-
}
|
|
363
|
-
return res.json();
|
|
364
|
-
}
|
|
365
|
-
var init_http = __esm({
|
|
366
|
-
"src/utils/http.ts"() {
|
|
367
|
-
"use strict";
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// src/index.ts
|
|
372
|
-
var src_exports = {};
|
|
373
|
-
__export(src_exports, {
|
|
374
|
-
ConsentScript: () => ConsentScript,
|
|
375
|
-
ConsentiPlugin: () => ConsentiPlugin,
|
|
376
|
-
ConsentiProfile: () => ConsentiProfile,
|
|
377
|
-
ConsentiSetup: () => ConsentiSetup,
|
|
378
|
-
CookieTrigger: () => CookieTrigger
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// src/styles/consenti-css.ts
|
|
382
|
-
var CONSENTI_CSS = \`:root{--consenti-color-bg: #ffffff;--consenti-color-text: #1a2e4a;--consenti-color-primary: #1565c0;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #f0f4f8;--consenti-color-secondary-text: #1a2e4a;--consenti-color-border: #dbe4ee;--consenti-color-overlay: rgba(26, 52, 96, 0.55);--consenti-font-family: system-ui, -apple-system, sans-serif;--consenti-font-size-base: 14px;--consenti-font-size-heading: 16px;--consenti-font-weight-heading: 600;--consenti-line-height: 1.5;--consenti-spacing-sm: 8px;--consenti-spacing-md: 16px;--consenti-spacing-lg: 24px;--consenti-border-radius: 8px;--consenti-border-radius-btn: 4px;--consenti-shadow: 0 4px 24px rgba(21, 101, 192, 0.14);--consenti-color-accent: #d32f2f;--consenti-color-accent-text: #ffffff;--consenti-toggle-bg-on: #43a047;--consenti-toggle-bg-off: #c8d6e8;--consenti-toggle-knob: #ffffff;--consenti-toggle-width: 44px;--consenti-toggle-height: 24px;--consenti-z-banner: 9999;--consenti-z-overlay: 9998;--consenti-z-modal: 10000}.consenti-root--dark{--consenti-color-bg: #1e2535;--consenti-color-text: #e2e8f0;--consenti-color-primary: #4a9eff;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #2a3447;--consenti-color-secondary-text: #cbd5e1;--consenti-color-border: #374151;--consenti-color-overlay: rgba(0, 0, 0, 0.65);--consenti-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);--consenti-toggle-bg-off: #4b5563}.consenti-overlay{position:fixed;inset:0;background:var(--consenti-color-overlay);z-index:var(--consenti-z-overlay)}.consenti-btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--consenti-spacing-sm) var(--consenti-spacing-md);border:none;border-radius:var(--consenti-border-radius-btn);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);font-weight:500;line-height:1;cursor:pointer;text-decoration:none;transition:opacity .15s ease}.consenti-btn:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-btn:hover{opacity:.9}.consenti-btn--primary{background:var(--consenti-color-primary);color:var(--consenti-color-primary-text)}.consenti-btn--secondary{background:var(--consenti-color-secondary);color:var(--consenti-color-secondary-text)}.consenti-btn--text{background:rgba(0,0,0,0);color:var(--consenti-color-primary);padding-left:0;padding-right:0;text-decoration:underline}.consenti-btn--accent{background:var(--consenti-color-accent);color:var(--consenti-color-accent-text)}.consenti-btn--close{background:rgba(0,0,0,0);color:var(--consenti-color-text);font-size:20px;line-height:1;padding:4px 8px;border-radius:50%}.consenti-btn--close:hover{background:var(--consenti-color-secondary);opacity:1}.consenti-banner{position:fixed;left:0;right:0;z-index:var(--consenti-z-banner);background:var(--consenti-color-bg);color:var(--consenti-color-text);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);display:flex;flex-wrap:wrap;align-items:center;gap:var(--consenti-spacing-md)}.consenti-banner--top{top:0;border-bottom:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--top{animation:consenti-slide-down .2s ease-out}}.consenti-banner--bottom{bottom:0;border-top:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--bottom{animation:consenti-slide-up .2s ease-out}}.consenti-banner--middle{top:50%;transform:translateY(-50%);left:50%;right:auto;transform:translate(-50%, -50%);width:min(600px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--left-bottom{bottom:var(--consenti-spacing-lg);left:var(--consenti-spacing-lg);right:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--right-bottom{bottom:var(--consenti-spacing-lg);right:var(--consenti-spacing-lg);left:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner__text-container{flex:1;min-width:200px}.consenti-banner__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0 0 var(--consenti-spacing-sm);width:100%}.consenti-banner__text{margin:0}.consenti-banner__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-sm)}.consenti-banner__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-banner__controls{position:absolute;top:var(--consenti-spacing-sm);right:var(--consenti-spacing-sm);display:flex;align-items:center;gap:4px}.consenti-banner__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);align-items:center}.consenti-banner--top .consenti-banner__buttons,.consenti-banner--bottom .consenti-banner__buttons{margin-left:2vw}@keyframes consenti-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes consenti-slide-down{from{transform:translateY(-100%)}to{transform:translateY(0)}}.consenti-modal{position:fixed;inset:0;z-index:var(--consenti-z-modal);display:flex;font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);line-height:var(--consenti-line-height)}.consenti-modal--passthrough{pointer-events:none}.consenti-modal--passthrough .consenti-modal__container{pointer-events:auto}.consenti-modal--center{align-items:center;justify-content:center}.consenti-modal--left{align-items:stretch;justify-content:flex-start}.consenti-modal--left .consenti-modal__container{border-radius:0 var(--consenti-border-radius) var(--consenti-border-radius) 0;height:100%;max-height:100vh}.consenti-modal--right{align-items:stretch;justify-content:flex-end}.consenti-modal--right .consenti-modal__container{border-radius:0;height:100%;max-height:100vh}.consenti-modal__overlay{position:absolute;inset:0;background:var(--consenti-color-overlay)}.consenti-modal__container{position:relative;z-index:1;background:var(--consenti-color-bg);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);width:min(560px,90vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}@media(prefers-reduced-motion: no-preference){.consenti-modal__container{animation:consenti-fade-in .15s ease-out}}.consenti-modal__header{display:flex;align-items:center;justify-content:space-between;padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-bottom:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0;flex:1}.consenti-modal__header-controls{display:flex;align-items:center;gap:4px;flex-shrink:0}.consenti-modal__close{flex-shrink:0}.consenti-modal__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-modal__subheading{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg) 0;margin:0;color:#6b7280;font-size:13px}.consenti-modal__text{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__body{overflow-y:auto;flex:1;padding:0 var(--consenti-spacing-lg)}.consenti-modal__categories{display:flex;flex-direction:column;gap:var(--consenti-spacing-md);padding:var(--consenti-spacing-md) 0}.consenti-modal__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-option{margin:0 var(--consenti-spacing-lg);padding-top:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-label{display:flex;align-items:center;gap:var(--consenti-spacing-sm);font-size:var(--consenti-font-size-base);cursor:pointer}.consenti-modal__receipt-checkbox{width:16px;height:16px;flex-shrink:0;cursor:pointer}.consenti-modal__receipt-desc{margin:4px 0 0 24px;font-size:12px;color:#6b7280}@keyframes consenti-fade-in{from{opacity:0;transform:scale(0.97)}to{opacity:1;transform:scale(1)}}.consenti-category{padding:var(--consenti-spacing-md) 0;border-bottom:1px solid var(--consenti-color-border)}.consenti-category:last-child{border-bottom:none}.consenti-category--mandatory .consenti-category__toggle{opacity:.6;cursor:default}.consenti-category__header{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-md);margin-bottom:var(--consenti-spacing-sm)}.consenti-category__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0}.consenti-category__text{color:#4b5563;font-size:13px;margin:0}.consenti-category__li-desc{margin:var(--consenti-spacing-sm) 0 0;font-size:12px;color:#6b7280;font-style:italic}.consenti-category__toggle{flex-shrink:0;position:relative;width:var(--consenti-toggle-width);height:var(--consenti-toggle-height);border-radius:calc(var(--consenti-toggle-height)/2);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__toggle--mandatory{cursor:default}.consenti-category__toggle-knob{position:absolute;top:2px;left:2px;width:calc(var(--consenti-toggle-height) - 4px);height:calc(var(--consenti-toggle-height) - 4px);border-radius:50%;background:var(--consenti-toggle-knob);transition:transform .2s ease;display:block}[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) - var(--consenti-toggle-height)))}.consenti-locale-switcher{position:relative;display:inline-flex}.consenti-locale-switcher__btn{font-size:1rem;line-height:1;cursor:pointer}.consenti-locale-switcher__list{position:absolute;top:calc(100% + 4px);right:0;z-index:calc(var(--consenti-z-modal) + 1);background:var(--consenti-color-bg);border:1px solid var(--consenti-color-border);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);list-style:none;margin:0;padding:4px 0;min-width:80px}.consenti-locale-switcher__list li{display:block}.consenti-locale-switcher__option{display:block;width:100%;padding:6px 12px;text-align:left;font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);background:none;border:none;cursor:pointer}.consenti-locale-switcher__option:hover{background:var(--consenti-color-secondary)}.consenti-locale-switcher__option--active{font-weight:600;color:var(--consenti-color-primary)}.consenti-locale-switcher--modal .consenti-locale-switcher__list{right:0}\`;
|
|
383
|
-
|
|
384
|
-
// src/utils/ssr.ts
|
|
385
|
-
var isClient = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
386
|
-
var safeWindow = () => isClient() ? window : null;
|
|
387
|
-
var safeNavigator = () => isClient() && typeof navigator !== "undefined" ? navigator : null;
|
|
388
|
-
|
|
389
|
-
// src/utils/broadcast.ts
|
|
390
|
-
var ConsentiChannel = class {
|
|
391
|
-
constructor() {
|
|
392
|
-
this.channel = null;
|
|
393
|
-
}
|
|
394
|
-
/** \`true\` when \`BroadcastChannel\` is available in the current environment. */
|
|
395
|
-
get isSupported() {
|
|
396
|
-
return isClient() && typeof BroadcastChannel !== "undefined";
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Opens the \`BroadcastChannel\` and registers a message handler.
|
|
400
|
-
* Silently no-ops if \`BroadcastChannel\` is unsupported.
|
|
401
|
-
*
|
|
402
|
-
* @param onMessage - Callback invoked with each received \`ConsentiMessage\`.
|
|
403
|
-
*/
|
|
404
|
-
connect(onMessage) {
|
|
405
|
-
if (!this.isSupported) return;
|
|
406
|
-
this.onMessageCallback = onMessage;
|
|
407
|
-
this.channel = new BroadcastChannel("consenti");
|
|
408
|
-
this.channel.onmessage = (event) => {
|
|
409
|
-
if (event.data?.type) {
|
|
410
|
-
onMessage(event.data);
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
this.channel.onmessageerror = () => {
|
|
414
|
-
console.warn("[Consenti] BroadcastChannel message error \\u2014 ignoring");
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Posts a message to all other tabs connected to the \`'consenti'\` channel.
|
|
419
|
-
* The sending tab does not receive its own message.
|
|
420
|
-
*
|
|
421
|
-
* @param message - The \`ConsentiMessage\` to broadcast.
|
|
422
|
-
*/
|
|
423
|
-
send(message) {
|
|
424
|
-
this.channel?.postMessage(message);
|
|
425
|
-
}
|
|
426
|
-
/** Closes the channel and removes the message callback. */
|
|
427
|
-
disconnect() {
|
|
428
|
-
this.channel?.close();
|
|
429
|
-
this.channel = null;
|
|
430
|
-
this.onMessageCallback = void 0;
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
// src/utils/receipt.ts
|
|
435
|
-
function generateReceipt(params) {
|
|
436
|
-
const receipt = {
|
|
437
|
-
version: "1.0",
|
|
438
|
-
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
439
|
-
visitorId: params.visitorId,
|
|
440
|
-
profileId: params.profileId,
|
|
441
|
-
profileVersion: params.profileVersion,
|
|
442
|
-
locale: params.locale,
|
|
443
|
-
consent: params.consent
|
|
444
|
-
};
|
|
445
|
-
if (params.signature) receipt.signature = params.signature;
|
|
446
|
-
return receipt;
|
|
447
|
-
}
|
|
448
|
-
function downloadReceipt(receipt) {
|
|
449
|
-
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
450
|
-
const json = JSON.stringify(receipt, null, 2);
|
|
451
|
-
const blob = new Blob([json], { type: "application/json" });
|
|
452
|
-
const url = URL.createObjectURL(blob);
|
|
453
|
-
const anchor = document.createElement("a");
|
|
454
|
-
anchor.href = url;
|
|
455
|
-
anchor.download = \`consenti-receipt-\${receipt.issuedAt.slice(0, 10)}.json\`;
|
|
456
|
-
anchor.style.display = "none";
|
|
457
|
-
document.body.appendChild(anchor);
|
|
458
|
-
anchor.click();
|
|
459
|
-
document.body.removeChild(anchor);
|
|
460
|
-
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/utils/cookie.ts
|
|
464
|
-
function getCookie(name) {
|
|
465
|
-
if (!isClient()) return null;
|
|
466
|
-
const match = document.cookie.match(new RegExp(\`(?:^|;\\\\s*)\${escapeRegExp(name)}=([^;]*)\`));
|
|
467
|
-
return match ? decodeURIComponent(match[1] ?? "") : null;
|
|
468
|
-
}
|
|
469
|
-
function setCookie(name, value, opts = {}) {
|
|
470
|
-
if (!isClient()) return;
|
|
471
|
-
const secure = opts.secure ?? (typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.protocol === "https:");
|
|
472
|
-
const parts = [
|
|
473
|
-
\`\${encodeURIComponent(name)}=\${encodeURIComponent(value)}\`,
|
|
474
|
-
\`Path=\${opts.path ?? "/"}\`,
|
|
475
|
-
\`SameSite=\${opts.sameSite ?? "Lax"}\`,
|
|
476
|
-
opts.maxAge != null ? \`Max-Age=\${opts.maxAge}\` : "",
|
|
477
|
-
secure ? "Secure" : "",
|
|
478
|
-
opts.domain ? \`Domain=\${opts.domain}\` : ""
|
|
479
|
-
];
|
|
480
|
-
document.cookie = parts.filter(Boolean).join("; ");
|
|
481
|
-
}
|
|
482
|
-
function deleteCookie(name, opts = {}) {
|
|
483
|
-
setCookie(name, "", { ...opts, maxAge: 0 });
|
|
484
|
-
}
|
|
485
|
-
function escapeRegExp(str) {
|
|
486
|
-
return str.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&");
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// src/utils/storage.ts
|
|
490
|
-
var ConsentStorage = class {
|
|
491
|
-
/**
|
|
492
|
-
* @param mode - \`'cookie'\` (default) or \`'localStorage'\`. Falls back to \`'cookie'\` if
|
|
493
|
-
* \`localStorage\` is requested but unavailable in the current environment.
|
|
494
|
-
*/
|
|
495
|
-
constructor(mode) {
|
|
496
|
-
this.mode = mode;
|
|
497
|
-
if (mode === "localStorage" && isClient() && typeof localStorage === "undefined") {
|
|
498
|
-
console.warn("[Consenti] localStorage is not available, falling back to cookie storage");
|
|
499
|
-
this.mode = "cookie";
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Reads a value from storage.
|
|
504
|
-
*
|
|
505
|
-
* @param key - Cookie name or localStorage key.
|
|
506
|
-
* @returns The stored string value, or \`null\` if absent or SSR context.
|
|
507
|
-
*/
|
|
508
|
-
read(key) {
|
|
509
|
-
if (!isClient()) return null;
|
|
510
|
-
if (this.mode === "localStorage") {
|
|
511
|
-
return localStorage.getItem(key);
|
|
512
|
-
}
|
|
513
|
-
return getCookie(key);
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Writes a value to storage.
|
|
517
|
-
*
|
|
518
|
-
* @param key - Cookie name or localStorage key.
|
|
519
|
-
* @param value - String value to persist.
|
|
520
|
-
* @param opts - Cookie attributes (ignored in localStorage mode).
|
|
521
|
-
*/
|
|
522
|
-
write(key, value, opts = {}) {
|
|
523
|
-
if (!isClient()) return;
|
|
524
|
-
if (this.mode === "localStorage") {
|
|
525
|
-
localStorage.setItem(key, value);
|
|
526
|
-
} else {
|
|
527
|
-
setCookie(key, value, opts);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Removes a value from storage.
|
|
532
|
-
*
|
|
533
|
-
* @param key - Cookie name or localStorage key.
|
|
534
|
-
* @param opts - Cookie \`path\` / \`domain\` attributes (must match the original write call).
|
|
535
|
-
*/
|
|
536
|
-
remove(key, opts = {}) {
|
|
537
|
-
if (!isClient()) return;
|
|
538
|
-
if (this.mode === "localStorage") {
|
|
539
|
-
localStorage.removeItem(key);
|
|
540
|
-
} else {
|
|
541
|
-
deleteCookie(key, opts);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
// src/core/consent-store.ts
|
|
547
|
-
var COOKIE_NAME_PREFIX = "consenti";
|
|
548
|
-
function buildConsentCookieName(userId, profileId) {
|
|
549
|
-
return \`\${COOKIE_NAME_PREFIX}_\${userId}_\${profileId}\`;
|
|
550
|
-
}
|
|
551
|
-
function buildCookieValue(consent) {
|
|
552
|
-
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
553
|
-
const pairs = Object.entries(consent).map(([id, status]) => \`\${id}:\${status}\`).join("|");
|
|
554
|
-
return \`t:\${epochSeconds}::\${pairs}\`;
|
|
555
|
-
}
|
|
556
|
-
function parseConsentCookie(value) {
|
|
557
|
-
const parts = value.split("::");
|
|
558
|
-
if (parts.length < 2) return null;
|
|
559
|
-
const tsPart = parts[0] ?? "";
|
|
560
|
-
let timestamp;
|
|
561
|
-
if (tsPart.startsWith("t:")) {
|
|
562
|
-
const epochStr = tsPart.slice(2);
|
|
563
|
-
const epoch = parseInt(epochStr, 10);
|
|
564
|
-
if (!epochStr || isNaN(epoch)) return null;
|
|
565
|
-
timestamp = new Date(epoch * 1e3).toISOString();
|
|
566
|
-
} else if (tsPart.startsWith("ConsentTimestamp:")) {
|
|
567
|
-
timestamp = tsPart.replace("ConsentTimestamp:", "");
|
|
568
|
-
if (!timestamp) return null;
|
|
569
|
-
} else {
|
|
570
|
-
return null;
|
|
571
|
-
}
|
|
572
|
-
const consentPart = parts[1] ?? "";
|
|
573
|
-
const sigPart = parts[2];
|
|
574
|
-
const consent = {};
|
|
575
|
-
for (const pair of consentPart.split("|")) {
|
|
576
|
-
const colonIdx = pair.indexOf(":");
|
|
577
|
-
if (colonIdx === -1) return null;
|
|
578
|
-
const id = pair.slice(0, colonIdx);
|
|
579
|
-
const status = pair.slice(colonIdx + 1);
|
|
580
|
-
if (!id || status !== "granted" && status !== "denied" && status !== "objected") {
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
consent[id] = status;
|
|
584
|
-
}
|
|
585
|
-
const result = { timestamp, consent };
|
|
586
|
-
if (sigPart?.startsWith("sig:")) {
|
|
587
|
-
result.signature = sigPart.slice(4);
|
|
588
|
-
}
|
|
589
|
-
return result;
|
|
590
|
-
}
|
|
591
|
-
var ConsentStore = class _ConsentStore {
|
|
592
|
-
/**
|
|
593
|
-
* @param userId - Visitor UUID (from \`consenti_uid\` cookie).
|
|
594
|
-
* @param profileId - Numeric profile ID.
|
|
595
|
-
* @param storageMode - \`'cookie'\` (default, cross-subdomain) or \`'localStorage'\` (origin-scoped).
|
|
596
|
-
* @param cookieDomains - Comma-separated domain list. First entry used as \`Domain\` attribute.
|
|
597
|
-
*/
|
|
598
|
-
constructor(userId, profileId, storageMode, cookieDomains) {
|
|
599
|
-
this.userId = userId;
|
|
600
|
-
this.profileId = profileId;
|
|
601
|
-
this.cookieDomains = cookieDomains;
|
|
602
|
-
this.cachedConsent = null;
|
|
603
|
-
this.cachedTimestamp = null;
|
|
604
|
-
this.storage = new ConsentStorage(storageMode);
|
|
605
|
-
}
|
|
606
|
-
/** The full cookie / localStorage key for this visitor + profile combination. */
|
|
607
|
-
get cookieName() {
|
|
608
|
-
return buildConsentCookieName(this.userId, this.profileId);
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Reads and parses the stored consent record.
|
|
612
|
-
* Updates the in-memory cache on success.
|
|
613
|
-
*
|
|
614
|
-
* @returns The parsed consent or \`null\` if absent / malformed.
|
|
615
|
-
*/
|
|
616
|
-
read() {
|
|
617
|
-
const raw = this.storage.read(this.cookieName);
|
|
618
|
-
if (!raw) return null;
|
|
619
|
-
const parsed = parseConsentCookie(raw);
|
|
620
|
-
if (parsed) {
|
|
621
|
-
this.cachedConsent = parsed.consent;
|
|
622
|
-
this.cachedTimestamp = parsed.timestamp;
|
|
623
|
-
}
|
|
624
|
-
return parsed;
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Reads, parses, and cryptographically verifies the stored consent record.
|
|
628
|
-
*
|
|
629
|
-
* If the cookie has a \`::sig:{hmac}\` segment, the signature is verified against
|
|
630
|
-
* the raw value using HMAC-SHA256. A mismatch means the cookie was tampered with —
|
|
631
|
-
* the cookie is deleted and \`null\` is returned so the widget re-prompts for consent.
|
|
632
|
-
*
|
|
633
|
-
* If the cookie has no signature segment, it is treated as valid (backwards compatible
|
|
634
|
-
* with cookies written before \`signCookies\` was enabled).
|
|
635
|
-
*
|
|
636
|
-
* @param signingKey - The HMAC key (from profile API response or \`core.cookieSigningKey\`).
|
|
637
|
-
* @returns The parsed consent, or \`null\` if absent / malformed / signature invalid.
|
|
638
|
-
*/
|
|
639
|
-
async readAndVerify(signingKey) {
|
|
640
|
-
const raw = this.storage.read(this.cookieName);
|
|
641
|
-
if (!raw) return null;
|
|
642
|
-
const parts = raw.split("::");
|
|
643
|
-
if (parts.length < 2) return null;
|
|
644
|
-
const parsed = parseConsentCookie(raw);
|
|
645
|
-
if (!parsed) return null;
|
|
646
|
-
const hasSig = parts.length === 3 && (parts[2] ?? "").startsWith("sig:");
|
|
647
|
-
if (hasSig) {
|
|
648
|
-
const { verifyValue: verifyValue2 } = await Promise.resolve().then(() => (init_hmac(), hmac_exports));
|
|
649
|
-
const valueToVerify = (parts[0] ?? "") + "::" + (parts[1] ?? "");
|
|
650
|
-
const sig = (parts[2] ?? "").slice(4);
|
|
651
|
-
const valid = await verifyValue2(valueToVerify, sig, signingKey);
|
|
652
|
-
if (!valid) {
|
|
653
|
-
console.warn("[Consenti] Cookie signature mismatch \\u2014 consent cleared and banner will re-appear");
|
|
654
|
-
this.delete();
|
|
655
|
-
return null;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
this.cachedConsent = parsed.consent;
|
|
659
|
-
this.cachedTimestamp = parsed.timestamp;
|
|
660
|
-
return parsed;
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Writes a consent record to storage.
|
|
664
|
-
*
|
|
665
|
-
* @param consent - The consent map to persist.
|
|
666
|
-
* @param signature - Optional pre-computed HMAC hex string. Use \`writeAndSign()\` instead
|
|
667
|
-
* if you need signing — it guarantees the signature covers the exact
|
|
668
|
-
* value that gets written (timestamps match).
|
|
669
|
-
*/
|
|
670
|
-
write(consent, signature) {
|
|
671
|
-
let value = buildCookieValue(consent);
|
|
672
|
-
if (signature) {
|
|
673
|
-
value = \`\${value}::sig:\${signature}\`;
|
|
674
|
-
}
|
|
675
|
-
const opts = this.buildCookieOpts();
|
|
676
|
-
this.storage.write(this.cookieName, value, opts);
|
|
677
|
-
this.cachedConsent = consent;
|
|
678
|
-
this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Builds the cookie value, signs it with HMAC-SHA256 in a single atomic step,
|
|
682
|
-
* and writes the signed value to storage.
|
|
683
|
-
*
|
|
684
|
-
* Using this instead of \`write(consent, signature)\` ensures the signature
|
|
685
|
-
* covers exactly the value that is written — there is no timestamp drift
|
|
686
|
-
* between building, signing, and writing.
|
|
687
|
-
*
|
|
688
|
-
* @param consent - The consent map to persist.
|
|
689
|
-
* @param signingKey - The HMAC key string.
|
|
690
|
-
* @returns The hex-encoded signature, stored in \`this.lastSignature\` by the caller.
|
|
691
|
-
*/
|
|
692
|
-
async writeAndSign(consent, signingKey) {
|
|
693
|
-
const { signValue: signValue2 } = await Promise.resolve().then(() => (init_hmac(), hmac_exports));
|
|
694
|
-
const rawValue = buildCookieValue(consent);
|
|
695
|
-
const sig = await signValue2(rawValue, signingKey);
|
|
696
|
-
const finalValue = \`\${rawValue}::sig:\${sig}\`;
|
|
697
|
-
const opts = this.buildCookieOpts();
|
|
698
|
-
this.storage.write(this.cookieName, finalValue, opts);
|
|
699
|
-
this.cachedConsent = consent;
|
|
700
|
-
this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
701
|
-
return sig;
|
|
702
|
-
}
|
|
703
|
-
/** Removes the consent record from storage and clears the in-memory cache. */
|
|
704
|
-
delete() {
|
|
705
|
-
const opts = this.buildCookieOpts();
|
|
706
|
-
this.storage.remove(this.cookieName, opts);
|
|
707
|
-
this.cachedConsent = null;
|
|
708
|
-
this.cachedTimestamp = null;
|
|
709
|
-
}
|
|
710
|
-
/** Returns \`true\` if a valid consent record exists (uses cache when available). */
|
|
711
|
-
hasConsent() {
|
|
712
|
-
if (this.cachedConsent) return true;
|
|
713
|
-
return this.read() !== null;
|
|
714
|
-
}
|
|
715
|
-
/** Returns the stored consent map, or \`null\` if no consent has been given. */
|
|
716
|
-
getConsent() {
|
|
717
|
-
if (this.cachedConsent) return this.cachedConsent;
|
|
718
|
-
return this.read()?.consent ?? null;
|
|
719
|
-
}
|
|
720
|
-
/** Returns the \`Date\` of the last consent submission, or \`false\` if no consent exists. */
|
|
721
|
-
getConsentDate() {
|
|
722
|
-
if (this.cachedTimestamp) return new Date(this.cachedTimestamp);
|
|
723
|
-
const parsed = this.read();
|
|
724
|
-
if (!parsed) return false;
|
|
725
|
-
return new Date(parsed.timestamp);
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Updates the in-memory cache without writing to storage.
|
|
729
|
-
* Used by the cross-tab BroadcastChannel handler to keep other tabs in sync.
|
|
730
|
-
*/
|
|
731
|
-
setInMemory(consent) {
|
|
732
|
-
this.cachedConsent = consent;
|
|
733
|
-
this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
734
|
-
}
|
|
735
|
-
/** Clears the in-memory cache. Used when consent is deleted from another tab. */
|
|
736
|
-
clearInMemory() {
|
|
737
|
-
this.cachedConsent = null;
|
|
738
|
-
this.cachedTimestamp = null;
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Derives the visitor UUID from an existing consent cookie, or generates a new one
|
|
742
|
-
* in memory.
|
|
743
|
-
*
|
|
744
|
-
* No separate identifier cookie is written before consent. Storing a tracking
|
|
745
|
-
* identifier before the user has given consent violates TTDSG §25 (Germany) and
|
|
746
|
-
* equivalent ePrivacy laws in the EU. The UUID is embedded in the consent cookie
|
|
747
|
-
* name and only persisted when consent is first saved (via \`write()\` / \`writeAndSign()\`).
|
|
748
|
-
*
|
|
749
|
-
* @param profileId - The active profile ID, used to locate an existing consent cookie.
|
|
750
|
-
* @param storageMode - The configured storage mode (\`'cookie'\` or \`'localStorage'\`).
|
|
751
|
-
*/
|
|
752
|
-
static getOrCreateUserId(profileId, storageMode) {
|
|
753
|
-
if (typeof document === "undefined") return crypto.randomUUID();
|
|
754
|
-
const prefix = \`\${COOKIE_NAME_PREFIX}_\`;
|
|
755
|
-
const suffix = \`_\${profileId}\`;
|
|
756
|
-
const all = storageMode === "localStorage" ? _ConsentStore._localStorageKeys() : _ConsentStore._cookieNames();
|
|
757
|
-
for (const name of all) {
|
|
758
|
-
if (name.startsWith(prefix) && name.endsWith(suffix)) {
|
|
759
|
-
const inner = name.slice(prefix.length, name.length - suffix.length);
|
|
760
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(inner)) {
|
|
761
|
-
return inner;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
return crypto.randomUUID();
|
|
766
|
-
}
|
|
767
|
-
/** Returns all document.cookie names. */
|
|
768
|
-
static _cookieNames() {
|
|
769
|
-
return document.cookie.split(";").map((c) => c.split("=")[0]?.trim() ?? "");
|
|
770
|
-
}
|
|
771
|
-
/** Returns all localStorage keys (empty array when unavailable). */
|
|
772
|
-
static _localStorageKeys() {
|
|
773
|
-
if (typeof localStorage === "undefined") return [];
|
|
774
|
-
return Object.keys(localStorage);
|
|
775
|
-
}
|
|
776
|
-
buildCookieOpts() {
|
|
777
|
-
const opts = {
|
|
778
|
-
path: "/",
|
|
779
|
-
sameSite: "Lax",
|
|
780
|
-
maxAge: 365 * 24 * 60 * 60
|
|
781
|
-
};
|
|
782
|
-
if (this.cookieDomains) {
|
|
783
|
-
const domain = this.cookieDomains.split(",")[0]?.trim();
|
|
784
|
-
if (domain) opts.domain = domain;
|
|
785
|
-
}
|
|
786
|
-
return opts;
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
// src/core/event-bus.ts
|
|
791
|
-
var EventBus = class {
|
|
792
|
-
/**
|
|
793
|
-
* @param profileId - Profile ID included in all GTM pushes.
|
|
794
|
-
* @param gtmConfig - Optional GTM integration settings.
|
|
795
|
-
*/
|
|
796
|
-
constructor(profileId, gtmConfig) {
|
|
797
|
-
this.profileId = profileId;
|
|
798
|
-
this.gtmConfig = gtmConfig;
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Dispatches a \`consenti:*\` custom event on \`window\` and, if GTM is configured,
|
|
802
|
-
* pushes the event to \`window.dataLayer\`.
|
|
803
|
-
*
|
|
804
|
-
* Silently no-ops in SSR contexts.
|
|
805
|
-
*
|
|
806
|
-
* @param name - The event name.
|
|
807
|
-
* @param detail - Event payload attached to \`event.detail\` and included in the GTM push.
|
|
808
|
-
*/
|
|
809
|
-
dispatch(name, detail = {}) {
|
|
810
|
-
if (!isClient()) return;
|
|
811
|
-
const event = new CustomEvent(name, { detail, bubbles: true });
|
|
812
|
-
window.dispatchEvent(event);
|
|
813
|
-
this.pushToGTM(name, detail);
|
|
814
|
-
}
|
|
815
|
-
pushToGTM(name, detail) {
|
|
816
|
-
const gtm = this.gtmConfig;
|
|
817
|
-
if (!gtm) return;
|
|
818
|
-
const win = safeWindow();
|
|
819
|
-
if (!win) return;
|
|
820
|
-
const layerName = gtm.dataLayer ?? "dataLayer";
|
|
821
|
-
const layer = win[layerName];
|
|
822
|
-
if (!Array.isArray(layer)) return;
|
|
823
|
-
const shouldPush = !gtm.events || gtm.events.length === 0 || gtm.events.includes(name);
|
|
824
|
-
if (!shouldPush) return;
|
|
825
|
-
layer.push({
|
|
826
|
-
event: name,
|
|
827
|
-
consenti: {
|
|
828
|
-
profileId: this.profileId,
|
|
829
|
-
consent: detail.consent,
|
|
830
|
-
consentId: detail.consentId,
|
|
831
|
-
consentAction: detail.consentAction
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
if (name === "consenti:consentSubmitted" && this.hasGtag(win)) {
|
|
835
|
-
if (gtm.urlPassthrough) {
|
|
836
|
-
layer.push({ url_passthrough: true });
|
|
837
|
-
}
|
|
838
|
-
if (gtm.adsDataRedaction) {
|
|
839
|
-
const adConsentDenied = detail.consent?.["ad_storage"] !== "granted";
|
|
840
|
-
layer.push({ ads_data_redaction: adConsentDenied });
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Returns \`true\` when gtag.js or Google Tag Manager is detected on the page.
|
|
846
|
-
* Used to gate GCM v2 cookieless-ping pushes so they are not emitted when
|
|
847
|
-
* Google services are absent.
|
|
848
|
-
*/
|
|
849
|
-
hasGtag(win) {
|
|
850
|
-
return typeof win["gtag"] === "function" || typeof win["google_tag_manager"] !== "undefined";
|
|
851
|
-
}
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
// src/core/profile-resolver.ts
|
|
855
|
-
init_http();
|
|
856
|
-
|
|
857
|
-
// src/utils/locale.ts
|
|
858
|
-
function resolveLocale(translations, requested, defaultLocale) {
|
|
859
|
-
const base = translations[defaultLocale];
|
|
860
|
-
if (!base) {
|
|
861
|
-
throw new Error(\`[Consenti] No translations found for defaultLocale "\${defaultLocale}"\`);
|
|
862
|
-
}
|
|
863
|
-
if (translations[requested]) {
|
|
864
|
-
return deepMerge(base, translations[requested]);
|
|
865
|
-
}
|
|
866
|
-
const lang = requested.split("-")[0] ?? "";
|
|
867
|
-
if (lang !== requested && translations[lang]) {
|
|
868
|
-
return deepMerge(base, translations[lang]);
|
|
869
|
-
}
|
|
870
|
-
return base;
|
|
871
|
-
}
|
|
872
|
-
function deepMerge(base, override) {
|
|
873
|
-
const result = { ...base };
|
|
874
|
-
for (const key in override) {
|
|
875
|
-
const overrideVal = override[key];
|
|
876
|
-
if (overrideVal === void 0 || overrideVal === null) continue;
|
|
877
|
-
if (typeof overrideVal === "object" && !Array.isArray(overrideVal) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
|
|
878
|
-
result[key] = deepMerge(result[key], overrideVal);
|
|
879
|
-
} else {
|
|
880
|
-
result[key] = overrideVal;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return result;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// src/core/profile-resolver.ts
|
|
887
|
-
var profileRegistry = /* @__PURE__ */ new Map();
|
|
888
|
-
function registerProfile(id, config) {
|
|
889
|
-
profileRegistry.set(id, config);
|
|
890
|
-
}
|
|
891
|
-
var DEFAULT_PROFILE = {
|
|
892
|
-
id: 0,
|
|
893
|
-
version: 1,
|
|
894
|
-
defaultLocale: "en",
|
|
895
|
-
cookies: [
|
|
896
|
-
{ id: "functionality_storage", mandatory: true },
|
|
897
|
-
{
|
|
898
|
-
id: "analytics_storage",
|
|
899
|
-
listenGpc: true,
|
|
900
|
-
expiry: 365
|
|
901
|
-
},
|
|
902
|
-
{
|
|
903
|
-
id: "ad_storage",
|
|
904
|
-
listenGpc: true,
|
|
905
|
-
expiry: 365
|
|
906
|
-
},
|
|
907
|
-
{
|
|
908
|
-
id: "ad_user_data",
|
|
909
|
-
listenGpc: true,
|
|
910
|
-
expiry: 365
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
id: "ad_personalization",
|
|
914
|
-
listenGpc: true,
|
|
915
|
-
expiry: 365
|
|
916
|
-
}
|
|
917
|
-
],
|
|
918
|
-
mainBanner: {
|
|
919
|
-
position: "bottom",
|
|
920
|
-
heading: "We value your privacy",
|
|
921
|
-
htmlText: "We use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking <strong>Accept All</strong> you consent to our use of cookies. You may change your preferences at any time.",
|
|
922
|
-
buttons: [
|
|
923
|
-
{ text: "Accept All", cookies: "*", style: "primary", action: "custom" },
|
|
924
|
-
{ text: "Reject All", cookies: "!", style: "primary", action: "custom" },
|
|
925
|
-
{ text: "Manage Preferences", style: "secondary", action: "manage" }
|
|
926
|
-
]
|
|
927
|
-
},
|
|
928
|
-
preferenceModal: {
|
|
929
|
-
heading: "Cookie Preferences",
|
|
930
|
-
subheading: "Choose which cookies you allow us to use.",
|
|
931
|
-
htmlText: "We use different types of cookies to optimise your experience. You can choose to enable or disable each category below. Necessary cookies cannot be disabled as they are required for core site functionality.",
|
|
932
|
-
showClose: true,
|
|
933
|
-
overlayOpacity: 50,
|
|
934
|
-
categories: [
|
|
935
|
-
{
|
|
936
|
-
id: "cat-necessary",
|
|
937
|
-
heading: "Strictly Necessary",
|
|
938
|
-
htmlText: "These cookies are essential for the website to function correctly. They include authentication, security, and accessibility features. They cannot be disabled.",
|
|
939
|
-
mandatory: true,
|
|
940
|
-
cookies: ["functionality_storage"]
|
|
941
|
-
},
|
|
942
|
-
{
|
|
943
|
-
id: "cat-analytics",
|
|
944
|
-
heading: "Analytics",
|
|
945
|
-
htmlText: "These cookies help us understand how visitors interact with our website by collecting and reporting information anonymously. This allows us to improve the site over time. (e.g. Google Analytics)",
|
|
946
|
-
cookies: ["analytics_storage"]
|
|
947
|
-
},
|
|
948
|
-
{
|
|
949
|
-
id: "cat-advertising",
|
|
950
|
-
heading: "Advertising",
|
|
951
|
-
htmlText: "These cookies are used to deliver advertisements more relevant to you and your interests. They are also used to limit the number of times you see an ad and to help measure the effectiveness of advertising campaigns. (e.g. Google Ads, Meta Pixel)",
|
|
952
|
-
cookies: ["ad_storage", "ad_user_data", "ad_personalization"]
|
|
953
|
-
}
|
|
954
|
-
],
|
|
955
|
-
buttons: [
|
|
956
|
-
{ text: "Accept All", cookies: "*", style: "primary", action: "custom" },
|
|
957
|
-
{ text: "Save Preferences", style: "primary", action: "submit" },
|
|
958
|
-
{ text: "Reject All", cookies: "!", style: "primary", action: "custom" }
|
|
959
|
-
]
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
async function resolveProfile(config) {
|
|
963
|
-
const locale = config.core.locale ?? "en";
|
|
964
|
-
const profileId = config.core.profileId ?? 0;
|
|
965
|
-
if (config.api?.enabled) {
|
|
966
|
-
try {
|
|
967
|
-
const base = config.api.baseUrl ?? (typeof window !== "undefined" ? window.location.origin : "");
|
|
968
|
-
const url = \`\${base}/consenti/api/v1/profiles/\${profileId}/\${encodeURIComponent(locale)}\`;
|
|
969
|
-
const apiProfile = await httpRequest(url, {}, config.api.authToken);
|
|
970
|
-
return {
|
|
971
|
-
id: profileId,
|
|
972
|
-
version: apiProfile.version,
|
|
973
|
-
defaultLocale: apiProfile.defaultLocale,
|
|
974
|
-
locales: apiProfile.locales,
|
|
975
|
-
cookies: apiProfile.cookies,
|
|
976
|
-
mainBanner: apiProfile.mainBanner,
|
|
977
|
-
preferenceModal: apiProfile.preferenceModal,
|
|
978
|
-
...apiProfile.gpcBanner != null ? { gpcBanner: apiProfile.gpcBanner } : {}
|
|
979
|
-
};
|
|
980
|
-
} catch {
|
|
981
|
-
console.warn("[Consenti] API profile fetch failed, falling back to local profile");
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
if (profileId > 0) {
|
|
985
|
-
const local = profileRegistry.get(profileId);
|
|
986
|
-
if (local) {
|
|
987
|
-
const resolved = resolveLocale(local.translations ?? {}, locale, local.defaultLocale);
|
|
988
|
-
return {
|
|
989
|
-
id: profileId,
|
|
990
|
-
version: 1,
|
|
991
|
-
defaultLocale: local.defaultLocale,
|
|
992
|
-
locales: Object.keys(local.translations ?? {}),
|
|
993
|
-
cookies: local.cookies ?? [],
|
|
994
|
-
mainBanner: resolved.mainBanner,
|
|
995
|
-
...resolved.gpcBanner ? { gpcBanner: resolved.gpcBanner } : {},
|
|
996
|
-
preferenceModal: resolved.preferenceModal,
|
|
997
|
-
...local.darkMode !== void 0 ? { darkMode: local.darkMode } : {}
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
throw new Error(
|
|
1001
|
-
\`[Consenti] Profile \${profileId} not found. Register it with ConsentiProfile or enable the API.\`
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
|
-
return DEFAULT_PROFILE;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// src/core/gpc.ts
|
|
1008
|
-
function detectGPC() {
|
|
1009
|
-
const nav = safeNavigator();
|
|
1010
|
-
if (!nav) return false;
|
|
1011
|
-
return "globalPrivacyControl" in nav && nav.globalPrivacyControl === true;
|
|
1012
|
-
}
|
|
1013
|
-
function applyGPCToConsent(cookies, currentConsent, mode) {
|
|
1014
|
-
if (!mode || !detectGPC()) return { ...currentConsent };
|
|
1015
|
-
const result = { ...currentConsent };
|
|
1016
|
-
for (const cookie of cookies) {
|
|
1017
|
-
if (cookie.mandatory) continue;
|
|
1018
|
-
if (cookie.listenGpc) {
|
|
1019
|
-
result[cookie.id] = cookie.type === "legitimate_interest" ? "objected" : "denied";
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
for (const cookie of cookies) {
|
|
1023
|
-
if (cookie.mandatory) result[cookie.id] = "granted";
|
|
1024
|
-
}
|
|
1025
|
-
return result;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// src/ui/buttons.ts
|
|
1029
|
-
function buildButton(btn, handlers) {
|
|
1030
|
-
const el = document.createElement("button");
|
|
1031
|
-
el.type = "button";
|
|
1032
|
-
el.className = \`consenti-btn consenti-btn--\${btn.style}\`;
|
|
1033
|
-
el.textContent = btn.text;
|
|
1034
|
-
el.addEventListener("click", () => {
|
|
1035
|
-
switch (btn.action) {
|
|
1036
|
-
case "custom": {
|
|
1037
|
-
const cookies = btn.cookies;
|
|
1038
|
-
if (cookies === "*") {
|
|
1039
|
-
handlers.onGrantAll();
|
|
1040
|
-
} else if (cookies === "!") {
|
|
1041
|
-
handlers.onDenyAll();
|
|
1042
|
-
} else if (Array.isArray(cookies) && cookies.length > 0) {
|
|
1043
|
-
handlers.onGrantSpecific(cookies);
|
|
1044
|
-
}
|
|
1045
|
-
break;
|
|
1046
|
-
}
|
|
1047
|
-
case "submit":
|
|
1048
|
-
handlers.onSubmit();
|
|
1049
|
-
break;
|
|
1050
|
-
case "manage":
|
|
1051
|
-
handlers.onManage();
|
|
1052
|
-
break;
|
|
1053
|
-
case "close":
|
|
1054
|
-
handlers.onClose();
|
|
1055
|
-
break;
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
return el;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// src/ui/overlay.ts
|
|
1062
|
-
function createOverlay(opacity, onClick) {
|
|
1063
|
-
const el = document.createElement("div");
|
|
1064
|
-
el.className = "consenti-overlay";
|
|
1065
|
-
el.style.opacity = String(opacity / 100);
|
|
1066
|
-
el.setAttribute("aria-hidden", "true");
|
|
1067
|
-
if (onClick) {
|
|
1068
|
-
el.addEventListener("click", onClick);
|
|
1069
|
-
}
|
|
1070
|
-
return el;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// src/ui/banner.ts
|
|
1074
|
-
var Banner = class {
|
|
1075
|
-
constructor() {
|
|
1076
|
-
this.el = null;
|
|
1077
|
-
this.overlayEl = null;
|
|
1078
|
-
}
|
|
1079
|
-
/**
|
|
1080
|
-
* Constructs the banner DOM from the given config and returns the root element.
|
|
1081
|
-
* The element is not yet inserted into the document — the caller appends it to
|
|
1082
|
-
* the mount root via \`Renderer.mount()\`.
|
|
1083
|
-
*
|
|
1084
|
-
* @param config - Banner layout and content config from the resolved profile.
|
|
1085
|
-
* @param handlers - Click handlers wired to each button.
|
|
1086
|
-
* @param privacyPolicyUrl - Optional URL appended as a "Privacy Policy" link after the body text.
|
|
1087
|
-
* @param locales - All locale codes available on the profile (for the locale switcher).
|
|
1088
|
-
* @param activeLocale - The currently active locale code.
|
|
1089
|
-
* @param onLocaleSwitch - Callback invoked when the user selects a different locale.
|
|
1090
|
-
* @returns The constructed banner \`<div>\` element.
|
|
1091
|
-
*/
|
|
1092
|
-
build(config, handlers, privacyPolicyUrl, locales, activeLocale, onLocaleSwitch) {
|
|
1093
|
-
const banner = document.createElement("div");
|
|
1094
|
-
banner.id = "consenti-banner";
|
|
1095
|
-
banner.className = \`consenti-banner consenti-banner--\${config.position}\`;
|
|
1096
|
-
banner.setAttribute("role", "region");
|
|
1097
|
-
banner.setAttribute("aria-label", "Cookie consent");
|
|
1098
|
-
banner.setAttribute("aria-live", "polite");
|
|
1099
|
-
const textContainer = document.createElement("div");
|
|
1100
|
-
textContainer.className = \`consenti-banner__text-container\`;
|
|
1101
|
-
const linksContainer = document.createElement("div");
|
|
1102
|
-
linksContainer.className = "consenti-banner__links";
|
|
1103
|
-
if (config.heading) {
|
|
1104
|
-
const headingTag = config.headingTag ?? "h2";
|
|
1105
|
-
const heading = document.createElement(headingTag);
|
|
1106
|
-
heading.id = "consenti-banner-heading";
|
|
1107
|
-
heading.className = "consenti-banner__heading";
|
|
1108
|
-
heading.textContent = config.heading;
|
|
1109
|
-
textContainer.appendChild(heading);
|
|
1110
|
-
}
|
|
1111
|
-
const text = document.createElement("div");
|
|
1112
|
-
text.className = "consenti-banner__text";
|
|
1113
|
-
text.innerHTML = config.htmlText;
|
|
1114
|
-
if (privacyPolicyUrl) {
|
|
1115
|
-
const link = document.createElement("a");
|
|
1116
|
-
link.href = privacyPolicyUrl;
|
|
1117
|
-
link.className = "consenti-banner__link consenti-banner__link--privacy consenti-btn consenti-btn--text";
|
|
1118
|
-
link.textContent = "Privacy Policy";
|
|
1119
|
-
link.setAttribute("target", "_blank");
|
|
1120
|
-
link.setAttribute("rel", "noopener noreferrer");
|
|
1121
|
-
linksContainer.appendChild(link);
|
|
1122
|
-
}
|
|
1123
|
-
textContainer.appendChild(text);
|
|
1124
|
-
const linkButtons = config.buttons.filter((b) => b.action === "link");
|
|
1125
|
-
if (linkButtons.length > 0) {
|
|
1126
|
-
for (const btn of linkButtons) {
|
|
1127
|
-
if (!btn.url) continue;
|
|
1128
|
-
const anchor = document.createElement("a");
|
|
1129
|
-
anchor.href = btn.url;
|
|
1130
|
-
anchor.className = \`consenti-banner__link consenti-btn consenti-btn--\${btn.style ?? "text"}\`;
|
|
1131
|
-
anchor.textContent = btn.text;
|
|
1132
|
-
anchor.setAttribute("target", "_blank");
|
|
1133
|
-
anchor.setAttribute("rel", "noopener noreferrer");
|
|
1134
|
-
linksContainer.appendChild(anchor);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
if (linkButtons.length > 0 || privacyPolicyUrl) {
|
|
1138
|
-
textContainer.appendChild(linksContainer);
|
|
1139
|
-
}
|
|
1140
|
-
banner.appendChild(textContainer);
|
|
1141
|
-
const actionButtons = config.buttons.filter((b) => b.action !== "link");
|
|
1142
|
-
if (actionButtons.length > 0) {
|
|
1143
|
-
const btns = document.createElement("div");
|
|
1144
|
-
btns.className = "consenti-banner__buttons";
|
|
1145
|
-
for (const btn of actionButtons) {
|
|
1146
|
-
btns.appendChild(buildButton(btn, handlers));
|
|
1147
|
-
}
|
|
1148
|
-
banner.appendChild(btns);
|
|
1149
|
-
}
|
|
1150
|
-
const hasClose = config.showClose;
|
|
1151
|
-
const hasLocaleSwitcher = config.showLocaleSwitcher && locales && locales.length > 1;
|
|
1152
|
-
if (hasClose || hasLocaleSwitcher) {
|
|
1153
|
-
const controls = document.createElement("div");
|
|
1154
|
-
controls.className = "consenti-banner__controls";
|
|
1155
|
-
if (hasLocaleSwitcher && locales && onLocaleSwitch) {
|
|
1156
|
-
controls.appendChild(buildLocaleSwitcher(locales, activeLocale, onLocaleSwitch));
|
|
1157
|
-
}
|
|
1158
|
-
if (hasClose) {
|
|
1159
|
-
const close = document.createElement("button");
|
|
1160
|
-
close.type = "button";
|
|
1161
|
-
close.className = "consenti-banner__close consenti-btn consenti-btn--close";
|
|
1162
|
-
close.setAttribute("aria-label", "Close cookie banner");
|
|
1163
|
-
close.textContent = "\\xD7";
|
|
1164
|
-
close.addEventListener("click", handlers.onClose);
|
|
1165
|
-
controls.appendChild(close);
|
|
1166
|
-
}
|
|
1167
|
-
banner.appendChild(controls);
|
|
1168
|
-
}
|
|
1169
|
-
if (config.overlayOpacity && config.overlayOpacity > 0) {
|
|
1170
|
-
this.overlayEl = createOverlay(config.overlayOpacity);
|
|
1171
|
-
}
|
|
1172
|
-
this.el = banner;
|
|
1173
|
-
return banner;
|
|
1174
|
-
}
|
|
1175
|
-
/** Returns the overlay element (if \`overlayOpacity > 0\`), or \`null\`. */
|
|
1176
|
-
getOverlay() {
|
|
1177
|
-
return this.overlayEl;
|
|
1178
|
-
}
|
|
1179
|
-
/** Makes the banner and its overlay visible after a \`hide()\` call. */
|
|
1180
|
-
show() {
|
|
1181
|
-
if (this.el) this.el.style.display = "";
|
|
1182
|
-
if (this.overlayEl) this.overlayEl.style.display = "";
|
|
1183
|
-
}
|
|
1184
|
-
/** Hides the banner and its overlay without removing them from the DOM. */
|
|
1185
|
-
hide() {
|
|
1186
|
-
if (this.el) this.el.style.display = "none";
|
|
1187
|
-
if (this.overlayEl) this.overlayEl.style.display = "none";
|
|
1188
|
-
}
|
|
1189
|
-
/** Removes the banner and overlay from the DOM entirely. */
|
|
1190
|
-
remove() {
|
|
1191
|
-
this.el?.remove();
|
|
1192
|
-
this.overlayEl?.remove();
|
|
1193
|
-
this.el = null;
|
|
1194
|
-
this.overlayEl = null;
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
function buildLocaleSwitcher(locales, activeLocale, onSwitch) {
|
|
1198
|
-
const wrapper = document.createElement("div");
|
|
1199
|
-
wrapper.className = "consenti-locale-switcher";
|
|
1200
|
-
const btn = document.createElement("button");
|
|
1201
|
-
btn.type = "button";
|
|
1202
|
-
btn.className = "consenti-locale-switcher__btn consenti-btn consenti-btn--close";
|
|
1203
|
-
btn.setAttribute("aria-label", "Switch language");
|
|
1204
|
-
btn.setAttribute("aria-haspopup", "listbox");
|
|
1205
|
-
btn.textContent = "\\u{1F310}";
|
|
1206
|
-
const list = document.createElement("ul");
|
|
1207
|
-
list.className = "consenti-locale-switcher__list";
|
|
1208
|
-
list.setAttribute("role", "listbox");
|
|
1209
|
-
list.setAttribute("aria-label", "Select language");
|
|
1210
|
-
list.hidden = true;
|
|
1211
|
-
for (const locale of locales) {
|
|
1212
|
-
const item = document.createElement("li");
|
|
1213
|
-
item.setAttribute("role", "option");
|
|
1214
|
-
item.setAttribute("aria-selected", String(locale === activeLocale));
|
|
1215
|
-
const localeBtn = document.createElement("button");
|
|
1216
|
-
localeBtn.type = "button";
|
|
1217
|
-
localeBtn.className = \`consenti-locale-switcher__option\${locale === activeLocale ? " consenti-locale-switcher__option--active" : ""}\`;
|
|
1218
|
-
localeBtn.textContent = locale;
|
|
1219
|
-
localeBtn.addEventListener("click", (e) => {
|
|
1220
|
-
e.stopPropagation();
|
|
1221
|
-
list.hidden = true;
|
|
1222
|
-
if (locale !== activeLocale) onSwitch(locale);
|
|
1223
|
-
});
|
|
1224
|
-
item.appendChild(localeBtn);
|
|
1225
|
-
list.appendChild(item);
|
|
1226
|
-
}
|
|
1227
|
-
btn.addEventListener("click", (e) => {
|
|
1228
|
-
e.stopPropagation();
|
|
1229
|
-
list.hidden = !list.hidden;
|
|
1230
|
-
});
|
|
1231
|
-
document.addEventListener("click", () => {
|
|
1232
|
-
list.hidden = true;
|
|
1233
|
-
}, { capture: true, once: false });
|
|
1234
|
-
wrapper.appendChild(btn);
|
|
1235
|
-
wrapper.appendChild(list);
|
|
1236
|
-
return wrapper;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// src/ui/focus-trap.ts
|
|
1240
|
-
var FOCUSABLE_SELECTORS = [
|
|
1241
|
-
"a[href]",
|
|
1242
|
-
"button:not([disabled])",
|
|
1243
|
-
"input:not([disabled])",
|
|
1244
|
-
"select:not([disabled])",
|
|
1245
|
-
"textarea:not([disabled])",
|
|
1246
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
1247
|
-
'[contenteditable="true"]'
|
|
1248
|
-
].join(", ");
|
|
1249
|
-
function trapFocus(container) {
|
|
1250
|
-
const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS)).filter(
|
|
1251
|
-
(el) => !el.closest('[aria-hidden="true"]')
|
|
1252
|
-
);
|
|
1253
|
-
const handleKeydown = (e) => {
|
|
1254
|
-
if (e.key !== "Tab") return;
|
|
1255
|
-
const els = getFocusable();
|
|
1256
|
-
if (els.length === 0) return;
|
|
1257
|
-
const first = els[0];
|
|
1258
|
-
const last = els[els.length - 1];
|
|
1259
|
-
if (e.shiftKey) {
|
|
1260
|
-
if (document.activeElement === first || document.activeElement === container) {
|
|
1261
|
-
e.preventDefault();
|
|
1262
|
-
last.focus();
|
|
1263
|
-
}
|
|
1264
|
-
} else {
|
|
1265
|
-
if (document.activeElement === last) {
|
|
1266
|
-
e.preventDefault();
|
|
1267
|
-
first.focus();
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
};
|
|
1271
|
-
const handleEscape = (e) => {
|
|
1272
|
-
if (e.key === "Escape") {
|
|
1273
|
-
container.dispatchEvent(new CustomEvent("consenti:closeRequest", { bubbles: true }));
|
|
1274
|
-
}
|
|
1275
|
-
};
|
|
1276
|
-
document.addEventListener("keydown", handleKeydown);
|
|
1277
|
-
document.addEventListener("keydown", handleEscape);
|
|
1278
|
-
getFocusable()[0]?.focus();
|
|
1279
|
-
return () => {
|
|
1280
|
-
document.removeEventListener("keydown", handleKeydown);
|
|
1281
|
-
document.removeEventListener("keydown", handleEscape);
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
// src/ui/modal.ts
|
|
1286
|
-
var Modal = class {
|
|
1287
|
-
constructor() {
|
|
1288
|
-
this.el = null;
|
|
1289
|
-
this.triggerEl = null;
|
|
1290
|
-
this.trapCleanup = null;
|
|
1291
|
-
/** Map of category ID → toggle button element, used to read state on submit. */
|
|
1292
|
-
this.toggleMap = /* @__PURE__ */ new Map();
|
|
1293
|
-
this.receiptCheckbox = null;
|
|
1294
|
-
/** Live toggle state — updated on each click so \`getToggleConsent()\` reads the latest. */
|
|
1295
|
-
this.toggleState = /* @__PURE__ */ new Map();
|
|
1296
|
-
/** When \`true\`, the modal wrapper is passthrough (overlayOpacity === 0). */
|
|
1297
|
-
this.passthrough = false;
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Constructs the modal DOM from the given config.
|
|
1301
|
-
* The modal is not inserted into the document until \`open()\` is called.
|
|
1302
|
-
*
|
|
1303
|
-
* @param config - Modal layout and content config from the resolved profile.
|
|
1304
|
-
* @param initialConsent - Current stored consent, used to pre-set toggle states.
|
|
1305
|
-
* @param handlers - Click handlers wired to each button.
|
|
1306
|
-
* @param allowReceipt - When \`true\`, renders the consent receipt download checkbox.
|
|
1307
|
-
* @param dpdpaConfig - DPDPA grievance notice config.
|
|
1308
|
-
* @param locales - All available locale codes (for locale switcher).
|
|
1309
|
-
* @param activeLocale - The currently active locale code.
|
|
1310
|
-
* @param onLocaleSwitch - Callback invoked when the user selects a different locale.
|
|
1311
|
-
*/
|
|
1312
|
-
build(config, initialConsent, handlers, allowReceipt, dpdpaConfig, locales, activeLocale, onLocaleSwitch) {
|
|
1313
|
-
const position = config.position ?? "center";
|
|
1314
|
-
const wrapper = document.createElement("div");
|
|
1315
|
-
wrapper.id = "consenti-modal";
|
|
1316
|
-
wrapper.className = \`consenti-modal consenti-modal--\${position}\`;
|
|
1317
|
-
wrapper.setAttribute("role", "dialog");
|
|
1318
|
-
wrapper.setAttribute("aria-modal", "true");
|
|
1319
|
-
const overlayOpacity = config.overlayOpacity ?? 50;
|
|
1320
|
-
this.passthrough = overlayOpacity === 0;
|
|
1321
|
-
if (overlayOpacity > 0) {
|
|
1322
|
-
const overlayClickHandler = config.persistent ? void 0 : () => handlers.onClose();
|
|
1323
|
-
const overlay = createOverlay(overlayOpacity, overlayClickHandler);
|
|
1324
|
-
overlay.className = "consenti-modal__overlay";
|
|
1325
|
-
wrapper.appendChild(overlay);
|
|
1326
|
-
} else {
|
|
1327
|
-
wrapper.classList.add("consenti-modal--passthrough");
|
|
1328
|
-
}
|
|
1329
|
-
const container = document.createElement("div");
|
|
1330
|
-
container.className = "consenti-modal__container";
|
|
1331
|
-
const header = document.createElement("div");
|
|
1332
|
-
header.className = "consenti-modal__header";
|
|
1333
|
-
if (config.heading) {
|
|
1334
|
-
const headingTag = config.headingTag ?? "h2";
|
|
1335
|
-
const heading = document.createElement(headingTag);
|
|
1336
|
-
heading.id = "consenti-modal-heading";
|
|
1337
|
-
heading.className = "consenti-modal__heading";
|
|
1338
|
-
heading.textContent = config.heading;
|
|
1339
|
-
header.appendChild(heading);
|
|
1340
|
-
wrapper.setAttribute("aria-labelledby", "consenti-modal-heading");
|
|
1341
|
-
}
|
|
1342
|
-
const hasClose = config.showClose !== false;
|
|
1343
|
-
const hasLocaleSwitcher = config.showLocaleSwitcher && locales && locales.length > 1;
|
|
1344
|
-
if (hasLocaleSwitcher || hasClose) {
|
|
1345
|
-
const headerControls = document.createElement("div");
|
|
1346
|
-
headerControls.className = "consenti-modal__header-controls";
|
|
1347
|
-
if (hasLocaleSwitcher && locales && onLocaleSwitch) {
|
|
1348
|
-
headerControls.appendChild(buildModalLocaleSwitcher(locales, activeLocale, onLocaleSwitch));
|
|
1349
|
-
}
|
|
1350
|
-
if (hasClose) {
|
|
1351
|
-
const close = document.createElement("button");
|
|
1352
|
-
close.type = "button";
|
|
1353
|
-
close.className = "consenti-modal__close consenti-btn consenti-btn--close";
|
|
1354
|
-
close.setAttribute("aria-label", "Close preference modal");
|
|
1355
|
-
close.textContent = "\\xD7";
|
|
1356
|
-
close.addEventListener("click", handlers.onClose);
|
|
1357
|
-
headerControls.appendChild(close);
|
|
1358
|
-
}
|
|
1359
|
-
header.appendChild(headerControls);
|
|
1360
|
-
}
|
|
1361
|
-
container.appendChild(header);
|
|
1362
|
-
if (config.subheading) {
|
|
1363
|
-
const sub = document.createElement("p");
|
|
1364
|
-
sub.className = "consenti-modal__subheading";
|
|
1365
|
-
sub.textContent = config.subheading;
|
|
1366
|
-
container.appendChild(sub);
|
|
1367
|
-
}
|
|
1368
|
-
if (config.htmlText) {
|
|
1369
|
-
const desc = document.createElement("div");
|
|
1370
|
-
desc.className = "consenti-modal__text";
|
|
1371
|
-
desc.innerHTML = config.htmlText;
|
|
1372
|
-
container.appendChild(desc);
|
|
1373
|
-
}
|
|
1374
|
-
const linkButtons = config.buttons.filter((b) => b.action === "link");
|
|
1375
|
-
if (linkButtons.length > 0) {
|
|
1376
|
-
const linksContainer = document.createElement("div");
|
|
1377
|
-
linksContainer.className = "consenti-modal__links";
|
|
1378
|
-
for (const btn of linkButtons) {
|
|
1379
|
-
if (!btn.url) continue;
|
|
1380
|
-
const anchor = document.createElement("a");
|
|
1381
|
-
anchor.href = btn.url;
|
|
1382
|
-
anchor.className = \`consenti-modal__link consenti-btn consenti-btn--\${btn.style ?? "text"}\`;
|
|
1383
|
-
anchor.textContent = btn.text;
|
|
1384
|
-
anchor.setAttribute("target", "_blank");
|
|
1385
|
-
anchor.setAttribute("rel", "noopener noreferrer");
|
|
1386
|
-
linksContainer.appendChild(anchor);
|
|
1387
|
-
}
|
|
1388
|
-
container.appendChild(linksContainer);
|
|
1389
|
-
}
|
|
1390
|
-
const body = document.createElement("div");
|
|
1391
|
-
body.className = "consenti-modal__body";
|
|
1392
|
-
const categories = document.createElement("div");
|
|
1393
|
-
categories.className = "consenti-modal__categories";
|
|
1394
|
-
for (const cat of config.categories) {
|
|
1395
|
-
const catEl = this.buildCategory(cat, initialConsent);
|
|
1396
|
-
categories.appendChild(catEl);
|
|
1397
|
-
}
|
|
1398
|
-
body.appendChild(categories);
|
|
1399
|
-
container.appendChild(body);
|
|
1400
|
-
if (allowReceipt) {
|
|
1401
|
-
container.appendChild(this.buildReceiptOption());
|
|
1402
|
-
}
|
|
1403
|
-
if (dpdpaConfig) {
|
|
1404
|
-
const notice = document.createElement("div");
|
|
1405
|
-
notice.className = "consenti-modal__dpdpa-notice";
|
|
1406
|
-
notice.innerHTML = [
|
|
1407
|
-
\`<strong>Data Fiduciary:</strong> \${dpdpaConfig.dataFiduciary}.\`,
|
|
1408
|
-
dpdpaConfig.purposeDescription ? \` <strong>Purpose:</strong> \${dpdpaConfig.purposeDescription}.\` : "",
|
|
1409
|
-
\` You may withdraw consent at any time or contact our Grievance Officer at\`,
|
|
1410
|
-
\` <a href="mailto:\${dpdpaConfig.grievanceEmail}">\${dpdpaConfig.grievanceEmail}</a>.\`
|
|
1411
|
-
].join("");
|
|
1412
|
-
container.appendChild(notice);
|
|
1413
|
-
}
|
|
1414
|
-
const btns = document.createElement("div");
|
|
1415
|
-
btns.className = "consenti-modal__buttons";
|
|
1416
|
-
for (const btn of config.buttons.filter((b) => b.action !== "link")) {
|
|
1417
|
-
btns.appendChild(buildButton(btn, handlers));
|
|
1418
|
-
}
|
|
1419
|
-
container.appendChild(btns);
|
|
1420
|
-
wrapper.appendChild(container);
|
|
1421
|
-
wrapper.addEventListener("consenti:closeRequest", () => handlers.onClose());
|
|
1422
|
-
this.el = wrapper;
|
|
1423
|
-
}
|
|
1424
|
-
buildCategory(cat, initialConsent) {
|
|
1425
|
-
const isLI = cat.type === "legitimate_interest";
|
|
1426
|
-
const isMandatory = cat.mandatory === true;
|
|
1427
|
-
let defaultOn;
|
|
1428
|
-
if (isMandatory) {
|
|
1429
|
-
defaultOn = true;
|
|
1430
|
-
} else if (isLI) {
|
|
1431
|
-
defaultOn = initialConsent[cat.cookies[0] ?? ""] !== "objected";
|
|
1432
|
-
} else {
|
|
1433
|
-
defaultOn = initialConsent[cat.cookies[0] ?? ""] === "granted";
|
|
1434
|
-
}
|
|
1435
|
-
this.toggleState.set(cat.id, defaultOn);
|
|
1436
|
-
const wrapper = document.createElement("div");
|
|
1437
|
-
wrapper.className = \`consenti-category\${isMandatory ? " consenti-category--mandatory" : ""}\`;
|
|
1438
|
-
const header = document.createElement("div");
|
|
1439
|
-
header.className = "consenti-category__header";
|
|
1440
|
-
const headingTag = cat.headingTag ?? "h3";
|
|
1441
|
-
const heading = document.createElement(headingTag);
|
|
1442
|
-
heading.id = \`consenti-cat-\${cat.id}-heading\`;
|
|
1443
|
-
heading.className = "consenti-category__heading";
|
|
1444
|
-
heading.textContent = cat.heading;
|
|
1445
|
-
header.appendChild(heading);
|
|
1446
|
-
const toggle = document.createElement("button");
|
|
1447
|
-
toggle.type = "button";
|
|
1448
|
-
toggle.className = \`consenti-category__toggle\${isMandatory ? " consenti-category__toggle--mandatory" : ""}\`;
|
|
1449
|
-
toggle.setAttribute("role", "switch");
|
|
1450
|
-
toggle.setAttribute("aria-checked", String(defaultOn));
|
|
1451
|
-
toggle.setAttribute("aria-labelledby", \`consenti-cat-\${cat.id}-heading\`);
|
|
1452
|
-
if (isMandatory) toggle.setAttribute("aria-disabled", "true");
|
|
1453
|
-
const knob = document.createElement("span");
|
|
1454
|
-
knob.className = "consenti-category__toggle-knob";
|
|
1455
|
-
knob.setAttribute("aria-hidden", "true");
|
|
1456
|
-
toggle.appendChild(knob);
|
|
1457
|
-
if (!isMandatory) {
|
|
1458
|
-
toggle.addEventListener("click", () => {
|
|
1459
|
-
const current = this.toggleState.get(cat.id) ?? false;
|
|
1460
|
-
const next = !current;
|
|
1461
|
-
this.toggleState.set(cat.id, next);
|
|
1462
|
-
toggle.setAttribute("aria-checked", String(next));
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
this.toggleMap.set(cat.id, toggle);
|
|
1466
|
-
header.appendChild(toggle);
|
|
1467
|
-
wrapper.appendChild(header);
|
|
1468
|
-
const text = document.createElement("div");
|
|
1469
|
-
text.className = "consenti-category__text";
|
|
1470
|
-
text.innerHTML = cat.htmlText;
|
|
1471
|
-
wrapper.appendChild(text);
|
|
1472
|
-
if (isLI && cat.legitimateInterest?.description) {
|
|
1473
|
-
const li = document.createElement("p");
|
|
1474
|
-
li.className = "consenti-category__li-desc";
|
|
1475
|
-
li.textContent = cat.legitimateInterest.description;
|
|
1476
|
-
wrapper.appendChild(li);
|
|
1477
|
-
}
|
|
1478
|
-
return wrapper;
|
|
1479
|
-
}
|
|
1480
|
-
buildReceiptOption() {
|
|
1481
|
-
const wrapper = document.createElement("div");
|
|
1482
|
-
wrapper.className = "consenti-modal__receipt-option";
|
|
1483
|
-
const label = document.createElement("label");
|
|
1484
|
-
label.className = "consenti-modal__receipt-label";
|
|
1485
|
-
const checkbox = document.createElement("input");
|
|
1486
|
-
checkbox.type = "checkbox";
|
|
1487
|
-
checkbox.className = "consenti-modal__receipt-checkbox";
|
|
1488
|
-
checkbox.id = "consenti-receipt-checkbox";
|
|
1489
|
-
checkbox.setAttribute("aria-describedby", "consenti-receipt-desc");
|
|
1490
|
-
this.receiptCheckbox = checkbox;
|
|
1491
|
-
label.appendChild(checkbox);
|
|
1492
|
-
label.append(" Get a copy of my consent choices (JSON)");
|
|
1493
|
-
wrapper.appendChild(label);
|
|
1494
|
-
const desc = document.createElement("p");
|
|
1495
|
-
desc.id = "consenti-receipt-desc";
|
|
1496
|
-
desc.className = "consenti-modal__receipt-desc";
|
|
1497
|
-
desc.textContent = "A JSON file will be downloaded to your device when you save your preferences.";
|
|
1498
|
-
wrapper.appendChild(desc);
|
|
1499
|
-
return wrapper;
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Appends the modal to \`document.body\` and activates the focus trap.
|
|
1503
|
-
* The receipt checkbox (if present) is reset to unchecked.
|
|
1504
|
-
*
|
|
1505
|
-
* @param triggerEl - The element that opened the modal. Focus returns here on \`close()\`.
|
|
1506
|
-
*/
|
|
1507
|
-
open(triggerEl) {
|
|
1508
|
-
if (!this.el) return;
|
|
1509
|
-
this.triggerEl = triggerEl ?? document.activeElement;
|
|
1510
|
-
if (this.receiptCheckbox) this.receiptCheckbox.checked = false;
|
|
1511
|
-
document.body.appendChild(this.el);
|
|
1512
|
-
if (!this.passthrough) {
|
|
1513
|
-
this.trapCleanup = trapFocus(this.el);
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Removes the modal from \`document.body\`, deactivates the focus trap,
|
|
1518
|
-
* and returns focus to \`triggerEl\`.
|
|
1519
|
-
*/
|
|
1520
|
-
close() {
|
|
1521
|
-
this.trapCleanup?.();
|
|
1522
|
-
this.trapCleanup = null;
|
|
1523
|
-
this.el?.remove();
|
|
1524
|
-
this.triggerEl?.focus();
|
|
1525
|
-
this.triggerEl = null;
|
|
1526
|
-
}
|
|
1527
|
-
/** Returns \`true\` if the modal is currently mounted in the document. */
|
|
1528
|
-
isOpen() {
|
|
1529
|
-
return this.el?.isConnected === true;
|
|
1530
|
-
}
|
|
1531
|
-
/** Returns \`true\` if the receipt download checkbox is checked at the time of calling. */
|
|
1532
|
-
isReceiptRequested() {
|
|
1533
|
-
return this.receiptCheckbox?.checked ?? false;
|
|
1534
|
-
}
|
|
1535
|
-
/**
|
|
1536
|
-
* Reads the current toggle state map and converts it to a \`ConsentValue\`.
|
|
1537
|
-
*
|
|
1538
|
-
* Called by the submit handler to build the consent object before writing to storage.
|
|
1539
|
-
*
|
|
1540
|
-
* @param categories - The category list from the resolved profile (provides cookie IDs and type).
|
|
1541
|
-
*/
|
|
1542
|
-
getToggleConsent(categories) {
|
|
1543
|
-
const consent = {};
|
|
1544
|
-
for (const cat of categories) {
|
|
1545
|
-
const isLI = cat.type === "legitimate_interest";
|
|
1546
|
-
const isMandatory = cat.mandatory === true;
|
|
1547
|
-
const on = this.toggleState.get(cat.id) ?? false;
|
|
1548
|
-
for (const cookieId of cat.cookies) {
|
|
1549
|
-
if (isMandatory) {
|
|
1550
|
-
consent[cookieId] = "granted";
|
|
1551
|
-
} else if (isLI) {
|
|
1552
|
-
consent[cookieId] = on ? "granted" : "objected";
|
|
1553
|
-
} else {
|
|
1554
|
-
consent[cookieId] = on ? "granted" : "denied";
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
return consent;
|
|
1559
|
-
}
|
|
1560
|
-
/** Removes the modal element from the DOM and clears all internal state. */
|
|
1561
|
-
destroy() {
|
|
1562
|
-
this.trapCleanup?.();
|
|
1563
|
-
this.el?.remove();
|
|
1564
|
-
this.el = null;
|
|
1565
|
-
this.toggleMap.clear();
|
|
1566
|
-
this.toggleState.clear();
|
|
1567
|
-
this.receiptCheckbox = null;
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
function buildModalLocaleSwitcher(locales, activeLocale, onSwitch) {
|
|
1571
|
-
const wrapper = document.createElement("div");
|
|
1572
|
-
wrapper.className = "consenti-locale-switcher consenti-locale-switcher--modal";
|
|
1573
|
-
const btn = document.createElement("button");
|
|
1574
|
-
btn.type = "button";
|
|
1575
|
-
btn.className = "consenti-locale-switcher__btn consenti-btn consenti-btn--close";
|
|
1576
|
-
btn.setAttribute("aria-label", "Switch language");
|
|
1577
|
-
btn.setAttribute("aria-haspopup", "listbox");
|
|
1578
|
-
btn.textContent = "\\u{1F310}";
|
|
1579
|
-
const list = document.createElement("ul");
|
|
1580
|
-
list.className = "consenti-locale-switcher__list";
|
|
1581
|
-
list.setAttribute("role", "listbox");
|
|
1582
|
-
list.setAttribute("aria-label", "Select language");
|
|
1583
|
-
list.hidden = true;
|
|
1584
|
-
for (const locale of locales) {
|
|
1585
|
-
const item = document.createElement("li");
|
|
1586
|
-
item.setAttribute("role", "option");
|
|
1587
|
-
item.setAttribute("aria-selected", String(locale === activeLocale));
|
|
1588
|
-
const localeBtn = document.createElement("button");
|
|
1589
|
-
localeBtn.type = "button";
|
|
1590
|
-
localeBtn.className = \`consenti-locale-switcher__option\${locale === activeLocale ? " consenti-locale-switcher__option--active" : ""}\`;
|
|
1591
|
-
localeBtn.textContent = locale;
|
|
1592
|
-
localeBtn.addEventListener("click", (e) => {
|
|
1593
|
-
e.stopPropagation();
|
|
1594
|
-
list.hidden = true;
|
|
1595
|
-
if (locale !== activeLocale) onSwitch(locale);
|
|
1596
|
-
});
|
|
1597
|
-
item.appendChild(localeBtn);
|
|
1598
|
-
list.appendChild(item);
|
|
1599
|
-
}
|
|
1600
|
-
btn.addEventListener("click", (e) => {
|
|
1601
|
-
e.stopPropagation();
|
|
1602
|
-
list.hidden = !list.hidden;
|
|
1603
|
-
});
|
|
1604
|
-
document.addEventListener("click", () => {
|
|
1605
|
-
list.hidden = true;
|
|
1606
|
-
}, { capture: true, once: false });
|
|
1607
|
-
wrapper.appendChild(btn);
|
|
1608
|
-
wrapper.appendChild(list);
|
|
1609
|
-
return wrapper;
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// src/ui/renderer.ts
|
|
1613
|
-
var ROOT_ID = "consenti-root";
|
|
1614
|
-
var Renderer = class {
|
|
1615
|
-
constructor() {
|
|
1616
|
-
this.root = null;
|
|
1617
|
-
this.styleEl = null;
|
|
1618
|
-
}
|
|
1619
|
-
/**
|
|
1620
|
-
* Returns the mount-point element, creating \`#consenti-root\` if needed.
|
|
1621
|
-
*
|
|
1622
|
-
* When \`rootEl\` is supplied:
|
|
1623
|
-
* - \`string\` → resolved via \`document.querySelector\`. Throws if no match.
|
|
1624
|
-
* - \`HTMLElement\` → used directly. Throws if not attached to the document.
|
|
1625
|
-
* On subsequent calls the previously stored root is returned immediately.
|
|
1626
|
-
*
|
|
1627
|
-
* @throws \`Error\` if called in SSR or if a supplied \`rootEl\` cannot be resolved.
|
|
1628
|
-
*/
|
|
1629
|
-
mount(rootEl) {
|
|
1630
|
-
if (!isClient()) throw new Error("[Consenti] Renderer.mount() called in SSR context");
|
|
1631
|
-
if (this.root) return this.root;
|
|
1632
|
-
if (rootEl !== void 0) {
|
|
1633
|
-
if (typeof rootEl === "string") {
|
|
1634
|
-
const found = document.querySelector(rootEl);
|
|
1635
|
-
if (!found) throw new Error(\`[Consenti] rootEl selector "\${rootEl}" did not match any element.\`);
|
|
1636
|
-
this.root = found;
|
|
1637
|
-
} else {
|
|
1638
|
-
if (!document.contains(rootEl)) throw new Error("[Consenti] rootEl element is not attached to the document.");
|
|
1639
|
-
this.root = rootEl;
|
|
1640
|
-
}
|
|
1641
|
-
return this.root;
|
|
1642
|
-
}
|
|
1643
|
-
let root = document.getElementById(ROOT_ID);
|
|
1644
|
-
if (!root) {
|
|
1645
|
-
root = document.createElement("div");
|
|
1646
|
-
root.id = ROOT_ID;
|
|
1647
|
-
document.body.appendChild(root);
|
|
1648
|
-
}
|
|
1649
|
-
this.root = root;
|
|
1650
|
-
return root;
|
|
1651
|
-
}
|
|
1652
|
-
/**
|
|
1653
|
-
* Injects a \`<style id="consenti-styles">\` tag into \`<head>\` with the given CSS string.
|
|
1654
|
-
* No-ops if the style element already exists (idempotent).
|
|
1655
|
-
*
|
|
1656
|
-
* @param css - The CSS content to inject.
|
|
1657
|
-
*/
|
|
1658
|
-
injectStyles(css) {
|
|
1659
|
-
if (!isClient()) return;
|
|
1660
|
-
if (this.styleEl) return;
|
|
1661
|
-
const style = document.createElement("style");
|
|
1662
|
-
style.id = "consenti-styles";
|
|
1663
|
-
style.textContent = css;
|
|
1664
|
-
document.head.appendChild(style);
|
|
1665
|
-
this.styleEl = style;
|
|
1666
|
-
}
|
|
1667
|
-
/** Removes \`#consenti-root\` and the injected \`<style>\` tag from the document. */
|
|
1668
|
-
unmount() {
|
|
1669
|
-
this.root?.remove();
|
|
1670
|
-
this.styleEl?.remove();
|
|
1671
|
-
this.root = null;
|
|
1672
|
-
this.styleEl = null;
|
|
1673
|
-
}
|
|
1674
|
-
/**
|
|
1675
|
-
* Announces a message to screen readers via a visually-hidden \`aria-live="polite"\` region.
|
|
1676
|
-
*
|
|
1677
|
-
* The region is created on first call and reused. The message is cleared then
|
|
1678
|
-
* re-set via \`requestAnimationFrame\` to guarantee the change is detected by
|
|
1679
|
-
* AT even if the same string is announced twice in a row.
|
|
1680
|
-
*
|
|
1681
|
-
* @param message - The message to announce (plain text, not HTML).
|
|
1682
|
-
*/
|
|
1683
|
-
announceToScreenReader(message) {
|
|
1684
|
-
if (!isClient()) return;
|
|
1685
|
-
let announcer = document.getElementById("consenti-announcer");
|
|
1686
|
-
if (!announcer) {
|
|
1687
|
-
announcer = document.createElement("div");
|
|
1688
|
-
announcer.id = "consenti-announcer";
|
|
1689
|
-
announcer.setAttribute("role", "status");
|
|
1690
|
-
announcer.setAttribute("aria-live", "polite");
|
|
1691
|
-
announcer.setAttribute("aria-atomic", "true");
|
|
1692
|
-
announcer.style.cssText = "position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;";
|
|
1693
|
-
document.body.appendChild(announcer);
|
|
1694
|
-
}
|
|
1695
|
-
announcer.textContent = "";
|
|
1696
|
-
requestAnimationFrame(() => {
|
|
1697
|
-
if (announcer) announcer.textContent = message;
|
|
1698
|
-
});
|
|
1699
|
-
}
|
|
1700
|
-
};
|
|
1701
|
-
|
|
1702
|
-
// src/core/consenti-setup.ts
|
|
1703
|
-
var ConsentiSetup = class {
|
|
1704
|
-
/**
|
|
1705
|
-
* Creates and initialises the consent widget.
|
|
1706
|
-
*
|
|
1707
|
-
* Construction is synchronous; the async \`init()\` is launched in the background.
|
|
1708
|
-
* Use \`await widget.ready\` or \`widget.onReady(cb)\` to run code after init completes.
|
|
1709
|
-
*
|
|
1710
|
-
* @param config - Widget configuration. See \`ConsentiConfig\` for all options.
|
|
1711
|
-
*/
|
|
1712
|
-
constructor(config) {
|
|
1713
|
-
this.config = config;
|
|
1714
|
-
this.initialized = false;
|
|
1715
|
-
this.initializing = false;
|
|
1716
|
-
this.profile = null;
|
|
1717
|
-
this.consentStore = null;
|
|
1718
|
-
this.eventBus = null;
|
|
1719
|
-
this.banner = null;
|
|
1720
|
-
this.modal = null;
|
|
1721
|
-
this.renderer = null;
|
|
1722
|
-
this.broadcastChannel = new ConsentiChannel();
|
|
1723
|
-
this.plugins = [];
|
|
1724
|
-
/** Tracks which banner variant is currently visible, or null when hidden. */
|
|
1725
|
-
this._bannerVariant = null;
|
|
1726
|
-
this.readyPromise = new Promise((resolve) => {
|
|
1727
|
-
this.readyResolve = resolve;
|
|
1728
|
-
});
|
|
1729
|
-
if (!isClient()) return;
|
|
1730
|
-
if (config.api?.enabled && config.core.storage === "localStorage") {
|
|
1731
|
-
console.warn(
|
|
1732
|
-
"[Consenti] localStorage storage mode cannot be used with API mode (API sets cookies via Set-Cookie header). Falling back to cookie storage."
|
|
1733
|
-
);
|
|
1734
|
-
config.core.storage = "cookie";
|
|
1735
|
-
}
|
|
1736
|
-
if (config.autoInit !== false) {
|
|
1737
|
-
this.init().catch((err) => console.warn("[Consenti] Init error:", err));
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
profileId() {
|
|
1741
|
-
return this.config.core.profileId ?? 0;
|
|
1742
|
-
}
|
|
1743
|
-
/**
|
|
1744
|
-
* Starts widget initialisation. Called automatically unless \`autoInit: false\` is set.
|
|
1745
|
-
* Can be called explicitly after construction when \`autoInit: false\`, or after \`destroy()\`
|
|
1746
|
-
* to re-initialise the same instance.
|
|
1747
|
-
*/
|
|
1748
|
-
async init() {
|
|
1749
|
-
if (!isClient()) return;
|
|
1750
|
-
if (this.initializing) return;
|
|
1751
|
-
this.initializing = true;
|
|
1752
|
-
this.initialized = false;
|
|
1753
|
-
this.readyPromise = new Promise((resolve) => {
|
|
1754
|
-
this.readyResolve = resolve;
|
|
1755
|
-
});
|
|
1756
|
-
try {
|
|
1757
|
-
const storageMode = this.config.core.storage ?? "cookie";
|
|
1758
|
-
const userId = this.config.core.userId ?? ConsentStore.getOrCreateUserId(this.profileId(), storageMode);
|
|
1759
|
-
this.profile = await resolveProfile(this.config);
|
|
1760
|
-
if (this.config.profileOverride) {
|
|
1761
|
-
this.profile = { ...this.profile, ...this.config.profileOverride };
|
|
1762
|
-
}
|
|
1763
|
-
this.eventBus = new EventBus(this.profileId(), this.config.utils?.gtm);
|
|
1764
|
-
this.consentStore = new ConsentStore(
|
|
1765
|
-
userId,
|
|
1766
|
-
this.profileId(),
|
|
1767
|
-
storageMode,
|
|
1768
|
-
this.config.core.cookieDomains
|
|
1769
|
-
);
|
|
1770
|
-
this.renderer = new Renderer();
|
|
1771
|
-
if (!this.config.core.disableCssTemplate && CONSENTI_CSS) {
|
|
1772
|
-
this.renderer.injectStyles(CONSENTI_CSS);
|
|
1773
|
-
}
|
|
1774
|
-
this.banner = new Banner();
|
|
1775
|
-
this.modal = new Modal();
|
|
1776
|
-
this.applyTheme();
|
|
1777
|
-
this.applyDarkMode();
|
|
1778
|
-
this.broadcastChannel.connect((msg) => this.handleBroadcastMessage(msg));
|
|
1779
|
-
await this.runPlugins();
|
|
1780
|
-
const signingKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
|
|
1781
|
-
const existing = this.config.core.signCookies && signingKey ? await this.consentStore.readAndVerify(signingKey) : this.consentStore.read();
|
|
1782
|
-
const hasExisting = existing !== null;
|
|
1783
|
-
this.eventBus.dispatch("consenti:bannerInitialized", {
|
|
1784
|
-
profileId: this.profileId(),
|
|
1785
|
-
hasExistingConsent: hasExisting
|
|
1786
|
-
});
|
|
1787
|
-
const gpcMode = this.config.core.autoHonorGPC ?? false;
|
|
1788
|
-
if (gpcMode && detectGPC()) {
|
|
1789
|
-
const gpcConsent = applyGPCToConsent(this.profile.cookies, {}, gpcMode);
|
|
1790
|
-
if (gpcMode === "strict") {
|
|
1791
|
-
await this.submitConsent(gpcConsent, true);
|
|
1792
|
-
this.initialized = true;
|
|
1793
|
-
this.readyResolve();
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
if (!hasExisting) {
|
|
1797
|
-
const gpcSigningKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
|
|
1798
|
-
if (this.config.core.signCookies && gpcSigningKey && this.consentStore) {
|
|
1799
|
-
this.lastSignature = await this.consentStore.writeAndSign(gpcConsent, gpcSigningKey);
|
|
1800
|
-
} else {
|
|
1801
|
-
this.consentStore?.write(gpcConsent);
|
|
1802
|
-
}
|
|
1803
|
-
this.broadcastChannel.send({
|
|
1804
|
-
type: "consentUpdated",
|
|
1805
|
-
consent: gpcConsent,
|
|
1806
|
-
profileId: this.profileId()
|
|
1807
|
-
});
|
|
1808
|
-
this.eventBus?.dispatch("consenti:consentSubmitted", {
|
|
1809
|
-
consent: gpcConsent,
|
|
1810
|
-
gpcDetected: true,
|
|
1811
|
-
consentId: crypto.randomUUID(),
|
|
1812
|
-
consentAction: this.deriveAction(gpcConsent, false),
|
|
1813
|
-
pageUrl: window.location.href
|
|
1814
|
-
});
|
|
1815
|
-
this.showBanner(true);
|
|
1816
|
-
this.initialized = true;
|
|
1817
|
-
this.readyResolve();
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
const regulation = this.config.core.regulation ?? "gdpr";
|
|
1822
|
-
if (regulation === "ccpa" && !hasExisting) {
|
|
1823
|
-
const defaultConsent = {};
|
|
1824
|
-
for (const cookie of this.profile.cookies) {
|
|
1825
|
-
defaultConsent[cookie.id] = "granted";
|
|
1826
|
-
}
|
|
1827
|
-
const ccpaSigKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
|
|
1828
|
-
if (this.config.core.signCookies && ccpaSigKey) {
|
|
1829
|
-
await this.consentStore.writeAndSign(defaultConsent, ccpaSigKey);
|
|
1830
|
-
} else {
|
|
1831
|
-
this.consentStore.write(defaultConsent);
|
|
1832
|
-
}
|
|
1833
|
-
this.initialized = true;
|
|
1834
|
-
this.readyResolve();
|
|
1835
|
-
return;
|
|
1836
|
-
}
|
|
1837
|
-
if (regulation === "cpra" && !hasExisting) {
|
|
1838
|
-
const gpcActive = this.config.core.autoHonorGPC && detectGPC();
|
|
1839
|
-
const defaultConsent = {};
|
|
1840
|
-
for (const cookie of this.profile.cookies) {
|
|
1841
|
-
if (cookie.mandatory) {
|
|
1842
|
-
defaultConsent[cookie.id] = "granted";
|
|
1843
|
-
continue;
|
|
1844
|
-
}
|
|
1845
|
-
if (cookie.cpraCategory === "sensitive") {
|
|
1846
|
-
defaultConsent[cookie.id] = "denied";
|
|
1847
|
-
} else if (gpcActive && (cookie.cpraCategory === "sale" || cookie.cpraCategory === "sharing")) {
|
|
1848
|
-
defaultConsent[cookie.id] = "denied";
|
|
1849
|
-
} else {
|
|
1850
|
-
defaultConsent[cookie.id] = "granted";
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
const sigKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
|
|
1854
|
-
if (this.config.core.signCookies && sigKey) {
|
|
1855
|
-
await this.consentStore.writeAndSign(defaultConsent, sigKey);
|
|
1856
|
-
} else {
|
|
1857
|
-
this.consentStore.write(defaultConsent);
|
|
1858
|
-
}
|
|
1859
|
-
this.initialized = true;
|
|
1860
|
-
this.readyResolve();
|
|
1861
|
-
return;
|
|
1862
|
-
}
|
|
1863
|
-
if (!hasExisting) {
|
|
1864
|
-
this.showBanner();
|
|
1865
|
-
}
|
|
1866
|
-
this.initialized = true;
|
|
1867
|
-
this.readyResolve();
|
|
1868
|
-
} finally {
|
|
1869
|
-
this.initializing = false;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
/** Applies the \`consenti-root--dark\` class based on config or profile flag. */
|
|
1873
|
-
applyDarkMode() {
|
|
1874
|
-
if (!this.renderer) return;
|
|
1875
|
-
const isDark = this.config.darkMode ?? this.profile?.darkMode ?? false;
|
|
1876
|
-
const root = this.renderer.mount(this.config.rootEl);
|
|
1877
|
-
if (isDark) {
|
|
1878
|
-
root.classList.add("consenti-root--dark");
|
|
1879
|
-
} else {
|
|
1880
|
-
root.classList.remove("consenti-root--dark");
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Injects CSS custom property overrides supplied via \`core.theme\` into the
|
|
1885
|
-
* Consenti root element so they cascade down to all widget elements.
|
|
1886
|
-
*/
|
|
1887
|
-
applyTheme() {
|
|
1888
|
-
const theme = this.config.core.theme;
|
|
1889
|
-
if (!theme || !this.renderer) return;
|
|
1890
|
-
const map = {
|
|
1891
|
-
"--consenti-color-bg": theme.bgColor,
|
|
1892
|
-
"--consenti-color-text": theme.textColor,
|
|
1893
|
-
"--consenti-color-primary": theme.primaryColor,
|
|
1894
|
-
"--consenti-color-primary-text": theme.primaryTextColor,
|
|
1895
|
-
"--consenti-color-secondary": theme.secondaryColor,
|
|
1896
|
-
"--consenti-color-secondary-text": theme.secondaryTextColor,
|
|
1897
|
-
"--consenti-color-border": theme.borderColor,
|
|
1898
|
-
"--consenti-font-family": theme.fontFamily,
|
|
1899
|
-
"--consenti-font-size-base": theme.fontSizeBase,
|
|
1900
|
-
"--consenti-font-size-heading": theme.fontSizeHeading,
|
|
1901
|
-
"--consenti-border-radius": theme.borderRadius,
|
|
1902
|
-
"--consenti-border-radius-btn": theme.buttonBorderRadius,
|
|
1903
|
-
"--consenti-toggle-bg-on": theme.toggleBgOn,
|
|
1904
|
-
"--consenti-toggle-bg-off": theme.toggleBgOff,
|
|
1905
|
-
"--consenti-color-accent": theme.accentColor,
|
|
1906
|
-
"--consenti-color-accent-text": theme.accentTextColor
|
|
1907
|
-
};
|
|
1908
|
-
const root = this.renderer.mount(this.config.rootEl);
|
|
1909
|
-
for (const [prop, value] of Object.entries(map)) {
|
|
1910
|
-
if (value !== void 0) root.style.setProperty(prop, value);
|
|
1911
|
-
}
|
|
1912
|
-
if (theme.fontSizeMultiplier) {
|
|
1913
|
-
const mult = parseFloat(theme.fontSizeMultiplier);
|
|
1914
|
-
if (!isNaN(mult)) {
|
|
1915
|
-
const baseSize = parseFloat(getComputedStyle(root).getPropertyValue("--consenti-font-size-base") || "14");
|
|
1916
|
-
const headingSize = parseFloat(getComputedStyle(root).getPropertyValue("--consenti-font-size-heading") || "16");
|
|
1917
|
-
root.style.setProperty("--consenti-font-size-base", \`\${Math.round(baseSize * mult)}px\`);
|
|
1918
|
-
root.style.setProperty("--consenti-font-size-heading", \`\${Math.round(headingSize * mult)}px\`);
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
async runPlugins() {
|
|
1923
|
-
this.plugins = this.config.plugins ?? [];
|
|
1924
|
-
for (const plugin of this.plugins) {
|
|
1925
|
-
try {
|
|
1926
|
-
await plugin.initialize(this);
|
|
1927
|
-
} catch (err) {
|
|
1928
|
-
console.warn("[Consenti] Plugin initialize error:", err);
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
/**
|
|
1933
|
-
* Invokes an optional plugin hook on all registered plugins.
|
|
1934
|
-
* Async hooks are awaited; errors are caught and logged so a broken plugin
|
|
1935
|
-
* never interferes with the consent flow.
|
|
1936
|
-
*
|
|
1937
|
-
* @param invoke - Function that calls the desired hook method on a single plugin.
|
|
1938
|
-
*/
|
|
1939
|
-
async callPluginHook(invoke) {
|
|
1940
|
-
for (const plugin of this.plugins) {
|
|
1941
|
-
try {
|
|
1942
|
-
await invoke(plugin, this);
|
|
1943
|
-
} catch (err) {
|
|
1944
|
-
console.warn("[Consenti] Plugin hook failed:", err);
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
buildHandlers(context) {
|
|
1949
|
-
return {
|
|
1950
|
-
onGrantAll: () => {
|
|
1951
|
-
const consent = {};
|
|
1952
|
-
for (const cookie of this.profile?.cookies ?? []) {
|
|
1953
|
-
consent[cookie.id] = "granted";
|
|
1954
|
-
}
|
|
1955
|
-
void this.submitConsent(consent);
|
|
1956
|
-
if (context === "banner") {
|
|
1957
|
-
this.banner?.hide();
|
|
1958
|
-
this._bannerVariant = null;
|
|
1959
|
-
}
|
|
1960
|
-
},
|
|
1961
|
-
onDenyAll: () => {
|
|
1962
|
-
const consent = {};
|
|
1963
|
-
for (const cookie of this.profile?.cookies ?? []) {
|
|
1964
|
-
consent[cookie.id] = cookie.mandatory ? "granted" : "denied";
|
|
1965
|
-
}
|
|
1966
|
-
void this.submitConsent(consent);
|
|
1967
|
-
if (context === "banner") {
|
|
1968
|
-
this.banner?.hide();
|
|
1969
|
-
this._bannerVariant = null;
|
|
1970
|
-
}
|
|
1971
|
-
},
|
|
1972
|
-
onSubmit: () => {
|
|
1973
|
-
if (context === "modal" && this.modal && this.profile) {
|
|
1974
|
-
const consent = this.modal.getToggleConsent(this.profile.preferenceModal.categories);
|
|
1975
|
-
void this.submitConsent(consent);
|
|
1976
|
-
this.hideModal();
|
|
1977
|
-
} else {
|
|
1978
|
-
const consent = {};
|
|
1979
|
-
for (const cookie of this.profile?.cookies ?? []) {
|
|
1980
|
-
if (cookie.mandatory) consent[cookie.id] = "granted";
|
|
1981
|
-
}
|
|
1982
|
-
void this.submitConsent(consent);
|
|
1983
|
-
if (context === "banner") {
|
|
1984
|
-
this.banner?.hide();
|
|
1985
|
-
this._bannerVariant = null;
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
},
|
|
1989
|
-
onManage: () => {
|
|
1990
|
-
this.showModal();
|
|
1991
|
-
},
|
|
1992
|
-
onClose: () => {
|
|
1993
|
-
if (context === "modal") {
|
|
1994
|
-
this.hideModal();
|
|
1995
|
-
} else {
|
|
1996
|
-
this.banner?.hide();
|
|
1997
|
-
this._bannerVariant = null;
|
|
1998
|
-
this.broadcastChannel.send({ type: "bannerClosed" });
|
|
1999
|
-
this.eventBus?.dispatch("consenti:bannerVisibility", { show: false, action: true });
|
|
2000
|
-
void this.callPluginHook((p) => p.onBannerHide?.());
|
|
2001
|
-
}
|
|
2002
|
-
},
|
|
2003
|
-
onGrantSpecific: (cookieIds) => {
|
|
2004
|
-
const consent = {};
|
|
2005
|
-
for (const cookie of this.profile?.cookies ?? []) {
|
|
2006
|
-
if (cookie.mandatory || cookieIds.includes(cookie.id)) {
|
|
2007
|
-
consent[cookie.id] = "granted";
|
|
2008
|
-
} else {
|
|
2009
|
-
consent[cookie.id] = "denied";
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
void this.submitConsent(consent);
|
|
2013
|
-
if (context === "banner") this.hideBanner();
|
|
2014
|
-
}
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
/**
|
|
2018
|
-
* Classifies a consent submission as \`accept_all\`, \`reject_all\`, \`custom\`, or \`update\`.
|
|
2019
|
-
* Used to enrich events and API payloads with intent metadata.
|
|
2020
|
-
*/
|
|
2021
|
-
deriveAction(fullConsent, hadExistingConsent) {
|
|
2022
|
-
if (hadExistingConsent) return "update";
|
|
2023
|
-
const nonMandatory = this.profile?.cookies.filter((c) => !c.mandatory) ?? [];
|
|
2024
|
-
if (nonMandatory.length === 0) return "custom";
|
|
2025
|
-
const allGranted = nonMandatory.every((c) => fullConsent[c.id] === "granted");
|
|
2026
|
-
const allDenied = nonMandatory.every(
|
|
2027
|
-
(c) => fullConsent[c.id] === "denied" || fullConsent[c.id] === "objected"
|
|
2028
|
-
);
|
|
2029
|
-
if (allGranted) return "accept_all";
|
|
2030
|
-
if (allDenied) return "reject_all";
|
|
2031
|
-
return "custom";
|
|
2032
|
-
}
|
|
2033
|
-
handleBroadcastMessage(msg) {
|
|
2034
|
-
switch (msg.type) {
|
|
2035
|
-
case "consentUpdated":
|
|
2036
|
-
if (msg.profileId !== this.profileId()) return;
|
|
2037
|
-
this.consentStore?.setInMemory(msg.consent);
|
|
2038
|
-
this.banner?.hide();
|
|
2039
|
-
if (this.modal?.isOpen()) this.modal.close();
|
|
2040
|
-
this.eventBus?.dispatch("consenti:consentSubmitted", {
|
|
2041
|
-
consent: msg.consent,
|
|
2042
|
-
fromBroadcast: true
|
|
2043
|
-
});
|
|
2044
|
-
break;
|
|
2045
|
-
case "bannerClosed":
|
|
2046
|
-
this.banner?.hide();
|
|
2047
|
-
this._bannerVariant = null;
|
|
2048
|
-
break;
|
|
2049
|
-
case "consentDeleted":
|
|
2050
|
-
this.consentStore?.clearInMemory();
|
|
2051
|
-
break;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
2055
|
-
/**
|
|
2056
|
-
* Returns \`true\` if the visitor has saved consent for the active profile.
|
|
2057
|
-
* Always returns \`false\` during SSR.
|
|
2058
|
-
*/
|
|
2059
|
-
hasConsent() {
|
|
2060
|
-
if (!isClient()) return false;
|
|
2061
|
-
return this.consentStore?.hasConsent() ?? false;
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Returns the stored consent map, or \`null\` if no consent has been saved.
|
|
2065
|
-
* Always returns \`null\` during SSR.
|
|
2066
|
-
*/
|
|
2067
|
-
getConsent() {
|
|
2068
|
-
if (!isClient()) return null;
|
|
2069
|
-
return this.consentStore?.getConsent() ?? null;
|
|
2070
|
-
}
|
|
2071
|
-
/**
|
|
2072
|
-
* Returns the \`Date\` when consent was last saved, or \`false\` if no consent exists.
|
|
2073
|
-
* Always returns \`false\` during SSR.
|
|
2074
|
-
*/
|
|
2075
|
-
getConsentDate() {
|
|
2076
|
-
if (!isClient()) return false;
|
|
2077
|
-
return this.consentStore?.getConsentDate() ?? false;
|
|
2078
|
-
}
|
|
2079
|
-
/**
|
|
2080
|
-
* Returns the consent map in Google Consent Mode v2 format, ready to pass to
|
|
2081
|
-
* \`gtag('consent', 'update', ...)\`. Returns \`null\` if no consent has been saved.
|
|
2082
|
-
*
|
|
2083
|
-
* Maps Consenti cookie IDs to the seven Google Consent Mode v2 parameters.
|
|
2084
|
-
* Cookies whose IDs exactly match a GCM v2 key are forwarded directly.
|
|
2085
|
-
* Non-GCM cookies are ignored by this method (they appear in \`getConsent()\` only).
|
|
2086
|
-
*/
|
|
2087
|
-
getGTMConsent() {
|
|
2088
|
-
const consent = this.getConsent();
|
|
2089
|
-
if (!consent) return null;
|
|
2090
|
-
const gcmKeys = [
|
|
2091
|
-
"ad_storage",
|
|
2092
|
-
"analytics_storage",
|
|
2093
|
-
"ad_user_data",
|
|
2094
|
-
"ad_personalization",
|
|
2095
|
-
"functionality_storage",
|
|
2096
|
-
"personalization_storage",
|
|
2097
|
-
"security_storage"
|
|
2098
|
-
];
|
|
2099
|
-
const result = {};
|
|
2100
|
-
for (const key of gcmKeys) {
|
|
2101
|
-
const raw = consent[key];
|
|
2102
|
-
if (raw === "granted") {
|
|
2103
|
-
result[key] = "granted";
|
|
2104
|
-
} else if (raw === "denied" || raw === "objected") {
|
|
2105
|
-
result[key] = "denied";
|
|
2106
|
-
} else {
|
|
2107
|
-
const safeDefault = key === "functionality_storage" || key === "security_storage" ? "granted" : "denied";
|
|
2108
|
-
result[key] = safeDefault;
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
return result;
|
|
2112
|
-
}
|
|
2113
|
-
/**
|
|
2114
|
-
* Records and persists the user's consent choices.
|
|
2115
|
-
*
|
|
2116
|
-
* Mandatory cookies are always forced to \`'granted'\` regardless of what is passed.
|
|
2117
|
-
* Non-mandatory cookies not present in \`consent\` default to \`'denied'\`.
|
|
2118
|
-
*
|
|
2119
|
-
* @param consent - Partial or full map of cookie ID → status.
|
|
2120
|
-
* @param gpcDerived - Internal flag set to \`true\` when called from the GPC strict path.
|
|
2121
|
-
*/
|
|
2122
|
-
async submitConsent(consent, gpcDerived = false) {
|
|
2123
|
-
if (!isClient() || !this.profile || !this.consentStore) return;
|
|
2124
|
-
const regulation = this.config.core.regulation ?? "gdpr";
|
|
2125
|
-
const hadExistingConsent = this.consentStore.hasConsent();
|
|
2126
|
-
const consentId = crypto.randomUUID();
|
|
2127
|
-
const pageUrl = window.location.href;
|
|
2128
|
-
const fullConsent = {};
|
|
2129
|
-
for (const cookie of this.profile.cookies) {
|
|
2130
|
-
if (cookie.mandatory) {
|
|
2131
|
-
fullConsent[cookie.id] = "granted";
|
|
2132
|
-
} else if (regulation === "cpra") {
|
|
2133
|
-
const fallback = cookie.cpraCategory === "sensitive" ? "denied" : "granted";
|
|
2134
|
-
fullConsent[cookie.id] = consent[cookie.id] ?? fallback;
|
|
2135
|
-
} else {
|
|
2136
|
-
fullConsent[cookie.id] = consent[cookie.id] ?? (regulation === "ccpa" ? "granted" : "denied");
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
const consentAction = this.deriveAction(fullConsent, hadExistingConsent);
|
|
2140
|
-
this.eventBus?.dispatch("consenti:consentBeingSubmitted", { consent: fullConsent, consentId });
|
|
2141
|
-
let apiResponse;
|
|
2142
|
-
if (this.config.api?.enabled) {
|
|
2143
|
-
try {
|
|
2144
|
-
const base = this.config.api.baseUrl ?? window.location.origin;
|
|
2145
|
-
const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
2146
|
-
apiResponse = await httpRequest2(
|
|
2147
|
-
\`\${base}/consenti/api/v1/consent\`,
|
|
2148
|
-
{
|
|
2149
|
-
method: "POST",
|
|
2150
|
-
body: JSON.stringify({
|
|
2151
|
-
profileId: this.profileId(),
|
|
2152
|
-
consent: fullConsent,
|
|
2153
|
-
locale: this.config.core.locale ?? "en",
|
|
2154
|
-
gpcDetected: gpcDerived || detectGPC(),
|
|
2155
|
-
consentId,
|
|
2156
|
-
action: consentAction,
|
|
2157
|
-
pageUrl
|
|
2158
|
-
})
|
|
2159
|
-
},
|
|
2160
|
-
this.config.api.authToken
|
|
2161
|
-
);
|
|
2162
|
-
} catch (err) {
|
|
2163
|
-
console.warn("[Consenti] API consent submission failed:", err);
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
const signingKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
|
|
2167
|
-
if (this.config.core.signCookies && signingKey) {
|
|
2168
|
-
this.lastSignature = await this.consentStore.writeAndSign(fullConsent, signingKey);
|
|
2169
|
-
} else {
|
|
2170
|
-
this.consentStore.write(fullConsent);
|
|
2171
|
-
}
|
|
2172
|
-
const receiptRequested = this.modal?.isReceiptRequested() ?? false;
|
|
2173
|
-
if (receiptRequested && this.config.core.allowReceipt) {
|
|
2174
|
-
const nameParts = this.consentStore.cookieName.split("_");
|
|
2175
|
-
const visitorId = nameParts.slice(1, -1).join("_");
|
|
2176
|
-
const receiptParams = {
|
|
2177
|
-
visitorId,
|
|
2178
|
-
profileId: this.profileId(),
|
|
2179
|
-
profileVersion: this.profile.version,
|
|
2180
|
-
locale: this.config.core.locale ?? "en",
|
|
2181
|
-
consent: fullConsent,
|
|
2182
|
-
...this.lastSignature ? { signature: this.lastSignature } : {}
|
|
2183
|
-
};
|
|
2184
|
-
const receipt = generateReceipt(receiptParams);
|
|
2185
|
-
downloadReceipt(receipt);
|
|
2186
|
-
}
|
|
2187
|
-
this.broadcastChannel.send({
|
|
2188
|
-
type: "consentUpdated",
|
|
2189
|
-
consent: fullConsent,
|
|
2190
|
-
profileId: this.profileId()
|
|
2191
|
-
});
|
|
2192
|
-
this.eventBus?.dispatch("consenti:consentSubmitted", {
|
|
2193
|
-
consent: fullConsent,
|
|
2194
|
-
apiResponse,
|
|
2195
|
-
gpcDetected: gpcDerived,
|
|
2196
|
-
consentId,
|
|
2197
|
-
consentAction,
|
|
2198
|
-
pageUrl
|
|
2199
|
-
});
|
|
2200
|
-
await this.callPluginHook((p) => p.onConsentSubmit?.(fullConsent));
|
|
2201
|
-
this.renderer?.announceToScreenReader("Your cookie preferences have been saved.");
|
|
2202
|
-
this.hideBanner();
|
|
2203
|
-
if (this.modal?.isOpen()) this.hideModal();
|
|
2204
|
-
}
|
|
2205
|
-
/**
|
|
2206
|
-
* Returns the currently visible banner variant, or \`false\` if no banner is shown.
|
|
2207
|
-
* Useful for programmatically checking state before calling \`showBanner\` / \`hideBanner\`.
|
|
2208
|
-
*/
|
|
2209
|
-
bannerVisibility() {
|
|
2210
|
-
return this._bannerVariant ?? false;
|
|
2211
|
-
}
|
|
2212
|
-
/**
|
|
2213
|
-
* Returns \`'preference'\` when the preference modal is open, or \`false\` when closed.
|
|
2214
|
-
*/
|
|
2215
|
-
modalVisibility() {
|
|
2216
|
-
return this.modal?.isOpen() ? "preference" : false;
|
|
2217
|
-
}
|
|
2218
|
-
/**
|
|
2219
|
-
* Mounts and shows the consent banner.
|
|
2220
|
-
*
|
|
2221
|
-
* No-ops if the requested variant is already visible. If a different variant is
|
|
2222
|
-
* currently shown (e.g. switching from main to GPC), the existing banner is replaced.
|
|
2223
|
-
*
|
|
2224
|
-
* @param useGpcVariant - When \`true\` and the profile has a \`gpcBanner\`, that variant
|
|
2225
|
-
* is shown instead of \`mainBanner\`. Used by the GPC flow.
|
|
2226
|
-
*/
|
|
2227
|
-
showBanner(useGpcVariant = false) {
|
|
2228
|
-
if (!isClient() || !this.profile || !this.banner || !this.renderer) return;
|
|
2229
|
-
const variant = useGpcVariant && this.profile.gpcBanner ? "gpc" : "main";
|
|
2230
|
-
if (this._bannerVariant === variant) return;
|
|
2231
|
-
const bannerConfig = variant === "gpc" ? this.profile.gpcBanner : this.profile.mainBanner;
|
|
2232
|
-
const root = this.renderer.mount(this.config.rootEl);
|
|
2233
|
-
const handlers = this.buildHandlers("banner");
|
|
2234
|
-
this.banner.remove();
|
|
2235
|
-
const locales = this.profile.locales;
|
|
2236
|
-
const activeLocale = this.config.core.locale ?? "en";
|
|
2237
|
-
const bannerEl = this.banner.build(
|
|
2238
|
-
bannerConfig,
|
|
2239
|
-
handlers,
|
|
2240
|
-
this.config.core.privacyPolicyUrl,
|
|
2241
|
-
locales,
|
|
2242
|
-
activeLocale,
|
|
2243
|
-
(locale) => this.switchLocale(locale)
|
|
2244
|
-
);
|
|
2245
|
-
root.appendChild(bannerEl);
|
|
2246
|
-
const overlay = this.banner.getOverlay();
|
|
2247
|
-
if (overlay) root.appendChild(overlay);
|
|
2248
|
-
this._bannerVariant = variant;
|
|
2249
|
-
this.eventBus?.dispatch("consenti:bannerVisibility", { show: true, action: false });
|
|
2250
|
-
void this.callPluginHook((p) => p.onBannerShow?.());
|
|
2251
|
-
}
|
|
2252
|
-
/** Hides the banner and notifies other tabs via BroadcastChannel. No-op if already hidden. */
|
|
2253
|
-
hideBanner() {
|
|
2254
|
-
if (this._bannerVariant === null) return;
|
|
2255
|
-
this.banner?.hide();
|
|
2256
|
-
this._bannerVariant = null;
|
|
2257
|
-
this.broadcastChannel.send({ type: "bannerClosed" });
|
|
2258
|
-
this.eventBus?.dispatch("consenti:bannerVisibility", { show: false, action: true });
|
|
2259
|
-
this.renderer?.announceToScreenReader("Cookie banner dismissed.");
|
|
2260
|
-
void this.callPluginHook((p) => p.onBannerHide?.());
|
|
2261
|
-
}
|
|
2262
|
-
/**
|
|
2263
|
-
* Opens the preference modal and hides the banner while the modal is open.
|
|
2264
|
-
* No-ops if the modal is already open.
|
|
2265
|
-
* If the modal is closed without saving consent, the banner is re-shown automatically
|
|
2266
|
-
* (see \`hideModal()\`). Only one banner or modal is visible at a time.
|
|
2267
|
-
*
|
|
2268
|
-
* @param triggerEl - The element that triggered the modal open. Focus returns here on close.
|
|
2269
|
-
*/
|
|
2270
|
-
showModal(triggerEl) {
|
|
2271
|
-
if (!isClient() || !this.profile || !this.modal) return;
|
|
2272
|
-
if (this.modal.isOpen()) return;
|
|
2273
|
-
const handlers = this.buildHandlers("modal");
|
|
2274
|
-
const existing = this.consentStore?.getConsent() ?? {};
|
|
2275
|
-
const regulation = this.config.core.regulation ?? "gdpr";
|
|
2276
|
-
const dpdpaCfg = regulation === "dpdpa" ? this.profile.dpdpa : void 0;
|
|
2277
|
-
const locales = this.profile.locales;
|
|
2278
|
-
const activeLocale = this.config.core.locale ?? "en";
|
|
2279
|
-
this.modal.build(
|
|
2280
|
-
this.profile.preferenceModal,
|
|
2281
|
-
existing,
|
|
2282
|
-
handlers,
|
|
2283
|
-
this.config.core.allowReceipt ?? false,
|
|
2284
|
-
dpdpaCfg,
|
|
2285
|
-
locales,
|
|
2286
|
-
activeLocale,
|
|
2287
|
-
(locale) => {
|
|
2288
|
-
this.hideModal();
|
|
2289
|
-
this.switchLocale(locale);
|
|
2290
|
-
}
|
|
2291
|
-
);
|
|
2292
|
-
this.banner?.hide();
|
|
2293
|
-
this.modal.open(triggerEl);
|
|
2294
|
-
const isDark = this.config.darkMode ?? this.profile?.darkMode ?? false;
|
|
2295
|
-
document.getElementById("consenti-modal")?.classList.toggle("consenti-root--dark", isDark);
|
|
2296
|
-
this.eventBus?.dispatch("consenti:modalVisibility", { show: true, action: true });
|
|
2297
|
-
void this.callPluginHook((p) => p.onModalShow?.());
|
|
2298
|
-
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Closes the preference modal and returns focus to the element that opened it.
|
|
2301
|
-
* No-ops if the modal is already closed.
|
|
2302
|
-
* If the user has not yet given consent, the banner is re-shown automatically.
|
|
2303
|
-
*/
|
|
2304
|
-
hideModal() {
|
|
2305
|
-
if (!this.modal?.isOpen()) return;
|
|
2306
|
-
this.modal.close();
|
|
2307
|
-
this.eventBus?.dispatch("consenti:modalVisibility", { show: false, action: true });
|
|
2308
|
-
void this.callPluginHook((p) => p.onModalHide?.());
|
|
2309
|
-
if (!this.consentStore?.hasConsent() && this._bannerVariant !== null) {
|
|
2310
|
-
this.banner?.show();
|
|
2311
|
-
} else if (!this.consentStore?.hasConsent()) {
|
|
2312
|
-
this.showBanner();
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
/**
|
|
2316
|
-
* Registers a callback to run once the widget has finished initialising.
|
|
2317
|
-
* If the widget is already initialised, the callback is invoked synchronously.
|
|
2318
|
-
*
|
|
2319
|
-
* @param callback - Function to call when the widget is ready.
|
|
2320
|
-
*/
|
|
2321
|
-
onReady(callback) {
|
|
2322
|
-
if (this.initialized) {
|
|
2323
|
-
callback();
|
|
2324
|
-
} else {
|
|
2325
|
-
void this.readyPromise.then(callback);
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
/**
|
|
2329
|
-
* A \`Promise\` that resolves once the widget has finished initialising.
|
|
2330
|
-
* Equivalent to wrapping \`onReady()\` in a \`Promise\`.
|
|
2331
|
-
*
|
|
2332
|
-
* @example
|
|
2333
|
-
* \`\`\`ts
|
|
2334
|
-
* await widget.ready
|
|
2335
|
-
* console.log('Widget ready')
|
|
2336
|
-
* \`\`\`
|
|
2337
|
-
*/
|
|
2338
|
-
get ready() {
|
|
2339
|
-
return this.readyPromise;
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* Deletes the stored consent record and notifies other tabs.
|
|
2343
|
-
* The banner will reappear on the next page load (or immediately if the
|
|
2344
|
-
* receiving tab calls \`showBanner()\` in response to \`consentDeleted\`).
|
|
2345
|
-
*/
|
|
2346
|
-
async deleteConsent() {
|
|
2347
|
-
if (!isClient() || !this.consentStore) return;
|
|
2348
|
-
if (this.config.api?.enabled) {
|
|
2349
|
-
try {
|
|
2350
|
-
const base = this.config.api.baseUrl ?? window.location.origin;
|
|
2351
|
-
const visitorId = this.consentStore.cookieName;
|
|
2352
|
-
const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
2353
|
-
await httpRequest2(
|
|
2354
|
-
\`\${base}/consenti/api/v1/consent/\${visitorId}\`,
|
|
2355
|
-
{ method: "DELETE" },
|
|
2356
|
-
this.config.api?.authToken
|
|
2357
|
-
);
|
|
2358
|
-
} catch (err) {
|
|
2359
|
-
console.warn("[Consenti] API consent deletion failed:", err);
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
this.consentStore.delete();
|
|
2363
|
-
this.broadcastChannel.send({ type: "consentDeleted" });
|
|
2364
|
-
}
|
|
2365
|
-
/**
|
|
2366
|
-
* Removes existing consent and immediately re-shows the consent banner so the user
|
|
2367
|
-
* can review and update their preferences.
|
|
2368
|
-
*
|
|
2369
|
-
* Equivalent to calling \`deleteConsent()\` followed by \`showBanner()\`, but in a single
|
|
2370
|
-
* method so callers do not need to coordinate the async deletion with the banner display.
|
|
2371
|
-
*
|
|
2372
|
-
* Use this to power "Review my consent" links in footers or privacy dashboards.
|
|
2373
|
-
*/
|
|
2374
|
-
async reConsent() {
|
|
2375
|
-
await this.deleteConsent();
|
|
2376
|
-
this.showBanner();
|
|
2377
|
-
}
|
|
2378
|
-
/**
|
|
2379
|
-
* Returns the fully resolved profile for the current session, or \`null\` during the
|
|
2380
|
-
* async init phase (before the profile has been fetched/resolved).
|
|
2381
|
-
*
|
|
2382
|
-
* Plugins can use this to read cookie definitions, banner config, modal categories,
|
|
2383
|
-
* and the profile version without accessing internal state.
|
|
2384
|
-
*/
|
|
2385
|
-
getProfile() {
|
|
2386
|
-
return this.profile;
|
|
2387
|
-
}
|
|
2388
|
-
/**
|
|
2389
|
-
* Returns the \`#consenti-root\` container element that wraps all widget DOM,
|
|
2390
|
-
* or \`null\` before the widget has mounted or after \`destroy()\`.
|
|
2391
|
-
*
|
|
2392
|
-
* Plugins can use this to append custom elements, apply extra CSS variables,
|
|
2393
|
-
* or observe mutations within the widget container.
|
|
2394
|
-
*/
|
|
2395
|
-
getRootElement() {
|
|
2396
|
-
if (!isClient()) return null;
|
|
2397
|
-
if (this.config.rootEl) {
|
|
2398
|
-
if (typeof this.config.rootEl === "string") {
|
|
2399
|
-
return document.querySelector(this.config.rootEl);
|
|
2400
|
-
}
|
|
2401
|
-
return this.config.rootEl;
|
|
2402
|
-
}
|
|
2403
|
-
return document.getElementById("consenti-root");
|
|
2404
|
-
}
|
|
2405
|
-
/**
|
|
2406
|
-
* Returns the \`#consenti-banner\` element when it is present in the DOM, or \`null\`.
|
|
2407
|
-
* The element may be hidden (\`display: none\`) — check \`bannerVisibility()\` for visibility state.
|
|
2408
|
-
*
|
|
2409
|
-
* Useful for adding extra content, custom event listeners, or mutation observers on the banner.
|
|
2410
|
-
*/
|
|
2411
|
-
getBannerElement() {
|
|
2412
|
-
if (!isClient()) return null;
|
|
2413
|
-
return document.getElementById("consenti-banner");
|
|
2414
|
-
}
|
|
2415
|
-
/**
|
|
2416
|
-
* Returns the \`#consenti-modal\` element when the modal has been built, or \`null\`.
|
|
2417
|
-
* The element is built on first \`showModal()\` call and reused on subsequent opens.
|
|
2418
|
-
*
|
|
2419
|
-
* Useful for injecting extra content into the modal or listening to modal-specific events.
|
|
2420
|
-
*/
|
|
2421
|
-
getModalElement() {
|
|
2422
|
-
if (!isClient()) return null;
|
|
2423
|
-
return document.getElementById("consenti-modal");
|
|
2424
|
-
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Switches the active locale, re-resolves the profile, and re-renders the widget.
|
|
2427
|
-
* Called by the locale switcher UI when the user selects a different language.
|
|
2428
|
-
*
|
|
2429
|
-
* @param locale - BCP 47 locale code to switch to (e.g. \`'fr'\`, \`'de-AT'\`).
|
|
2430
|
-
*/
|
|
2431
|
-
switchLocale(locale) {
|
|
2432
|
-
if (!isClient()) return;
|
|
2433
|
-
this.config.core.locale = locale;
|
|
2434
|
-
this.banner?.remove();
|
|
2435
|
-
if (this.modal?.isOpen()) this.modal.close();
|
|
2436
|
-
this._bannerVariant = null;
|
|
2437
|
-
this.profile = null;
|
|
2438
|
-
this.initialized = false;
|
|
2439
|
-
this.readyPromise = new Promise((resolve) => {
|
|
2440
|
-
this.readyResolve = resolve;
|
|
2441
|
-
});
|
|
2442
|
-
this.init().catch((err) => console.warn("[Consenti] Locale switch error:", err));
|
|
2443
|
-
}
|
|
2444
|
-
/**
|
|
2445
|
-
* Destroys the widget — removes all DOM elements, disconnects the BroadcastChannel,
|
|
2446
|
-
* and calls \`destroy()\` on all registered plugins.
|
|
2447
|
-
*
|
|
2448
|
-
* After \`destroy()\`, the instance should not be used again.
|
|
2449
|
-
*/
|
|
2450
|
-
destroy() {
|
|
2451
|
-
for (const plugin of this.plugins) {
|
|
2452
|
-
try {
|
|
2453
|
-
plugin.destroy();
|
|
2454
|
-
} catch {
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
this.modal?.destroy();
|
|
2458
|
-
this.renderer?.unmount();
|
|
2459
|
-
this.broadcastChannel.disconnect();
|
|
2460
|
-
this.banner = null;
|
|
2461
|
-
this.modal = null;
|
|
2462
|
-
this.renderer = null;
|
|
2463
|
-
this.consentStore = null;
|
|
2464
|
-
this.eventBus = null;
|
|
2465
|
-
this.initialized = false;
|
|
2466
|
-
}
|
|
2467
|
-
};
|
|
2468
|
-
|
|
2469
|
-
// src/plugins/plugin.base.ts
|
|
2470
|
-
var ConsentiPlugin = class {
|
|
2471
|
-
};
|
|
2472
|
-
|
|
2473
|
-
// src/profile/consenti-profile.ts
|
|
2474
|
-
var nextLocalId = 1e3;
|
|
2475
|
-
var ConsentiProfile = class {
|
|
2476
|
-
/**
|
|
2477
|
-
* @param config - Full profile configuration including cookies, locales, and UI text.
|
|
2478
|
-
*/
|
|
2479
|
-
constructor(config) {
|
|
2480
|
-
this.config = config;
|
|
2481
|
-
this.id = nextLocalId++;
|
|
2482
|
-
registerProfile(this.id, config);
|
|
2483
|
-
}
|
|
2484
|
-
/** Returns the auto-assigned numeric profile ID. Pass this to \`core.profileId\`. */
|
|
2485
|
-
getId() {
|
|
2486
|
-
return this.id;
|
|
2487
|
-
}
|
|
2488
|
-
/** Returns the raw \`ProfileConfig\` passed to the constructor. */
|
|
2489
|
-
toJSON() {
|
|
2490
|
-
return this.config;
|
|
2491
|
-
}
|
|
2492
|
-
};
|
|
2493
|
-
|
|
2494
|
-
// src/utils/consent-script.ts
|
|
2495
|
-
var ConsentScript = class {
|
|
2496
|
-
constructor(options) {
|
|
2497
|
-
this.options = options;
|
|
2498
|
-
this.scriptEl = null;
|
|
2499
|
-
this.handler = () => this.evaluate();
|
|
2500
|
-
if (!isClient()) return;
|
|
2501
|
-
window.addEventListener("consenti:consentSubmitted", this.handler);
|
|
2502
|
-
this.evaluate();
|
|
2503
|
-
}
|
|
2504
|
-
evaluate() {
|
|
2505
|
-
const consent = this.options.widget.getConsent();
|
|
2506
|
-
const granted = consent?.[this.options.cookieId] === "granted";
|
|
2507
|
-
if (granted && !this.scriptEl) {
|
|
2508
|
-
this.inject();
|
|
2509
|
-
} else if (!granted && this.scriptEl) {
|
|
2510
|
-
this.revoke();
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
inject() {
|
|
2514
|
-
const script = document.createElement("script");
|
|
2515
|
-
if (this.options.src) script.src = this.options.src;
|
|
2516
|
-
if (this.options.innerHTML) script.innerHTML = this.options.innerHTML;
|
|
2517
|
-
for (const [key, value] of Object.entries(this.options.attributes ?? {})) {
|
|
2518
|
-
script.setAttribute(key, value);
|
|
2519
|
-
}
|
|
2520
|
-
document.head.appendChild(script);
|
|
2521
|
-
this.scriptEl = script;
|
|
2522
|
-
this.options.onLoad?.();
|
|
2523
|
-
}
|
|
2524
|
-
revoke() {
|
|
2525
|
-
this.scriptEl?.remove();
|
|
2526
|
-
this.scriptEl = null;
|
|
2527
|
-
this.options.onRevoke?.();
|
|
2528
|
-
}
|
|
2529
|
-
/**
|
|
2530
|
-
* Removes the event listener and the injected \`<script>\` element (if present).
|
|
2531
|
-
* Call this when the widget is destroyed or the script is no longer needed.
|
|
2532
|
-
*/
|
|
2533
|
-
destroy() {
|
|
2534
|
-
if (!isClient()) return;
|
|
2535
|
-
window.removeEventListener("consenti:consentSubmitted", this.handler);
|
|
2536
|
-
this.revoke();
|
|
2537
|
-
}
|
|
2538
|
-
};
|
|
2539
|
-
|
|
2540
|
-
// src/utils/cookie-trigger.ts
|
|
2541
|
-
var CookieTrigger = class {
|
|
2542
|
-
constructor(options) {
|
|
2543
|
-
const action = options.action ?? "modal";
|
|
2544
|
-
this.clickHandler = () => {
|
|
2545
|
-
if (action === "modal") options.widget.showModal();
|
|
2546
|
-
else options.widget.showBanner();
|
|
2547
|
-
};
|
|
2548
|
-
if (!options.el) {
|
|
2549
|
-
const btn = document.createElement("button");
|
|
2550
|
-
btn.type = "button";
|
|
2551
|
-
btn.className = "consenti-btn consenti-btn--text consenti-cookie-trigger";
|
|
2552
|
-
btn.textContent = options.label ?? "Cookie Settings";
|
|
2553
|
-
btn.setAttribute("aria-label", options.label ?? "Open cookie settings");
|
|
2554
|
-
this.el = btn;
|
|
2555
|
-
this.created = true;
|
|
2556
|
-
} else if (typeof options.el === "string") {
|
|
2557
|
-
if (!isClient()) {
|
|
2558
|
-
this.el = document.createElement("span");
|
|
2559
|
-
this.created = false;
|
|
2560
|
-
return;
|
|
2561
|
-
}
|
|
2562
|
-
const found = document.querySelector(options.el);
|
|
2563
|
-
if (!found) {
|
|
2564
|
-
throw new Error(
|
|
2565
|
-
\`[Consenti] CookieTrigger: element not found for selector "\${options.el}"\`
|
|
2566
|
-
);
|
|
2567
|
-
}
|
|
2568
|
-
this.el = found;
|
|
2569
|
-
this.created = false;
|
|
2570
|
-
} else {
|
|
2571
|
-
this.el = options.el;
|
|
2572
|
-
this.created = false;
|
|
2573
|
-
}
|
|
2574
|
-
this.el.addEventListener("click", this.clickHandler);
|
|
2575
|
-
}
|
|
2576
|
-
/**
|
|
2577
|
-
* Returns the trigger element.
|
|
2578
|
-
* When no \`el\` was provided, append the returned element to the desired location
|
|
2579
|
-
* in the DOM after construction.
|
|
2580
|
-
*/
|
|
2581
|
-
getElement() {
|
|
2582
|
-
return this.el;
|
|
2583
|
-
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Removes the click handler and, if this instance auto-created the button,
|
|
2586
|
-
* removes it from the DOM.
|
|
2587
|
-
*/
|
|
2588
|
-
destroy() {
|
|
2589
|
-
this.el.removeEventListener("click", this.clickHandler);
|
|
2590
|
-
if (this.created) this.el.remove();
|
|
2591
|
-
}
|
|
2592
|
-
};
|
|
2593
|
-
return __toCommonJS(src_exports);
|
|
2594
|
-
})();
|
|
2595
|
-
//# sourceMappingURL=index.umd.js.map`,Zi=":root{--consenti-color-bg: #ffffff;--consenti-color-text: #1a2e4a;--consenti-color-primary: #1565c0;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #f0f4f8;--consenti-color-secondary-text: #1a2e4a;--consenti-color-border: #dbe4ee;--consenti-color-overlay: rgba(26, 52, 96, 0.55);--consenti-font-family: system-ui, -apple-system, sans-serif;--consenti-font-size-base: 14px;--consenti-font-size-heading: 16px;--consenti-font-weight-heading: 600;--consenti-line-height: 1.5;--consenti-spacing-sm: 8px;--consenti-spacing-md: 16px;--consenti-spacing-lg: 24px;--consenti-border-radius: 8px;--consenti-border-radius-btn: 4px;--consenti-shadow: 0 4px 24px rgba(21, 101, 192, 0.14);--consenti-color-accent: #d32f2f;--consenti-color-accent-text: #ffffff;--consenti-toggle-bg-on: #43a047;--consenti-toggle-bg-off: #c8d6e8;--consenti-toggle-knob: #ffffff;--consenti-toggle-width: 44px;--consenti-toggle-height: 24px;--consenti-z-banner: 9999;--consenti-z-overlay: 9998;--consenti-z-modal: 10000}.consenti-root--dark{--consenti-color-bg: #1e2535;--consenti-color-text: #e2e8f0;--consenti-color-primary: #4a9eff;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #2a3447;--consenti-color-secondary-text: #cbd5e1;--consenti-color-border: #374151;--consenti-color-overlay: rgba(0, 0, 0, 0.65);--consenti-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);--consenti-toggle-bg-off: #4b5563}.consenti-overlay{position:fixed;inset:0;background:var(--consenti-color-overlay);z-index:var(--consenti-z-overlay)}.consenti-btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--consenti-spacing-sm) var(--consenti-spacing-md);border:none;border-radius:var(--consenti-border-radius-btn);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);font-weight:500;line-height:1;cursor:pointer;text-decoration:none;transition:opacity .15s ease}.consenti-btn:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-btn:hover{opacity:.9}.consenti-btn--primary{background:var(--consenti-color-primary);color:var(--consenti-color-primary-text)}.consenti-btn--secondary{background:var(--consenti-color-secondary);color:var(--consenti-color-secondary-text)}.consenti-btn--text{background:rgba(0,0,0,0);color:var(--consenti-color-primary);padding-left:0;padding-right:0;text-decoration:underline}.consenti-btn--accent{background:var(--consenti-color-accent);color:var(--consenti-color-accent-text)}.consenti-btn--close{background:rgba(0,0,0,0);color:var(--consenti-color-text);font-size:20px;line-height:1;padding:4px 8px;border-radius:50%}.consenti-btn--close:hover{background:var(--consenti-color-secondary);opacity:1}.consenti-banner{position:fixed;left:0;right:0;z-index:var(--consenti-z-banner);background:var(--consenti-color-bg);color:var(--consenti-color-text);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);display:flex;flex-wrap:wrap;align-items:center;gap:var(--consenti-spacing-md)}.consenti-banner--top{top:0;border-bottom:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--top{animation:consenti-slide-down .2s ease-out}}.consenti-banner--bottom{bottom:0;border-top:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--bottom{animation:consenti-slide-up .2s ease-out}}.consenti-banner--middle{top:50%;transform:translateY(-50%);left:50%;right:auto;transform:translate(-50%, -50%);width:min(600px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--left-bottom{bottom:var(--consenti-spacing-lg);left:var(--consenti-spacing-lg);right:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--right-bottom{bottom:var(--consenti-spacing-lg);right:var(--consenti-spacing-lg);left:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner__text-container{flex:1;min-width:200px}.consenti-banner__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0 0 var(--consenti-spacing-sm);width:100%}.consenti-banner__text{margin:0}.consenti-banner__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-sm)}.consenti-banner__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-banner__controls{position:absolute;top:var(--consenti-spacing-sm);right:var(--consenti-spacing-sm);display:flex;align-items:center;gap:4px}.consenti-banner__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);align-items:center}.consenti-banner--top .consenti-banner__buttons,.consenti-banner--bottom .consenti-banner__buttons{margin-left:2vw}@keyframes consenti-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes consenti-slide-down{from{transform:translateY(-100%)}to{transform:translateY(0)}}.consenti-modal{position:fixed;inset:0;z-index:var(--consenti-z-modal);display:flex;font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);line-height:var(--consenti-line-height)}.consenti-modal--passthrough{pointer-events:none}.consenti-modal--passthrough .consenti-modal__container{pointer-events:auto}.consenti-modal--center{align-items:center;justify-content:center}.consenti-modal--left{align-items:stretch;justify-content:flex-start}.consenti-modal--left .consenti-modal__container{border-radius:0 var(--consenti-border-radius) var(--consenti-border-radius) 0;height:100%;max-height:100vh}.consenti-modal--right{align-items:stretch;justify-content:flex-end}.consenti-modal--right .consenti-modal__container{border-radius:0;height:100%;max-height:100vh}.consenti-modal__overlay{position:absolute;inset:0;background:var(--consenti-color-overlay)}.consenti-modal__container{position:relative;z-index:1;background:var(--consenti-color-bg);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);width:min(560px,90vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}@media(prefers-reduced-motion: no-preference){.consenti-modal__container{animation:consenti-fade-in .15s ease-out}}.consenti-modal__header{display:flex;align-items:center;justify-content:space-between;padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-bottom:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0;flex:1}.consenti-modal__header-controls{display:flex;align-items:center;gap:4px;flex-shrink:0}.consenti-modal__close{flex-shrink:0}.consenti-modal__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-modal__subheading{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg) 0;margin:0;color:#6b7280;font-size:13px}.consenti-modal__text{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__body{overflow-y:auto;flex:1;padding:0 var(--consenti-spacing-lg)}.consenti-modal__categories{display:flex;flex-direction:column;gap:var(--consenti-spacing-md);padding:var(--consenti-spacing-md) 0}.consenti-modal__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-option{margin:0 var(--consenti-spacing-lg);padding-top:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-label{display:flex;align-items:center;gap:var(--consenti-spacing-sm);font-size:var(--consenti-font-size-base);cursor:pointer}.consenti-modal__receipt-checkbox{width:16px;height:16px;flex-shrink:0;cursor:pointer}.consenti-modal__receipt-desc{margin:4px 0 0 24px;font-size:12px;color:#6b7280}@keyframes consenti-fade-in{from{opacity:0;transform:scale(0.97)}to{opacity:1;transform:scale(1)}}.consenti-category{padding:var(--consenti-spacing-md) 0;border-bottom:1px solid var(--consenti-color-border)}.consenti-category:last-child{border-bottom:none}.consenti-category--mandatory .consenti-category__toggle{opacity:.6;cursor:default}.consenti-category__header{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-md);margin-bottom:var(--consenti-spacing-sm)}.consenti-category__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0}.consenti-category__text{color:#4b5563;font-size:13px;margin:0}.consenti-category__li-desc{margin:var(--consenti-spacing-sm) 0 0;font-size:12px;color:#6b7280;font-style:italic}.consenti-category__toggle{flex-shrink:0;position:relative;width:var(--consenti-toggle-width);height:var(--consenti-toggle-height);border-radius:calc(var(--consenti-toggle-height)/2);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__toggle--mandatory{cursor:default}.consenti-category__toggle-knob{position:absolute;top:2px;left:2px;width:calc(var(--consenti-toggle-height) - 4px);height:calc(var(--consenti-toggle-height) - 4px);border-radius:50%;background:var(--consenti-toggle-knob);transition:transform .2s ease;display:block}[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) - var(--consenti-toggle-height)))}.consenti-locale-switcher{position:relative;display:inline-flex}.consenti-locale-switcher__btn{font-size:1rem;line-height:1;cursor:pointer}.consenti-locale-switcher__list{position:absolute;top:calc(100% + 4px);right:0;z-index:calc(var(--consenti-z-modal) + 1);background:var(--consenti-color-bg);border:1px solid var(--consenti-color-border);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);list-style:none;margin:0;padding:4px 0;min-width:80px}.consenti-locale-switcher__list li{display:block}.consenti-locale-switcher__option{display:block;width:100%;padding:6px 12px;text-align:left;font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);background:none;border:none;cursor:pointer}.consenti-locale-switcher__option:hover{background:var(--consenti-color-secondary)}.consenti-locale-switcher__option--active{font-weight:600;color:var(--consenti-color-primary)}.consenti-locale-switcher--modal .consenti-locale-switcher__list{right:0}",Xi=Ur,Qi=Vr,st={main:"Main Banner",gpc:"GPC Banner",modal:"Modal"};function es(n){const t={text:n.text,style:n.type,action:n.action};return n.cookies!==void 0&&(t.cookies=n.cookies),n.url!==void 0&&(t.url=n.url),t}function tn(n){if(!n)return;const t=n.buttons;return{...n,buttons:(t??[]).map(es)}}function on(n){return(n==null?void 0:n.showLocaleSwitcher)===!0}function at(n,t,o){const r=n,s=r.translations,i=r.defaultLocale??"en",a=s==null?void 0:s[i],c=tn(a==null?void 0:a.mainBanner),l=tn(a==null?void 0:a.gpcBanner),p=tn(a==null?void 0:a.preferenceModal),d=on(a==null?void 0:a.mainBanner)||on(a==null?void 0:a.gpcBanner)||on(a==null?void 0:a.preferenceModal),u={defaultLocale:i,...d?{locales:["en","fr"]}:{},cookies:r.cookies,mainBanner:c,gpcBanner:l,preferenceModal:p},h=JSON.stringify(u);return`<!DOCTYPE html>
|
|
2596
|
-
<html lang="en"${t?' class="dark"':""}>
|
|
2597
|
-
<head>
|
|
2598
|
-
<meta charset="UTF-8">
|
|
2599
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
2600
|
-
<style>${Zi}</style>
|
|
2601
|
-
<style>body{margin:0;font-family:sans-serif;background:${t?"#111827":"#f5f5f5"}}</style>
|
|
2602
|
-
</head>
|
|
2603
|
-
<body>
|
|
2604
|
-
<script>${Yi}<\/script>
|
|
2605
|
-
<script>
|
|
2606
|
-
const { ConsentiSetup } = window.Consenti
|
|
2607
|
-
const w = new ConsentiSetup({
|
|
2608
|
-
darkMode: ${t},
|
|
2609
|
-
core: { profileId: 0 },
|
|
2610
|
-
profileOverride: ${h},
|
|
2611
|
-
})
|
|
2612
|
-
${o==="modal"?"w.onReady(function() { w.showModal() })":o==="gpc"?"w.onReady(function() { w.deleteConsent().then(function() { w.showBanner(true) }) })":"w.onReady(function() { w.deleteConsent().then(function() { w.showBanner() }) })"}
|
|
2613
|
-
|
|
2614
|
-
window.addEventListener('message', function(e) {
|
|
2615
|
-
if (!e.data || typeof e.data.type !== 'string') return
|
|
2616
|
-
var type = e.data.type
|
|
2617
|
-
if (type === 'preview:main') {
|
|
2618
|
-
if (w.modalVisibility()) w.hideModal()
|
|
2619
|
-
w.showBanner(false)
|
|
2620
|
-
} else if (type === 'preview:gpc') {
|
|
2621
|
-
if (w.modalVisibility()) w.hideModal()
|
|
2622
|
-
w.showBanner(true)
|
|
2623
|
-
} else if (type === 'preview:modal') {
|
|
2624
|
-
if (!w.modalVisibility()) w.showModal()
|
|
2625
|
-
}
|
|
2626
|
-
})
|
|
2627
|
-
<\/script>
|
|
2628
|
-
</body>
|
|
2629
|
-
</html>`}function gn({draft:n,debounceMs:t=600,expandable:o=!1,height:r="360px",previewMode:s}){const{theme:i}=Rt(),a=i==="dark",[c,l]=g(!1),p=s!==void 0,[d,u]=g("main"),h=p?s:d,[m,k]=g(()=>at(n,a,h)),[y,b]=g(0),v=Y(null),w=Y(h),N=Y(h);return N.current=h,j(()=>{var x,$;w.current!==h&&(w.current=h,($=(x=v.current)==null?void 0:x.contentWindow)==null||$.postMessage({type:`preview:${h}`},"*"))},[h]),j(()=>{const x=setTimeout(()=>{const $=N.current;k(at(n,a,$)),b(D=>D+1),w.current=$},t);return()=>clearTimeout(x)},[n,t,a]),j(()=>(c?document.body.style.overflow="hidden":document.body.style.overflow="",()=>{document.body.style.overflow=""}),[c]),e("div",{class:`border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden bg-gray-50 dark:bg-gray-900${c?" shadow-2xl":""}`,style:c?{position:"fixed",inset:0,zIndex:9999,display:"flex",flexDirection:"column"}:{},children:[e("div",{class:"px-4 py-2.5 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0 gap-3",children:[e("div",{class:"flex items-center gap-3 min-w-0",children:[e("div",{class:"shrink-0",children:[e("span",{class:"text-sm font-medium text-gray-700 dark:text-gray-300",children:"Live Preview"}),e("span",{class:"text-xs text-gray-400 dark:text-gray-500 ml-2",children:"Updates after you stop typing"})]}),!p&&e("div",{class:"flex items-center gap-1",children:Object.keys(st).map(x=>e("button",{type:"button",onClick:()=>u(x),class:`px-2.5 py-0.5 text-xs rounded-full transition-colors ${d===x?"bg-blue-600 text-white":"text-gray-500 hover:text-gray-700 hover:bg-gray-200 dark:hover:bg-gray-700 dark:hover:text-gray-300"}`,children:st[x]},x))})]}),o&&e("button",{type:"button",onClick:()=>l(x=>!x),class:"text-xs text-blue-600 dark:text-blue-400 hover:underline flex items-center gap-1 shrink-0",children:c?e(Z,{children:[e(Qi,{size:12})," Collapse"]}):e(Z,{children:[e(Xi,{size:12})," Expand"]})})]}),e("iframe",{ref:v,srcdoc:m,class:"w-full border-none block",style:c?{flex:1,minHeight:0}:{height:r},sandbox:"allow-scripts allow-same-origin",title:"Banner preview"},y)]})}const ne={list:()=>P("/cookie-templates"),get:n=>P(`/cookie-templates/${n}`),create:n=>P("/cookie-templates",{method:"POST",body:JSON.stringify(n)}),update:(n,t)=>P(`/cookie-templates/${n}`,{method:"PUT",body:JSON.stringify(t)}),delete:n=>P(`/cookie-templates/${n}`,{method:"DELETE"}),copy:(n,t)=>P(`/cookie-templates/${n}/copy`,{method:"POST",body:JSON.stringify({name:t})})},re={list:()=>P("/ui-templates"),get:n=>P(`/ui-templates/${n}`),create:n=>P("/ui-templates",{method:"POST",body:JSON.stringify(n)}),update:(n,t)=>P(`/ui-templates/${n}`,{method:"PUT",body:JSON.stringify(t)}),delete:n=>P(`/ui-templates/${n}`,{method:"DELETE"}),copy:(n,t)=>P(`/ui-templates/${n}/copy`,{method:"POST",body:JSON.stringify({name:t})})};function eo(n,t,o,r,s){const i=(a,c,l)=>a[c]||l;return{locale:n,mainBanner:{heading:t.mainBanner.heading,htmlText:ye(he(t.mainBanner.htmlText)),buttons:o.buttons.map((a,c)=>i(t.mainBanner.buttonLabels,c,a.text))},gpcBanner:{heading:t.gpcBanner.heading,htmlText:ye(he(t.gpcBanner.htmlText)),buttons:r.buttons.map((a,c)=>i(t.gpcBanner.buttonLabels,c,a.text))},preferenceModal:{heading:t.preferenceModal.heading,subheading:t.preferenceModal.subheading,htmlText:ye(he(t.preferenceModal.htmlText)),buttons:s.buttons.map((a,c)=>i(t.preferenceModal.buttonLabels,c,a.text)),categories:t.preferenceModal.categories.map(a=>({id:a.id,heading:a.heading,htmlText:ye(he(a.htmlText))}))}}}function ns(n,t,o,r,s){return JSON.stringify(eo(n,t,o,r,s),null,2)}function ts(n,t,o,r,s){const i=eo(n,t,o,r,s),a=[["field","value"]],c=l=>`"${l.replace(/"/g,'""')}"`;return a.push(["mainBanner.heading",c(i.mainBanner.heading)]),a.push(["mainBanner.htmlText",c(i.mainBanner.htmlText)]),i.mainBanner.buttons.forEach((l,p)=>a.push([`mainBanner.button.${p}`,c(l)])),a.push(["gpcBanner.heading",c(i.gpcBanner.heading)]),a.push(["gpcBanner.htmlText",c(i.gpcBanner.htmlText)]),i.gpcBanner.buttons.forEach((l,p)=>a.push([`gpcBanner.button.${p}`,c(l)])),a.push(["preferenceModal.heading",c(i.preferenceModal.heading)]),a.push(["preferenceModal.subheading",c(i.preferenceModal.subheading)]),a.push(["preferenceModal.htmlText",c(i.preferenceModal.htmlText)]),i.preferenceModal.buttons.forEach((l,p)=>a.push([`preferenceModal.button.${p}`,c(l)])),i.preferenceModal.categories.forEach(l=>{a.push([`category.${l.id}.heading`,c(l.heading)]),a.push([`category.${l.id}.htmlText`,c(l.htmlText)])}),a.map(l=>l.join(",")).join(`
|
|
2630
|
-
`)}function se(n){return Xt(_n(n))}function os(n,t){var r,s,i,a,c,l,p,d,u,h;const o=JSON.parse(n);return{mainBanner:{heading:String(((r=o.mainBanner)==null?void 0:r.heading)??t.mainBanner.heading),htmlText:se(String(((s=o.mainBanner)==null?void 0:s.htmlText)??"")),buttonLabels:(((i=o.mainBanner)==null?void 0:i.buttons)??[]).map(String)},gpcBanner:{heading:String(((a=o.gpcBanner)==null?void 0:a.heading)??t.gpcBanner.heading),htmlText:se(String(((c=o.gpcBanner)==null?void 0:c.htmlText)??"")),buttonLabels:(((l=o.gpcBanner)==null?void 0:l.buttons)??[]).map(String)},preferenceModal:{heading:String(((p=o.preferenceModal)==null?void 0:p.heading)??t.preferenceModal.heading),subheading:String(((d=o.preferenceModal)==null?void 0:d.subheading)??t.preferenceModal.subheading),htmlText:se(String(((u=o.preferenceModal)==null?void 0:u.htmlText)??"")),buttonLabels:(((h=o.preferenceModal)==null?void 0:h.buttons)??[]).map(String),categories:t.preferenceModal.categories.map(m=>{var y,b;const k=(b=(y=o.preferenceModal)==null?void 0:y.categories)==null?void 0:b.find(v=>v.id===m.id);return k?{id:m.id,heading:String(k.heading),htmlText:se(String(k.htmlText??""))}:m})}}}function rs(n,t){const o={};n.split(/\r?\n/).slice(1).forEach(a=>{if(!a.trim())return;const c=a.match(/^([^,]+),(.*)$/);if(!c)return;const l=c[1].trim();let p=c[2].trim();p.startsWith('"')&&p.endsWith('"')&&(p=p.slice(1,-1).replace(/""/g,'"')),o[l]=p});const s=structuredClone(t);o["mainBanner.heading"]&&(s.mainBanner.heading=o["mainBanner.heading"]),o["mainBanner.htmlText"]!==void 0&&(s.mainBanner.htmlText=se(o["mainBanner.htmlText"])),o["gpcBanner.heading"]&&(s.gpcBanner.heading=o["gpcBanner.heading"]),o["gpcBanner.htmlText"]!==void 0&&(s.gpcBanner.htmlText=se(o["gpcBanner.htmlText"])),o["preferenceModal.heading"]&&(s.preferenceModal.heading=o["preferenceModal.heading"]),o["preferenceModal.subheading"]!==void 0&&(s.preferenceModal.subheading=o["preferenceModal.subheading"]),o["preferenceModal.htmlText"]!==void 0&&(s.preferenceModal.htmlText=se(o["preferenceModal.htmlText"]));const i=(a,c)=>{const l=[...c];for(let p=0;;p++){const d=`${a}.button.${p}`;if(o[d]===void 0)break;l[p]=o[d]}return l};return s.mainBanner.buttonLabels=i("mainBanner",s.mainBanner.buttonLabels),s.gpcBanner.buttonLabels=i("gpcBanner",s.gpcBanner.buttonLabels),s.preferenceModal.buttonLabels=i("preferenceModal",s.preferenceModal.buttonLabels),s.preferenceModal.categories=s.preferenceModal.categories.map(a=>{const c=`category.${a.id}.heading`,l=`category.${a.id}.htmlText`;return{...a,heading:o[c]??a.heading,htmlText:o[l]!==void 0?se(o[l]):a.htmlText}}),s}function lt(n,t,o){const r=new Blob([n],{type:o}),s=URL.createObjectURL(r),i=document.createElement("a");i.href=s,i.download=t,i.style.display="none",document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(s)}function is({step:n}){const t=["Profile Config","Templates","Content"];return e("div",{class:"flex items-center gap-0 mb-6",children:t.map((o,r)=>{const s=r+1,i=n>s,a=n===s;return e("div",{class:"flex items-center flex-1 last:flex-none",children:[e("div",{class:"flex items-center gap-2 shrink-0",children:[e("div",{class:`w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-colors ${i?"bg-green-500 text-white":a?"bg-blue-600 text-white":"bg-gray-200 text-gray-500"}`,children:i?e(_e,{size:14}):s}),e("span",{class:`text-sm font-medium hidden sm:block ${a?"text-blue-600":i?"text-green-600":"text-gray-400"}`,children:o})]}),r<t.length-1&&e("div",{class:`h-px flex-1 mx-3 ${n>s?"bg-green-400":"bg-gray-200"}`})]},s)})})}function Se(n){return{mainBanner:{heading:"",htmlText:"",buttonLabels:n.mainBanner.buttons.map(t=>t.text)},gpcBanner:{heading:"",htmlText:"",buttonLabels:n.gpcBanner.buttons.map(t=>t.text)},preferenceModal:{heading:"",subheading:"",htmlText:"",buttonLabels:n.preferenceModal.buttons.map(t=>t.text),categories:n.preferenceModal.categories.map(t=>({id:t.id,heading:"",htmlText:""}))}}}function ss(n,t){return{...n,mainBanner:{...n.mainBanner,buttonLabels:t.mainBanner.buttons.map((o,r)=>n.mainBanner.buttonLabels[r]??o.text)},gpcBanner:{...n.gpcBanner,buttonLabels:t.gpcBanner.buttons.map((o,r)=>n.gpcBanner.buttonLabels[r]??o.text)},preferenceModal:{...n.preferenceModal,buttonLabels:t.preferenceModal.buttons.map((o,r)=>n.preferenceModal.buttonLabels[r]??o.text),categories:t.preferenceModal.categories.map(o=>{var r,s;return{id:o.id,heading:((r=n.preferenceModal.categories.find(i=>i.id===o.id))==null?void 0:r.heading)??"",htmlText:((s=n.preferenceModal.categories.find(i=>i.id===o.id))==null?void 0:s.htmlText)??""}})}}}function as(n,t,o,r){const s=r[o]??Se(t);return{cookies:n.cookies,defaultLocale:o,translations:{[o]:{mainBanner:{position:t.mainBanner.position,overlayOpacity:t.mainBanner.overlayOpacity,showClose:t.mainBanner.showClose,showLocaleSwitcher:t.mainBanner.showLocaleSwitcher,headingTag:t.mainBanner.headingTag,heading:s.mainBanner.heading,htmlText:s.mainBanner.htmlText,buttons:t.mainBanner.buttons.map((i,a)=>({text:s.mainBanner.buttonLabels[a]??i.text,type:i.type,action:i.action,...i.cookies!=null?{cookies:i.cookies}:{},...i.url!=null?{url:i.url}:{}}))},gpcBanner:{position:t.gpcBanner.position,overlayOpacity:t.gpcBanner.overlayOpacity,showClose:t.gpcBanner.showClose,showLocaleSwitcher:t.gpcBanner.showLocaleSwitcher,headingTag:t.gpcBanner.headingTag,heading:s.gpcBanner.heading,htmlText:s.gpcBanner.htmlText,buttons:t.gpcBanner.buttons.map((i,a)=>({text:s.gpcBanner.buttonLabels[a]??i.text,type:i.type,action:i.action,...i.cookies!=null?{cookies:i.cookies}:{},...i.url!=null?{url:i.url}:{}}))},preferenceModal:{position:t.preferenceModal.position,overlayOpacity:t.preferenceModal.overlayOpacity,showClose:t.preferenceModal.showClose,showLocaleSwitcher:t.preferenceModal.showLocaleSwitcher,persistent:t.preferenceModal.persistent,headingTag:t.preferenceModal.headingTag,heading:s.preferenceModal.heading,...t.preferenceModal.hasSubheading&&s.preferenceModal.subheading?{subheading:s.preferenceModal.subheading}:{},...s.preferenceModal.htmlText?{htmlText:s.preferenceModal.htmlText}:{},buttons:t.preferenceModal.buttons.map((i,a)=>({text:s.preferenceModal.buttonLabels[a]??i.text,type:i.type,action:i.action,...i.cookies!=null?{cookies:i.cookies}:{},...i.url!=null?{url:i.url}:{}})),categories:t.preferenceModal.categories.map(i=>{const a=s.preferenceModal.categories.find(c=>c.id===i.id);return{id:i.id,heading:(a==null?void 0:a.heading)||i.id,headingTag:i.headingTag,htmlText:(a==null?void 0:a.htmlText)??"",mandatory:i.mandatory,type:i.type,...i.liEnabled?{legitimateInterest:{enabled:!0}}:{},cookies:i.cookies}})}}}}}function ls({cookies:n}){return n.length?e("div",{class:"overflow-x-auto",children:e("table",{class:"w-full text-xs",children:[e("thead",{children:e("tr",{class:"text-left text-gray-500 border-b border-gray-200",children:[e("th",{class:"pb-2 pr-3 pl-1 font-medium",children:"ID"}),e("th",{class:"pb-2 pr-3 font-medium",children:"Legal Basis"}),e("th",{class:"pb-2 pr-3 font-medium text-center",children:"Mandatory"}),e("th",{class:"pb-2 pr-3 font-medium text-center",children:"GPC"}),e("th",{class:"pb-2 pr-3 font-medium",children:"Expiry"}),e("th",{class:"pb-2 pr-3 font-medium",children:"TCF Vendor"}),e("th",{class:"pb-2 font-medium",children:"CPRA Category"})]})}),e("tbody",{children:n.map(t=>e("tr",{class:"border-b border-gray-100 last:border-0",children:[e("td",{class:"py-1.5 pr-3 pl-1 font-mono text-gray-800",children:t.id}),e("td",{class:"py-1.5 pr-3 text-gray-600",children:t.type??"consent"}),e("td",{class:"py-1.5 pr-3 text-center text-gray-600",children:t.mandatory?"✓":"–"}),e("td",{class:"py-1.5 pr-3 text-center text-gray-600",children:t.listenGpc?"✓":"–"}),e("td",{class:"py-1.5 pr-3 text-gray-600",children:t.expiry!=null?`${t.expiry}d`:"–"}),e("td",{class:"py-1.5 pr-3 text-gray-600",children:t.tcfVendorId??"–"}),e("td",{class:"py-1.5 text-gray-600",children:t.cpraCategory??"–"})]},t.id))})]})}):e("p",{class:"text-xs text-gray-400 italic",children:"No cookies defined in this template."})}function ct({id:n,current:t}){const o=!n||n==="new",[r,s]=g(""),[i,a]=g("en"),[c,l]=g([]),[p,d]=g([]),[u,h]=g(!1),[m,k]=g(""),[y,b]=g(""),[v,w]=g(""),[N,I]=g([]),[x,$]=g([]),[D,T]=g(""),[_,R]=g(null),[U,L]=g(""),[C,O]=g(null),[B,S]=g("en"),[H,X]=g({}),[ge,Cn]=g(""),io=Y(null),[Me,ce]=g(1),[so,ao]=g(!o),[Sn,Tn]=g(!1),[de,W]=g("");j(()=>{Promise.all([ne.list(),re.list()]).then(([f,A])=>{I(f),$(A)}).catch(()=>{})},[]),j(()=>{o||ae.get(n).then(f=>{const A=f;s(A.name),a(A.defaultLocale),S(A.defaultLocale);const M=A.profileJson;Array.isArray(M.regulations)?d(M.regulations):typeof M.regulation=="string"&&M.regulation&&d([M.regulation]),Array.isArray(M.allowedOrigins)&&l(M.allowedOrigins),M.darkMode===!0&&h(!0),M.localeContents&&typeof M.localeContents=="object"&&X(M.localeContents),M.dpdpa&&(k(M.dpdpa.dataFiduciary??""),b(M.dpdpa.grievanceEmail??""),w(M.dpdpa.purposeDescription??""));const V=M.cookieTemplateId,K=M.uiTemplateId;V&&(T(V),ne.get(V).then(R).catch(()=>{})),K&&(L(K),re.get(K).then(O).catch(()=>{}))}).catch(()=>W("Failed to load profile.")).finally(()=>ao(!1))},[n,o]);const lo=f=>{if(T(f),!f){R(null);return}ne.get(f).then(R).catch(()=>R(null))},co=f=>{if(L(f),!f){O(null);return}re.get(f).then(A=>{O(A),X(M=>{const V=Object.keys(M),K=V.length?V:[i],Q={};for(const we of K)Q[we]=M[we]?ss(M[we],A):Se(A);return Q})}).catch(()=>O(null))},ho=f=>{X(A=>({...A,[f]:C?Se(C):{mainBanner:{heading:"",htmlText:"",buttonLabels:[]},gpcBanner:{heading:"",htmlText:"",buttonLabels:[]},preferenceModal:{heading:"",subheading:"",htmlText:"",buttonLabels:[],categories:[]}}})),S(f)},Pe=(f,A,M)=>{X(V=>({...V,[B]:{...V[B],[f]:{...V[B][f],[A]:M}}}))},Ze=(f,A)=>{X(M=>({...M,[B]:{...M[B],preferenceModal:{...M[B].preferenceModal,[f]:A}}}))},In=(f,A,M)=>{X(V=>{var Q;const K=[...((Q=V[B])==null?void 0:Q.preferenceModal.categories)??[]];return K[f]={...K[f],[A]:M},{...V,[B]:{...V[B],preferenceModal:{...V[B].preferenceModal,categories:K}}}})},Xe=(f,A,M)=>{X(V=>{const K=V[B],Q=[...K[f].buttonLabels??[]];return Q[A]=M,{...V,[B]:{...K,[f]:{...K[f],buttonLabels:Q}}}})},po=()=>{const f=H[B];if(!f||!C)return;const A=ns(B,f,C.mainBanner,C.gpcBanner,C.preferenceModal);lt(A,`locale-${B}.json`,"application/json")},uo=()=>{const f=H[B];if(!f||!C)return;const A=ts(B,f,C.mainBanner,C.gpcBanner,C.preferenceModal);lt(A,`locale-${B}.csv`,"text/csv")},go=f=>{Cn("");const A=new FileReader;A.onload=M=>{var K;const V=(K=M.target)==null?void 0:K.result;try{const Q=H[B]??(C?Se(C):{mainBanner:{heading:"",htmlText:"",buttonLabels:[]},gpcBanner:{heading:"",htmlText:"",buttonLabels:[]},preferenceModal:{heading:"",subheading:"",htmlText:"",buttonLabels:[],categories:[]}}),we=f.name.endsWith(".csv")?rs(V,Q):os(V,Q);X(yo=>({...yo,[B]:we}))}catch{Cn("Could not parse the uploaded file. Make sure it matches the Consenti locale template format.")}},A.readAsText(f)},Ee=_&&C?as(_,C,i,H):null,mo=async()=>{if(!r.trim()){W("Profile name is required."),ce(1);return}if(!D){W("Select a cookie template."),ce(2);return}if(!U){W("Select a UI template."),ce(2);return}const f=p.includes("dpdpa")&&m.trim()&&y.trim()?{dataFiduciary:m.trim(),grievanceEmail:y.trim(),...v.trim()?{purposeDescription:v.trim()}:{}}:void 0,A={cookieTemplateId:D,uiTemplateId:U,defaultLocale:i,...p.length>0?{regulations:p}:{},...c.length>0?{allowedOrigins:c}:{},...f?{dpdpa:f}:{},...u?{darkMode:!0}:{},localeContents:H};Tn(!0),W("");try{const M={name:r,defaultLocale:i,profileJson:A};o?await ae.create(M):await ae.update(n,M),window.location.hash="#/banners/profiles"}catch{W("Failed to save profile.")}finally{Tn(!1)}},J=H[B],fo=Object.keys(H).length?Object.keys(H):[i];return so?e(F,{title:"Profile Editor",current:t,children:e("p",{class:"text-gray-400 text-sm",children:"Loading…"})}):e(F,{title:o?"New Profile":"Edit Profile",current:t,children:[e(is,{step:Me}),Me===1&&e("div",{class:"space-y-5",children:[e("div",{class:"bg-white border border-gray-200 rounded-lg p-5 space-y-4",children:[e("div",{class:"grid grid-cols-2 gap-4",children:[e("div",{children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:["Profile Name ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:`w-full border rounded px-3 py-2 text-sm ${!r.trim()&&de?"border-red-400":"border-gray-300"}`,value:r,onInput:f=>s(f.target.value),placeholder:"My Website"})]}),e("div",{children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:"Default Locale"}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-2 text-sm",value:i,onInput:f=>a(f.target.value),placeholder:"en"})]})]}),e("div",{children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:["Allowed Domains",e("span",{class:"ml-1.5 text-xs font-normal text-gray-400",children:"Supports wildcards (*.example.com). Leave empty to allow all."})]}),e(Ji,{value:c,onChange:l})]}),e("div",{children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-2",children:["Compliance Regulations",e("span",{class:"ml-1.5 text-xs font-normal text-gray-400",children:"(optional)"})]}),e("div",{class:"grid grid-cols-2 gap-2",children:[{value:"gdpr",label:"GDPR",desc:"EU/EEA opt-in. No tracking until consent is given. Banner shown on first visit."},{value:"ccpa",label:"CCPA",desc:'California opt-out. Cookies default granted. Provide a "Do Not Sell" trigger.'},{value:"cpra",label:"CPRA",desc:"California 2023. Opt-out for sale/sharing; opt-in for sensitive data. GPC honored."},{value:"dpdpa",label:"DPDPA",desc:"India 2023. Opt-in like GDPR. GPC not recognised. Data Fiduciary notice required."}].map(f=>e("label",{class:`flex items-start gap-2.5 p-3 border rounded-lg cursor-pointer transition-colors select-none ${p.includes(f.value)?"border-blue-400 bg-blue-50":"border-gray-200 hover:border-gray-300 bg-white"}`,children:[e("input",{type:"checkbox",class:"mt-0.5 accent-blue-600 shrink-0",checked:p.includes(f.value),onChange:A=>{A.target.checked?d(M=>[...M,f.value]):d(M=>M.filter(V=>V!==f.value))}}),e("div",{children:[e("span",{class:"text-sm font-medium text-gray-700",children:f.label}),e("p",{class:"text-xs text-gray-500 mt-0.5 leading-relaxed",children:f.desc})]})]},f.value))})]}),e("div",{children:e("label",{class:"flex items-center gap-2.5 p-3 border border-gray-200 rounded-lg cursor-pointer hover:border-gray-300 bg-white select-none w-fit",children:[e("input",{type:"checkbox",class:"accent-blue-600",checked:u,onChange:f=>h(f.target.checked)}),e("div",{children:[e("span",{class:"text-sm font-medium text-gray-700",children:"Enable Dark Mode"}),e("p",{class:"text-xs text-gray-500 mt-0.5",children:"Applies dark colour tokens to the consent widget for sites with a dark theme."})]})]})}),p.includes("dpdpa")&&e("div",{class:"border border-amber-200 bg-amber-50 rounded-lg p-4 space-y-3",children:[e("p",{class:"text-xs font-semibold text-amber-800",children:"DPDPA — Required Consent Notice Fields"}),e("div",{class:"grid grid-cols-2 gap-3",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-700 mb-1",children:["Data Fiduciary Name ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:m,onInput:f=>k(f.target.value),placeholder:"Acme Corp Pvt Ltd"})]}),e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-700 mb-1",children:["Grievance Officer Email ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{type:"email",class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:y,onInput:f=>b(f.target.value),placeholder:"grievance@acme.com"})]})]}),e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-700 mb-1",children:"Purpose Description"}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:v,onInput:f=>w(f.target.value),placeholder:"We collect your data to personalise your experience…"})]})]})]}),de&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2",children:de}),e("div",{class:"flex justify-between",children:[e("a",{href:"#/banners/profiles",class:"px-5 py-2 rounded-lg text-sm text-red-600 hover:bg-red-100 transition-colors",children:"Cancel"}),e("button",{type:"button",onClick:()=>{if(!r.trim()){W("Profile name is required before continuing.");return}W(""),ce(2)},class:"bg-blue-600 text-white px-6 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors flex items-center gap-1",children:["Next: Templates ",e(Fe,{size:14})]})]})]}),Me===2&&e("div",{class:"space-y-5",children:[e("div",{class:"bg-white border border-gray-200 rounded-lg p-5",children:[e("div",{class:"flex items-start justify-between gap-4 mb-3",children:[e("div",{children:[e("h3",{class:"text-sm font-semibold text-gray-700",children:"Cookie Template"}),e("p",{class:"text-xs text-gray-500 mt-0.5",children:"Defines which cookies/purposes appear in the preference modal. All fields are derived from the template — read-only in the profile."})]}),e("a",{href:"#/banners/cookie-templates/new",class:"text-xs text-blue-600 hover:underline whitespace-nowrap shrink-0",target:"_blank",children:"+ New ↗"})]}),e("select",{class:"border border-gray-300 rounded px-3 py-2 text-sm w-full max-w-sm mb-4",value:D,onChange:f=>lo(f.target.value),children:[e("option",{value:"",children:"— Select a cookie template —"}),N.map(f=>e("option",{value:f.id,children:[f.name," (",f.cookies.length," cookie",f.cookies.length!==1?"s":"",")"]},f.id))]}),_?e(ls,{cookies:_.cookies}):e("p",{class:"text-xs text-gray-400 italic",children:"No template selected."})]}),e("div",{class:"bg-white border border-gray-200 rounded-lg p-5",children:[e("div",{class:"flex items-start justify-between gap-4 mb-3",children:[e("div",{children:[e("h3",{class:"text-sm font-semibold text-gray-700",children:"UI Template"}),e("p",{class:"text-xs text-gray-500 mt-0.5",children:"Defines banner/modal layout, button count, and category structure. You add text content in Step 3."})]}),e("a",{href:"#/banners/ui-templates/new",class:"text-xs text-blue-600 hover:underline whitespace-nowrap shrink-0",target:"_blank",children:"+ New ↗"})]}),e("select",{class:"border border-gray-300 rounded px-3 py-2 text-sm w-full max-w-sm mb-4",value:U,onChange:f=>co(f.target.value),children:[e("option",{value:"",children:"— Select a UI template —"}),x.map(f=>e("option",{value:f.id,children:f.name},f.id))]}),C&&e("div",{class:"flex flex-wrap gap-3 text-xs text-gray-500",children:[e("span",{children:["Main banner: ",e("strong",{class:"text-gray-700",children:C.mainBanner.position})]}),e("span",{children:["GPC banner: ",e("strong",{class:"text-gray-700",children:C.gpcBanner.position})]}),e("span",{children:["Modal: ",e("strong",{class:"text-gray-700",children:C.preferenceModal.position})]}),e("span",{children:[e("strong",{class:"text-gray-700",children:C.preferenceModal.categories.length})," categories"]}),e("span",{children:[e("strong",{class:"text-gray-700",children:C.mainBanner.buttons.length+C.preferenceModal.buttons.length})," buttons total"]})]})]}),Ee?e(gn,{draft:Ee,expandable:!0}):e("div",{class:"bg-gray-50 border border-dashed border-gray-300 rounded-lg p-6 text-center text-xs text-gray-400",children:"Select both a cookie template and a UI template to see a live preview."}),de&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2",children:de}),e("div",{class:"flex justify-between",children:[e("button",{type:"button",onClick:()=>{W(""),ce(1)},class:"px-5 py-2 rounded-lg text-sm text-gray-600 hover:bg-gray-100 transition-colors flex items-center gap-1",children:[e(qe,{size:14})," Back"]}),e("button",{type:"button",onClick:()=>{if(!D){W("Select a cookie template to continue.");return}if(!U){W("Select a UI template to continue.");return}!H[i]&&C&&X(f=>({...f,[i]:Se(C)})),S(i),W(""),ce(3)},class:"bg-blue-600 text-white px-6 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors flex items-center gap-1",children:["Next: Content ",e(Fe,{size:14})]})]})]}),Me===3&&e("div",{class:"space-y-5",children:[e("div",{class:"bg-white border border-gray-200 rounded-lg p-5",children:[e("div",{class:"flex flex-wrap items-start justify-between gap-3 mb-4",children:[e(Vi,{locales:fo,defaultLocale:i,activeLocale:B,onSelect:S,onAdd:ho}),e("div",{class:"flex items-center gap-1.5 shrink-0",children:[e("button",{type:"button",onClick:po,disabled:!C,class:"flex items-center gap-1 text-xs px-2.5 py-1.5 border border-gray-300 rounded hover:bg-gray-50 text-gray-600 transition-colors disabled:opacity-40",title:"Download locale content as JSON",children:[e(Xn,{size:12})," JSON"]}),e("button",{type:"button",onClick:uo,disabled:!C,class:"flex items-center gap-1 text-xs px-2.5 py-1.5 border border-gray-300 rounded hover:bg-gray-50 text-gray-600 transition-colors disabled:opacity-40",title:"Download locale content as CSV for translation",children:[e(Xn,{size:12})," CSV"]}),B!==i&&e("label",{class:"flex items-center gap-1 text-xs px-2.5 py-1.5 border border-blue-300 rounded hover:bg-blue-50 text-blue-600 cursor-pointer transition-colors",title:"Upload translated JSON or CSV",children:[e(Mi,{size:12})," Import",e("input",{ref:io,type:"file",accept:".json,.csv",class:"hidden",onChange:f=>{var M;const A=(M=f.target.files)==null?void 0:M[0];A&&go(A),f.target.value=""}})]})]})]}),ge&&e("p",{class:"text-xs text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2 mb-3",children:ge}),J&&C?e("div",{class:"space-y-6",children:[e("section",{children:[e("h4",{class:"text-xs font-semibold text-gray-700 uppercase tracking-wide mb-3",children:"Main Banner"}),e("div",{class:"space-y-3",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:["Heading ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:J.mainBanner.heading,onInput:f=>Pe("mainBanner","heading",f.target.value),placeholder:"We value your privacy"})]}),e(De,{label:"Body Text",value:J.mainBanner.htmlText,onChange:f=>Pe("mainBanner","htmlText",f),placeholder:"We use cookies to improve your experience…",rows:4}),C.mainBanner.buttons.length>0&&e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1.5",children:"Button Labels"}),e("div",{class:"grid grid-cols-3 gap-3",children:C.mainBanner.buttons.map((f,A)=>e("div",{children:[e("label",{class:"block text-xs text-gray-400 mb-1",children:[f.action," ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-2 py-1 text-sm",value:J.mainBanner.buttonLabels[A]??"",onInput:M=>Xe("mainBanner",A,M.target.value),placeholder:f.text})]},A))})]})]})]}),e("hr",{}),e("section",{children:[e("h4",{class:"text-xs font-semibold text-gray-700 uppercase tracking-wide mb-3",children:"GPC Banner"}),e("div",{class:"space-y-3",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:"Heading"}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:J.gpcBanner.heading,onInput:f=>Pe("gpcBanner","heading",f.target.value),placeholder:"Global Privacy Control Detected"})]}),e(De,{label:"Body Text",value:J.gpcBanner.htmlText,onChange:f=>Pe("gpcBanner","htmlText",f),rows:3}),C.gpcBanner.buttons.length>0&&e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1.5",children:"Button Labels"}),e("div",{class:"grid grid-cols-3 gap-3",children:C.gpcBanner.buttons.map((f,A)=>e("div",{children:[e("label",{class:"block text-xs text-gray-400 mb-1",children:[f.action," ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-2 py-1 text-sm",value:J.gpcBanner.buttonLabels[A]??"",onInput:M=>Xe("gpcBanner",A,M.target.value),placeholder:f.text})]},A))})]})]})]}),e("hr",{}),e("section",{children:[e("h4",{class:"text-xs font-semibold text-gray-700 uppercase tracking-wide mb-3",children:"Preference Modal"}),e("div",{class:"space-y-3",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:["Heading ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:J.preferenceModal.heading,onInput:f=>Ze("heading",f.target.value),placeholder:"Privacy Preferences"})]}),C.preferenceModal.hasSubheading&&e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:"Subheading"}),e("input",{class:"w-full border border-gray-300 rounded px-3 py-1.5 text-sm",value:J.preferenceModal.subheading,onInput:f=>Ze("subheading",f.target.value),placeholder:"Manage your cookie preferences below."})]}),e(De,{label:"Intro Text (optional)",value:J.preferenceModal.htmlText,onChange:f=>Ze("htmlText",f),placeholder:"Optional intro above categories…",rows:3}),C.preferenceModal.buttons.length>0&&e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1.5",children:"Button Labels"}),e("div",{class:"grid grid-cols-3 gap-3",children:C.preferenceModal.buttons.map((f,A)=>e("div",{children:[e("label",{class:"block text-xs text-gray-400 mb-1",children:[f.action," ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-2 py-1 text-sm",value:J.preferenceModal.buttonLabels[A]??"",onInput:M=>Xe("preferenceModal",A,M.target.value),placeholder:f.text})]},A))})]}),J.preferenceModal.categories.map((f,A)=>{const M=C.preferenceModal.categories.find(V=>V.id===f.id);return e("div",{class:"border border-gray-100 rounded p-3 bg-gray-50 space-y-2",children:[e("div",{class:"flex items-center gap-2",children:[e("span",{class:"text-xs font-semibold text-gray-700",children:f.id||`Category ${A+1}`}),(M==null?void 0:M.mandatory)&&e("span",{class:"text-xs bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded-full",children:"mandatory"}),(M==null?void 0:M.type)==="legitimate_interest"&&e("span",{class:"text-xs bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded-full",children:"LI"})]}),e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-0.5",children:["Category Heading ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:"w-full border border-gray-300 rounded px-2 py-1.5 text-sm",value:f.heading,onInput:V=>In(A,"heading",V.target.value),placeholder:f.id})]}),e(De,{label:"Category Description",value:f.htmlText,onChange:V=>In(A,"htmlText",V),placeholder:"Description of this cookie category…",rows:3})]},f.id)})]})]})]}):e("p",{class:"text-xs text-gray-400 italic py-4 text-center",children:U&&!C?"Loading template…":"Select a UI template in step 2 to begin editing content."})]}),Ee&&e(gn,{draft:Ee,expandable:!0}),de&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2",children:de}),e("div",{class:"flex justify-between",children:[e("button",{type:"button",onClick:()=>{W(""),ce(2)},class:"px-5 py-2 rounded-lg text-sm text-gray-600 hover:bg-gray-100 transition-colors flex items-center gap-1",children:[e(qe,{size:14})," Back"]}),e("button",{type:"button",onClick:mo,disabled:Sn,class:"bg-blue-600 text-white px-6 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors",children:Sn?"Saving…":o?"Create Profile":"Save Profile"})]})]})]})}function cs({current:n}){const[t,o]=g([]),[r,s]=g(!0),{requestConfirm:i,dialog:a}=ue(),c=()=>{s(!0),ne.list().then(o).catch(()=>o([])).finally(()=>s(!1))};j(()=>{c()},[]);const l=async(d,u)=>{await i({title:`Delete "${u}"?`,message:"This cookie template will be permanently removed. Profiles linked to it will lose their cookie definitions."})&&(await ne.delete(d).catch(()=>null),c())},p=async(d,u)=>{await ne.copy(d,`${u} (Copy)`).catch(()=>null),c()};return e(F,{title:"Cookie Templates",current:n,children:[a,e("div",{class:"flex justify-between items-center mb-4",children:[e("p",{class:"text-sm text-gray-500",children:[t.length," template",t.length!==1?"s":""]}),e("a",{href:"#/banners/cookie-templates/new",class:"bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors",children:"+ New Cookie Template"})]}),r&&e("p",{class:"text-sm text-gray-400",children:"Loading…"}),!r&&t.length===0&&e("div",{class:"bg-white border border-gray-200 rounded-lg p-8 text-center",children:[e("p",{class:"text-gray-500 text-sm mb-2",children:"No cookie templates yet."}),e("p",{class:"text-gray-400 text-xs",children:"Cookie templates define the list of cookies/purposes for your consent profiles. Create one to reuse across multiple profiles."})]}),!r&&t.length>0&&e("div",{class:"space-y-3",children:t.map(d=>e("div",{class:"bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-between gap-4",children:[e("div",{class:"flex-1 min-w-0",children:[e("p",{class:"font-medium text-sm text-gray-900",children:d.name}),e("p",{class:"text-xs text-gray-500 mt-0.5",children:[d.cookies.length," cookie",d.cookies.length!==1?"s":""," — ",d.cookies.filter(u=>u.mandatory).length," mandatory"," · ","Updated ",new Date(d.updatedAt).toLocaleDateString()]}),e("div",{class:"flex flex-wrap gap-1 mt-2",children:d.cookies.map(u=>e("span",{class:`text-xs px-2 py-0.5 rounded-full ${u.mandatory?"bg-blue-100 text-blue-700":"bg-gray-100 text-gray-600"}`,children:[u.id,u.mandatory?" (required)":"",u.tcfVendorId?` TCF:${u.tcfVendorId}`:"",u.cpraCategory?` CPRA:${u.cpraCategory}`:""]},u.id))})]}),e("div",{class:"flex items-center gap-3 shrink-0",children:[e("button",{onClick:()=>p(d.id,d.name),class:"text-gray-400 hover:text-blue-600 transition-colors",title:"Duplicate",children:e(Je,{size:14})}),e("a",{href:`#/banners/cookie-templates/${d.id}`,class:"text-blue-600 hover:text-blue-800 transition-colors",title:"Edit",children:e(xn,{size:14})}),e("button",{onClick:()=>l(d.id,d.name),class:"text-red-400 hover:text-red-600 transition-colors",title:"Delete",children:e(vn,{size:14})})]})]},d.id))})]})}function ds({vendorId:n,vendorName:t,onSelect:o,disabled:r}){const[s,i]=g(""),[a,c]=g([]),[l,p]=g(!1),[d,u]=g(!1),h=Y(null),m=Y(null);j(()=>{const w=N=>{m.current&&!m.current.contains(N.target)&&p(!1)};return document.addEventListener("mousedown",w),()=>document.removeEventListener("mousedown",w)},[]);const k=w=>{if(!w.trim()){c([]);return}u(!0),P(`/tcf/vendors?q=${encodeURIComponent(w)}&limit=20`).then(N=>c(N.vendors)).catch(()=>c([])).finally(()=>u(!1))},y=w=>{i(w),p(!0),h.current&&clearTimeout(h.current),h.current=setTimeout(()=>k(w),250)},b=w=>{o(w),i(""),c([]),p(!1)},v=()=>{o(null),i(""),c([])};return n&&!s?e("div",{class:"flex items-center gap-1 text-xs",children:[e("span",{class:"font-mono text-blue-700 bg-blue-50 border border-blue-200 px-1.5 py-0.5 rounded",children:n}),e("span",{class:"text-gray-600 truncate max-w-[100px]",title:t,children:t??`Vendor ${n}`}),!r&&e("button",{type:"button",onClick:v,class:"text-gray-400 hover:text-red-500 ml-1 leading-none",title:"Clear vendor",children:"✕"})]}):e("div",{class:"relative",ref:m,children:[e("input",{type:"text",value:s,placeholder:"Search vendor…",disabled:r,onInput:w=>y(w.target.value),onFocus:()=>{s&&p(!0)},class:"border border-gray-300 rounded px-2 py-1 text-xs w-36 disabled:bg-gray-50"}),l&&(d||a.length>0)&&e("div",{class:"absolute z-50 top-full left-0 mt-0.5 w-64 bg-white border border-gray-200 rounded shadow-lg max-h-48 overflow-y-auto text-xs",children:[d&&e("p",{class:"px-3 py-2 text-gray-400",children:"Searching…"}),!d&&a.map(w=>e("button",{type:"button",onMouseDown:()=>b(w),class:"w-full text-left px-3 py-2 hover:bg-blue-50 flex items-center justify-between gap-2",children:[e("span",{class:"font-medium text-gray-800 truncate",children:w.name}),e("span",{class:"shrink-0 font-mono text-gray-400",children:["#",w.id]})]},w.id)),!d&&a.length===0&&s&&e("p",{class:"px-3 py-2 text-gray-400",children:"No vendors found"})]})]})}function hs({cookie:n,idx:t,onChange:o,onRemove:r,duplicateIds:s,tcfEnabled:i,showCpra:a}){const c=(m,k)=>o({...n,[m]:k}),l=!n.id.trim(),p=!l&&((s==null?void 0:s.has(n.id.trim()))??!1),d=l?"Required":p?"Duplicate ID":"",u=n.expiry<1?"Must be ≥ 1":"",h=t!==void 0?t%2===0?"bg-white":"bg-gray-50/50":"bg-white";return e("tr",{class:`border-b align-middle text-xs ${h}`,children:[e("td",{class:"py-2.5 pr-3 pl-1",children:[e("input",{class:`border rounded px-2 py-1.5 w-40 font-mono ${d?"border-red-400 bg-red-50":"border-gray-300"}`,value:n.id,onInput:m=>c("id",m.target.value),placeholder:"analytics_storage"}),d&&e("p",{class:"text-xs text-red-500 mt-0.5",children:d})]}),e("td",{class:"py-2.5 pr-3",children:e("select",{class:"border border-gray-300 rounded pl-2 pr-4 py-1.5 text-xs w-36",value:n.type,onChange:m=>c("type",m.target.value),children:[e("option",{value:"consent",children:"Consent"}),e("option",{value:"legitimate_interest",children:"Legitimate Interest"})]})}),e("td",{class:"py-2.5 pr-3 text-center",children:e("input",{type:"checkbox",class:"w-4 h-4 accent-blue-600",checked:n.mandatory,onChange:m=>c("mandatory",m.target.checked)})}),e("td",{class:"py-2.5 pr-3 text-center",children:e("input",{type:"checkbox",class:"w-4 h-4 accent-blue-600",checked:n.listenGpc,disabled:n.mandatory,onChange:m=>c("listenGpc",m.target.checked),title:n.mandatory?"Mandatory cookies are never auto-denied by GPC":""})}),e("td",{class:"py-2.5 pr-3",children:[e("div",{class:"flex items-center gap-1",children:[e("input",{type:"number",min:"1",max:"3650",class:`border rounded px-2 py-1.5 w-16 ${u?"border-red-400 bg-red-50":"border-gray-300"}`,value:n.expiry,onInput:m=>c("expiry",parseInt(m.target.value)||1)}),e("span",{class:"text-gray-400 text-xs",children:"days"})]}),u&&e("p",{class:"text-xs text-red-500 mt-0.5",children:u})]}),i&&e("td",{class:"py-2.5 pr-3",children:[e(ds,{vendorId:n.tcfVendorId,vendorName:n.tcfVendorId?`Vendor ${n.tcfVendorId}`:void 0,onSelect:m=>o({...n,tcfVendorId:(m==null?void 0:m.id)??void 0,tcfPurposes:(m==null?void 0:m.purposes)??void 0})}),n.tcfPurposes&&n.tcfPurposes.length>0&&e("p",{class:"text-gray-400 mt-0.5",children:["Purposes: ",n.tcfPurposes.join(", ")]})]}),a&&e("td",{class:"py-2.5 pr-3",children:e("select",{class:"border border-gray-300 rounded pl-2 pr-4 py-1.5 text-xs w-28",value:n.cpraCategory??"",onChange:m=>{const k=m.target.value;c("cpraCategory",k||void 0)},children:[e("option",{value:"",children:"—"}),e("option",{value:"sale",children:"Sale"}),e("option",{value:"sharing",children:"Sharing"}),e("option",{value:"sensitive",children:"Sensitive"})]})}),e("td",{class:"py-2.5 text-center",children:e("button",{type:"button",onClick:r,class:"text-red-400 hover:text-red-600 text-lg leading-none px-1",title:"Remove",children:"✕"})})]})}function no(){return[{id:"necessary",type:"consent",mandatory:!0,listenGpc:!1,expiry:365},{id:"analytics_storage",type:"consent",mandatory:!1,listenGpc:!0,expiry:365},{id:"ad_storage",type:"consent",mandatory:!1,listenGpc:!0,expiry:365},{id:"ad_user_data",type:"consent",mandatory:!1,listenGpc:!0,expiry:365},{id:"ad_personalization",type:"consent",mandatory:!1,listenGpc:!0,expiry:365}]}function ps(){return{mainBanner:{position:"bottom",overlayOpacity:0,showClose:!1,headingTag:"h2",buttons:[{text:"Accept All",type:"primary",action:"custom",cookies:"*"},{text:"Reject All",type:"accent",action:"custom",cookies:"!"},{text:"Manage Preferences",type:"secondary",action:"manage"}]},gpcBanner:{position:"bottom",overlayOpacity:0,showClose:!1,headingTag:"h2",buttons:[{text:"Accept All",type:"primary",action:"custom",cookies:"*"},{text:"Continue with GPC",type:"accent",action:"custom",cookies:"!"}]},preferenceModal:{position:"right",overlayOpacity:50,showClose:!0,persistent:!1,headingTag:"h2",hasSubheading:!0,buttons:[{text:"Save Preferences",type:"primary",action:"submit"},{text:"Accept All",type:"secondary",action:"custom",cookies:"*"}],categories:[{id:"necessary",headingTag:"h3",mandatory:!0,type:"consent",liEnabled:!1,cookies:["necessary"]},{id:"analytics",headingTag:"h3",mandatory:!1,type:"consent",liEnabled:!1,cookies:["analytics_storage"]},{id:"marketing",headingTag:"h3",mandatory:!1,type:"consent",liEnabled:!1,cookies:["ad_storage","ad_user_data","ad_personalization"]}]}}}function us(n,t){const o={necessary:{heading:"Necessary",htmlText:"Essential cookies required for the website to function properly. These cannot be disabled."},analytics:{heading:"Analytics",htmlText:"Cookies that help us understand how visitors interact with our website."},marketing:{heading:"Marketing",htmlText:"Cookies used for advertising and tracking across websites."}};return{mainBanner:{heading:"We value your privacy",htmlText:"We use cookies to improve your experience, analyze site usage, and for marketing. Please accept or manage your preferences.",buttonLabels:(t==null?void 0:t.mainBanner.buttons.map(r=>r.text))??[]},gpcBanner:{heading:"Global Privacy Control Detected",htmlText:"We detected a GPC signal from your browser. Your preferences have been automatically adjusted.",buttonLabels:(t==null?void 0:t.gpcBanner.buttons.map(r=>r.text))??[]},preferenceModal:{heading:"Privacy Preferences",subheading:"Manage your cookie preferences below.",htmlText:"",buttonLabels:(t==null?void 0:t.preferenceModal.buttons.map(r=>r.text))??[],categories:n.map(r=>{var s,i;return{id:r.id,heading:((s=o[r.id])==null?void 0:s.heading)??r.id,htmlText:((i=o[r.id])==null?void 0:i.htmlText)??""}})}}}function gs(n,t,o,r,s,i,a,c){const l={};for(const[p,d]of Object.entries(r))l[p]={mainBanner:{position:t.mainBanner.position,overlayOpacity:t.mainBanner.overlayOpacity,showClose:t.mainBanner.showClose,...t.mainBanner.showLocaleSwitcher?{showLocaleSwitcher:!0}:{},headingTag:t.mainBanner.headingTag,heading:d.mainBanner.heading,htmlText:d.mainBanner.htmlText,buttons:t.mainBanner.buttons.map((u,h)=>({...u,text:d.mainBanner.buttonLabels[h]||u.text}))},gpcBanner:{position:t.gpcBanner.position,overlayOpacity:t.gpcBanner.overlayOpacity,showClose:t.gpcBanner.showClose,...t.gpcBanner.showLocaleSwitcher?{showLocaleSwitcher:!0}:{},headingTag:t.gpcBanner.headingTag,heading:d.gpcBanner.heading,htmlText:d.gpcBanner.htmlText,buttons:t.gpcBanner.buttons.map((u,h)=>({...u,text:d.gpcBanner.buttonLabels[h]||u.text}))},preferenceModal:{position:t.preferenceModal.position,overlayOpacity:t.preferenceModal.overlayOpacity,showClose:t.preferenceModal.showClose,...t.preferenceModal.showLocaleSwitcher?{showLocaleSwitcher:!0}:{},persistent:t.preferenceModal.persistent,headingTag:t.preferenceModal.headingTag,heading:d.preferenceModal.heading,...t.preferenceModal.hasSubheading&&d.preferenceModal.subheading?{subheading:d.preferenceModal.subheading}:{},...d.preferenceModal.htmlText?{htmlText:d.preferenceModal.htmlText}:{},buttons:t.preferenceModal.buttons.map((u,h)=>({...u,text:d.preferenceModal.buttonLabels[h]||u.text})),categories:t.preferenceModal.categories.map(u=>{const h=d.preferenceModal.categories.find(m=>m.id===u.id);return{id:u.id,heading:(h==null?void 0:h.heading)??u.id,headingTag:u.headingTag,htmlText:(h==null?void 0:h.htmlText)??"",mandatory:u.mandatory,type:u.type,...u.liEnabled?{legitimateInterest:{enabled:!0}}:{},cookies:u.cookies}})}};return{_meta:s,cookies:n,defaultLocale:o,translations:l}}function ms(){return{id:"",type:"consent",mandatory:!1,listenGpc:!0,expiry:365}}const dt=[{field:"ID",label:"Cookie ID",Icon:bi,description:"A unique machine-readable identifier for this cookie or tracking purpose.",usage:"Must match exactly what your frontend widget references (e.g. analytics_storage, ad_storage). Used in GTM / Google Consent Mode v2 consent states.",laws:[{name:"GDPR",text:"Each processing purpose must be clearly identified and disclosed in your privacy notice (Art. 13/14)."},{name:"ePrivacy",text:"Non-essential cookies require prior informed consent. ID is what links consent to a specific purpose."}],examples:"necessary · analytics_storage · ad_storage · ad_user_data · ad_personalization · functional"},{field:"Legal Basis",label:"Legal Basis",Icon:ri,description:"The legal ground under which you process data associated with this cookie.",usage:["Consent (default): The user actively opts in. Required for advertising, most analytics, and non-essential cookies. Show the banner before any processing.","Legitimate Interest: You may process on a compelling business need, but must pass a 3-part balancing test, document it, and provide an easy objection mechanism."].join(`
|
|
2631
|
-
`),laws:[{name:"GDPR Art. 6(1)(a)",text:"Consent as a lawful basis. Must be freely given, specific, informed, and unambiguous."},{name:"GDPR Art. 6(1)(f)",text:"Legitimate interests. Cannot override fundamental rights/freedoms of data subjects."},{name:"ePrivacy Dir. Art. 5(3)",text:"For cookies: consent is the default rule. LI is generally NOT available for tracking cookies under ePrivacy."}],examples:'Use "Consent" for analytics, advertising, A/B testing. Reserve "Legitimate Interest" only for strictly-server-side processing with legal review.'},{field:"Mandatory",label:"Mandatory (Always Granted)",Icon:Rr,description:"When enabled, this cookie is always granted — the visitor cannot turn it off.",usage:"Use only for cookies that are strictly necessary for the site to function: session tokens, CSRF protection, load balancing, UI preferences set by the user.",laws:[{name:"ePrivacy Dir. Recital 25",text:'"Strictly necessary" cookies are exempt from consent. Only use this flag for cookies that genuinely meet this definition.'},{name:"GDPR",text:"Mandatory cookies still need to be disclosed in your privacy notice. Only the consent UI step is skipped."},{name:"ICO (UK)",text:"Has challenged broad interpretation. Do not mark analytics or advertising cookies as mandatory."}],examples:"session · csrf_token · load_balancer_id · cookie_consent_id · XSRF-TOKEN"},{field:"Listen GPC",label:"Honor GPC Signal",Icon:Wt,description:"When enabled, this cookie is automatically denied if the visitor's browser sends a Global Privacy Control signal.",usage:"GPC is signalled via the Sec-GPC: 1 HTTP header or navigator.globalPrivacyControl. When honored, non-essential cookies are pre-denied before showing any banner.",laws:[{name:"CCPA / CPRA (California)",text:"Legally required to honor GPC as a valid opt-out of sale/sharing. CA AG has enforced this."},{name:"Colorado CPA",text:"Must honor universal opt-out signals including GPC from July 2024."},{name:"Connecticut CTDPA",text:"Must honor GPC from 2024."},{name:"GDPR",text:"No legal obligation, but highly recommended as a privacy-friendly practice."}],examples:"Enable for all non-mandatory cookies. Disable only for strictly necessary (mandatory) cookies."},{field:"Expiry",label:"Re-consent Interval (days)",Icon:tr,description:"How many days until this consent expires and the user is asked again.",usage:"After this period, the consent cookie is treated as expired and the banner is shown again on next visit.",laws:[{name:"CNIL (France)",text:"Consent must be renewed at least every 13 months (397 days). Recommends 6 months for good practice."},{name:"ICO (UK)",text:"No specific maximum set, but recommends reviewing at least annually. Best practice: 6–12 months."},{name:"GDPR",text:"No prescribed expiry, but consent must remain freely revocable and valid. Stale consent may not remain valid."}],examples:"180 days (6 months) — CNIL best practice · 365 days (1 year) — standard · 395 days — CNIL maximum"},{field:"TCF Vendors",label:"TCF Vendor IDs",Icon:er,description:"Links this cookie purpose to one or more IAB-registered ad-tech vendor IDs from the Global Vendor List (GVL). Required to generate a valid TC String.",usage:['Enable the "TCF Vendors" column to expose a vendor ID input per cookie row.',"Enter the numeric GVL ID(s) for every ad-tech vendor that processes data under this purpose (e.g. 755 for Google, 32 for AppNexus).","Consenti encodes these IDs along with the accepted purposes into a Base64 TC String, which ad platforms read from the cookie to determine which vendors are authorised to fire.","Without vendor IDs, the TC String will be invalid and compliant ad platforms will refuse to fire their tags."].join(`
|
|
2632
|
-
`),laws:[{name:"IAB TCF v2.2",text:"A TC String must encode both purpose consents and vendor consents. Each vendor must be explicitly listed by its GVL ID — blanket consent is not permitted."},{name:"GDPR Art. 6(1)(a)",text:"Consent must be specific to each vendor and each purpose. Bundling all vendors into one consent decision is invalid."},{name:"GDPR Art. 13/14",text:"Your privacy notice must name every processor. TCF vendor IDs correspond to the processors you must disclose."},{name:"ePrivacy Dir.",text:"TCF is the standard mechanism for obtaining valid ad-tech consent under ePrivacy across the EU/EEA."}],examples:"755 (Google Advertising Products) · 32 (AppNexus) · 91 (Criteo) · 52 (IAB Europe) — look up IDs on the TCF Vendors page"},{field:"CPRA Category",label:"CPRA Data Category",Icon:Yt,description:"Maps this cookie to a California Privacy Rights Act (CPRA) data category. Required to correctly scope Do Not Sell / Share opt-out rights and Sensitive PI restrictions.",usage:['Enable the "CPRA Category" column to assign a data category to each cookie row.','"Personal Information" (PI): standard consumer data subject to the right to opt-out of sale/sharing.','"Sensitive Personal Information" (SPI): a restricted subset (precise geolocation, race, health, biometrics, etc.) that triggers additional opt-out and use-limitation rights. Consumers can direct you to limit use to what is necessary for providing the service.',`"Non-PI": data that does not meet CCPA's definition of personal information — no opt-out required.`].join(`
|
|
2633
|
-
`),laws:[{name:"CPRA / CCPA § 1798.100",text:"Consumers have the right to know what personal information is collected and to opt-out of its sale or sharing."},{name:"CPRA § 1798.121",text:'Consumers have the right to limit the use and disclosure of sensitive personal information. Businesses must provide a clear "Limit Use of My SPI" link.'},{name:"CPRA § 1798.140(ae)",text:"Sensitive PI includes precise geolocation, racial/ethnic origin, religious beliefs, genetic data, biometric data, health data, sexual orientation, and contents of communications."},{name:"CPRA / GPC",text:'A GPC signal constitutes a valid opt-out of sale and sharing for PI. Pairing CPRA Category with the "Honor GPC" flag ensures the correct cookies are denied automatically.'}],examples:"analytics_storage → PersonalInfo · ad_storage → PersonalInfo · precise_location → SensitivePI · session → Non-PI"}];function ht({id:n,current:t}){const o=!n||n==="new",[r,s]=g(""),[i,a]=g(""),[c,l]=g(no),[p,d]=g(""),[u,h]=g(0),[m,k]=g(!1),[y,b]=g(!1),[v,w]=g(!1),[N,I]=g(!o);j(()=>{!o&&n&&ne.get(n).then(_=>{s(_.name),l(_.cookies)}).catch(()=>d("Template not found.")).finally(()=>I(!1))},[n,o]);const x=c.reduce((_,R)=>{const U=R.id.trim();return U&&(_[U]=(_[U]??0)+1),_},{}),$=new Set(Object.entries(x).filter(([,_])=>_>1).map(([_])=>_)),D=async()=>{let _=!0;if(r.trim()||(a("Template name is required."),_=!1),c.some(R=>!R.id.trim())&&(d("All cookies must have an ID."),_=!1),$.size>0&&(d("Cookie IDs must be unique."),_=!1),c.some(R=>R.expiry<1)&&(d("Expiry must be at least 1 day."),_=!1),!!_){if(!o&&n){const U=(await ae.list().catch(()=>[])).filter(L=>{var O;const C=L.profileJson;return C.cookieTemplateId===n||((O=C._meta)==null?void 0:O.cookieTemplateId)===n});if(U.length>0){const L=U.map(O=>`"${O.name}"`).join(", ");if(!window.confirm(`This template is used by ${U.length} profile${U.length!==1?"s":""}: ${L}.
|
|
2634
|
-
|
|
2635
|
-
Saving will apply cookie changes to all of them. Continue?`))return}}d(""),a(""),w(!0);try{o?await ne.create({name:r,cookies:c}):await ne.update(n,{name:r,cookies:c}),window.location.hash="#/banners/cookie-templates"}catch{d("Failed to save template.")}finally{w(!1)}}},T=dt[u];return e(F,{title:o?"New Cookie Template":"Edit Cookie Template",current:t,children:[N&&e("p",{class:"text-sm text-gray-400",children:"Loading template…"}),!N&&e("div",{class:"space-y-5",children:e("div",{class:"grid grid-cols-1 xl:grid-cols-[2.25fr_1.75fr] gap-5 items-start",children:[e("div",{class:"bg-white border border-gray-200 rounded-lg p-5",children:[e("div",{class:"py-5",children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:["Template Name ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:`w-full border rounded px-3 py-2 text-sm ${i?"border-red-400 bg-red-50":"border-gray-300"}`,value:r,onInput:_=>{s(_.target.value),a("")},placeholder:"e.g. Google Analytics + Ads"}),i&&e("p",{class:"text-xs text-red-500 mt-1",children:i})]}),e("div",{class:"flex items-center justify-between mb-3",children:[e("div",{children:[e("h3",{class:"text-sm font-semibold text-gray-700",children:"Cookie Definitions"}),e("p",{class:"text-xs text-gray-500 mt-0.5",children:["IDs must match what the frontend widget uses (e.g. ",e("code",{class:"font-mono bg-gray-100 px-1 rounded",children:"analytics_storage"}),")."]})]}),e("div",{class:"flex items-center gap-3",children:[e("label",{class:"flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer select-none",children:[e("input",{type:"checkbox",class:"accent-blue-600",checked:m,onChange:_=>k(_.target.checked)}),"TCF Vendors"]}),e("label",{class:"flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer select-none",children:[e("input",{type:"checkbox",class:"accent-blue-600",checked:y,onChange:_=>b(_.target.checked)}),"CPRA Category"]}),e("button",{type:"button",onClick:()=>l(_=>[..._,ms()]),class:"text-sm text-blue-600 hover:underline shrink-0",children:"+ Add Cookie"})]})]}),e("div",{class:"overflow-x-auto",children:e("table",{class:"w-full text-xs",children:[e("thead",{children:e("tr",{class:"text-left text-gray-500 border-b border-gray-200",children:[e("th",{class:"pb-2 pr-3 pl-1 font-medium",children:e("button",{type:"button",class:"hover:text-blue-600 transition-colors",onClick:()=>h(0),children:["ID / Name ",e("span",{class:"text-red-500",children:"*"})]})}),e("th",{class:"pb-2 pr-3 font-medium",children:e("button",{type:"button",class:"hover:text-blue-600 transition-colors",onClick:()=>h(1),children:"Legal Basis"})}),e("th",{class:"pb-2 pr-3 font-medium text-center",children:e("button",{type:"button",class:"hover:text-blue-600 transition-colors",onClick:()=>h(2),children:"Mandatory"})}),e("th",{class:"pb-2 pr-3 font-medium text-center",children:e("button",{type:"button",class:"hover:text-blue-600 transition-colors",onClick:()=>h(3),children:"GPC"})}),e("th",{class:"pb-2 pr-3 font-medium",children:e("button",{type:"button",class:"hover:text-blue-600 transition-colors",onClick:()=>h(4),children:"Expiry"})}),m&&e("th",{class:"pb-2 pr-3 font-medium text-blue-700",children:"TCF Vendor"}),y&&e("th",{class:"pb-2 pr-3 font-medium text-purple-700",children:"CPRA Category"}),e("th",{class:"pb-2 text-center font-medium"})]})}),e("tbody",{children:c.map((_,R)=>e(hs,{cookie:_,idx:R,onChange:U=>l(L=>L.map((C,O)=>O===R?U:C)),onRemove:()=>l(U=>U.filter((L,C)=>C!==R)),duplicateIds:$,tcfEnabled:m,showCpra:y},R))})]})}),c.length===0&&e("p",{class:"text-sm text-gray-400 text-center py-4",children:'No cookies defined. Click "+ Add Cookie" to start.'}),e("div",{className:"mt-7",children:[p&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2",children:p}),e("div",{class:"flex justify-between",children:[e("a",{href:"#/banners/cookie-templates",class:"px-5 py-2 rounded-lg text-sm text-red-600 hover:bg-red-100 transition-colors",children:"Cancel"}),e("button",{type:"button",onClick:D,disabled:v,class:"bg-blue-600 text-white px-5 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors",children:v?"Saving…":o?"Create Template":"Save Template"})]})]})]}),e("div",{class:"bg-white border border-gray-200 rounded-lg p-5 xl:sticky xl:top-4",children:[e("p",{class:"text-xs text-gray-400 mb-3 uppercase tracking-wide font-semibold",children:"Click a column header to learn more"}),e("div",{class:"flex flex-wrap gap-1.5 mb-4",children:dt.map((_,R)=>e("button",{type:"button",onClick:()=>h(R),class:`text-xs px-2.5 py-1 rounded-full border transition-colors ${u===R?"bg-blue-600 text-white border-blue-600":"border-gray-300 text-gray-600 hover:border-blue-400"}`,children:_.field},R))}),T&&e("div",{class:"space-y-3",children:[e("div",{class:"flex items-center gap-2",children:[e(T.Icon,{size:18,className:"text-gray-600 shrink-0"}),e("h4",{class:"font-semibold text-gray-800 text-sm",children:T.label})]}),e("p",{class:"text-xs text-gray-600 leading-relaxed",children:T.description}),e("div",{class:"bg-blue-50 border border-blue-100 rounded p-3",children:[e("p",{class:"text-xs font-semibold text-blue-700 mb-1",children:"Usage"}),e("p",{class:"text-xs text-blue-800 leading-relaxed whitespace-pre-line",children:T.usage})]}),e("div",{children:[e("p",{class:"text-xs font-semibold text-gray-600 mb-2",children:"Regulatory guidance"}),e("div",{class:"space-y-2",children:T.laws.map((_,R)=>e("div",{class:"bg-amber-50 border border-amber-100 rounded p-2.5",children:[e("span",{class:"text-xs font-bold text-amber-700",children:[_.name," "]}),e("span",{class:"text-xs text-amber-800",children:_.text})]},R))})]}),e("div",{class:"bg-gray-50 border border-gray-200 rounded p-2.5",children:[e("p",{class:"text-xs font-semibold text-gray-600 mb-1",children:"Examples"}),e("p",{class:"text-xs text-gray-500 font-mono leading-relaxed",children:T.examples})]})]})]})]})})]})}function fs({current:n}){const[t,o]=g([]),[r,s]=g(!0),{requestConfirm:i,dialog:a}=ue(),c=()=>{s(!0),re.list().then(o).catch(()=>o([])).finally(()=>s(!1))};j(()=>{c()},[]);const l=async(d,u)=>{await i({title:`Delete "${u}"?`,message:"This UI template will be permanently removed. Profiles linked to it will lose their UI settings."})&&(await re.delete(d).catch(()=>null),c())},p=async(d,u)=>{await re.copy(d,`${u} (Copy)`).catch(()=>null),c()};return e(F,{title:"UI Templates",current:n,children:[a,e("div",{class:"flex justify-between items-center mb-4",children:[e("p",{class:"text-sm text-gray-500",children:[t.length," template",t.length!==1?"s":""]}),e("a",{href:"#/banners/ui-templates/new",class:"bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors",children:"+ New UI Template"})]}),r&&e("p",{class:"text-sm text-gray-400",children:"Loading…"}),!r&&t.length===0&&e("div",{class:"bg-white border border-gray-200 rounded-lg p-8 text-center",children:[e("p",{class:"text-gray-500 text-sm mb-2",children:"No UI templates yet."}),e("p",{class:"text-gray-400 text-xs",children:"UI templates define the visual configuration — positions, buttons, categories — for your consent banners and modals."})]}),!r&&t.length>0&&e("div",{class:"space-y-3",children:t.map(d=>e("div",{class:"bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-between gap-4",children:[e("div",{class:"flex-1 min-w-0",children:[e("p",{class:"font-medium text-sm text-gray-900",children:d.name}),e("div",{class:"flex flex-wrap gap-3 mt-1 text-xs text-gray-500",children:[e("span",{children:["Main banner: ",e("span",{class:"text-gray-700",children:d.mainBanner.position})]}),e("span",{children:["GPC banner: ",e("span",{class:"text-gray-700",children:d.gpcBanner.position})]}),e("span",{children:["Modal: ",e("span",{class:"text-gray-700",children:d.preferenceModal.position})]}),e("span",{children:[d.preferenceModal.categories.length," categories"]}),e("span",{children:["Updated ",new Date(d.updatedAt).toLocaleDateString()]})]}),e("div",{class:"flex flex-wrap gap-1 mt-2",children:d.mainBanner.buttons.map((u,h)=>e("span",{class:"text-xs px-2 py-0.5 bg-gray-100 text-gray-600 rounded-full",children:[u.type,": ",u.text]},h))})]}),e("div",{class:"flex items-center gap-3 shrink-0",children:[e("button",{onClick:()=>p(d.id,d.name),class:"text-gray-400 hover:text-blue-600 transition-colors",title:"Duplicate",children:e(Je,{size:14})}),e("a",{href:`#/banners/ui-templates/${d.id}`,class:"text-blue-600 hover:text-blue-800 transition-colors",title:"Edit",children:e(xn,{size:14})}),e("button",{onClick:()=>l(d.id,d.name),class:"text-red-400 hover:text-red-600 transition-colors",title:"Delete",children:e(vn,{size:14})})]})]},d.id))})]})}const Ce=["h1","h2","h3","h4","h5","h6"];function ys(n){return/^[a-zA-Z][a-zA-Z0-9-]*$/.test(n)}function kn({value:n,onChange:t,label:o="Heading Tag",required:r}){const[s,i]=g(()=>!Ce.includes(n)),[a,c]=g("");j(()=>{Ce.includes(n)&&i(!1)},[n]);const l=d=>{i(d),c(""),d||t(Ce.includes(n)?n:"h2")},p=d=>{t(d),c(d&&!ys(d)?"Invalid HTML tag name (letters, digits, hyphens only)":"")};return e("div",{children:[e("div",{class:"flex items-center justify-between mb-1",children:[e("label",{class:"text-xs font-medium text-gray-600",children:[o,r&&e("span",{class:"text-red-500 ml-0.5",children:"*"})]}),e("label",{class:"flex items-center gap-1.5 text-xs text-gray-500 cursor-pointer select-none",children:[e("input",{type:"checkbox",class:"w-3.5 h-3.5",checked:s,onChange:d=>l(d.target.checked)}),"Custom tag"]})]}),s?e("div",{children:[e("input",{class:`w-full border rounded px-2 py-1.5 text-sm font-mono ${a?"border-red-400 bg-red-50":"border-gray-300"}`,value:n,onInput:d=>p(d.target.value),placeholder:"e.g. p, div, span"}),a&&e("p",{class:"text-xs text-red-500 mt-0.5",children:a})]}):e("select",{class:"w-full border border-gray-300 rounded px-2 py-1.5 text-sm font-mono",value:Ce.includes(n)?n:"h2",onChange:d=>t(d.target.value),children:Ce.map(d=>e("option",{value:d,children:d},d))})]})}function to({value:n,onChange:t}){return e("div",{class:"flex items-center gap-3",children:[e("input",{type:"range",min:"0",max:"100",class:"flex-1 h-1.5 rounded-full appearance-none bg-gray-200 accent-blue-600 cursor-pointer",value:n,onInput:o=>t(parseInt(o.target.value))}),e("span",{class:"text-sm font-mono text-gray-700 w-10 text-right shrink-0",children:[n,"%"]})]})}function bs(n){return n==="*"?"* — Accept all cookies":n==="!"?"! — Deny non mandatory cookies":Array.isArray(n)&&n.length>0?n.join(", "):""}function oo({value:n,onChange:t,showSpecial:o=!0,extraCookies:r,disabled:s=!1,placeholder:i="Select cookies…"}){const[a,c]=g(!1),[l,p]=g(null),[d,u]=g([]),h=Y(null),m=Y(null);j(()=>{ne.list().then(u).catch(()=>{})},[]),j(()=>{const x=$=>{var _,R;const D=(_=h.current)==null?void 0:_.contains($.target),T=(R=m.current)==null?void 0:R.contains($.target);!D&&!T&&c(!1)};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[]),j(()=>{if(!a)return;const x=$=>{var D;(D=m.current)!=null&&D.contains($.target)||c(!1)};return window.addEventListener("scroll",x,{capture:!0,passive:!0}),()=>window.removeEventListener("scroll",x,!0)},[a]);const k=()=>{if(!s){if(!a&&h.current){const x=h.current.getBoundingClientRect();window.innerHeight-x.bottom>=280?p({top:x.bottom+4,left:x.left}):p({bottom:window.innerHeight-x.top+4,left:x.left})}c(x=>!x)}},y=d.flatMap(x=>x.cookies.map($=>({id:$.id,source:x.name}))),b=[...r??[],...y].filter((x,$,D)=>D.findIndex(T=>T.id===x.id)===$),v=x=>x==="*"?n==="*":x==="!"?n==="!":Array.isArray(n)&&n.includes(x),w=x=>{if(x==="*"){t(n==="*"?void 0:"*");return}if(x==="!"){t(n==="!"?void 0:"!");return}const $=Array.isArray(n)?[...n]:[],D=$.includes(x)?$.filter(T=>T!==x):[...$,x];t(D.length?D:void 0)},N=bs(n),I={position:"fixed",zIndex:9999,left:`${(l==null?void 0:l.left)??0}px`,width:"288px",...(l==null?void 0:l.top)!==void 0?{top:`${l.top}px`}:{bottom:`${(l==null?void 0:l.bottom)??0}px`}};return e("div",{class:"relative",ref:h,children:[e("button",{type:"button",disabled:s,onClick:k,class:`w-full text-left border rounded px-2 py-1.5 text-xs flex items-center justify-between gap-1 ${s?"bg-gray-50 text-gray-400 border-gray-200 cursor-not-allowed":"bg-white border-gray-300 hover:border-blue-400 cursor-pointer transition-colors"}`,children:[e("span",{class:N?"font-mono text-gray-800":"text-gray-400",children:N||i}),e("span",{class:"text-gray-400 shrink-0 ml-1",children:a?e(lr,{size:12}):e(Kt,{size:12})})]}),a&&l&&qt(e("div",{ref:m,style:I,class:"bg-white border border-gray-200 rounded-lg shadow-xl max-h-64 overflow-y-auto",children:[o&&e("div",{class:"border-b border-gray-100",children:[e("p",{class:"text-xs font-semibold text-gray-400 px-3 pt-2 pb-1 uppercase tracking-wide",children:"Special"}),[{id:"*",label:"Accept all cookies",sub:"Grants every cookie in this profile"},{id:"!",label:"Reject non mandatory cookies",sub:"Denies non mandatory cookies in this profile"}].map(x=>e("button",{type:"button",class:`w-full text-left flex items-center gap-2.5 px-3 py-2 hover:bg-gray-50 ${v(x.id)?"bg-blue-50":""}`,onClick:()=>w(x.id),children:[e("span",{class:`w-4 h-4 rounded border flex-shrink-0 flex items-center justify-center text-xs ${v(x.id)?"bg-blue-600 border-blue-600 text-white":"border-gray-300"}`,children:v(x.id)?e(_e,{size:10}):null}),e("div",{class:"min-w-0",children:[e("span",{class:"font-mono text-xs font-semibold text-gray-700",children:x.id}),e("span",{class:"text-xs text-gray-400 ml-1.5",children:x.label}),e("p",{class:"text-xs text-gray-400",children:x.sub})]})]},x.id))]}),e("div",{children:[e("p",{class:"text-xs font-semibold text-gray-400 px-3 pt-2 pb-1 uppercase tracking-wide",children:"Cookie IDs"}),b.length===0?e("p",{class:"text-xs text-gray-400 px-3 pb-3",children:["No cookies defined yet."," ",e("a",{href:"#/banners/cookie-templates/new",class:"text-blue-600 hover:underline",target:"_blank",children:"Create a template ↗"})]}):b.map(x=>e("button",{type:"button",class:`w-full text-left flex items-center gap-2.5 px-3 py-1.5 hover:bg-gray-50 ${v(x.id)?"bg-blue-50":""}`,onClick:()=>w(x.id),children:[e("span",{class:`w-4 h-4 rounded border flex-shrink-0 flex items-center justify-center text-xs ${v(x.id)?"bg-blue-600 border-blue-600 text-white":"border-gray-300"}`,children:v(x.id)?e(_e,{size:10}):null}),e("div",{class:"min-w-0 flex items-baseline gap-1.5",children:[e("span",{class:"font-mono text-xs text-gray-800",children:x.id}),x.source&&e("span",{class:"text-xs text-gray-400 truncate",children:["· ",x.source]})]})]},x.id))]})]}),document.body)]})}const xs=["primary","secondary","accent","text"],vs=["custom","submit","manage","close","link"],_s={primary:"Primary",secondary:"Secondary",accent:"Accent",text:"Text"},ks={custom:"Custom (grant/deny)",submit:"Submit (save toggles)",manage:"Manage (open modal)",close:"Close (dismiss)",link:"Link (open URL)"};function ws({btn:n,onChange:t,onRemove:o,extraCookies:r}){var c;const s=(l,p)=>t({...n,[l]:p}),i=!n.text.trim(),a=n.action==="link"&&!((c=n.url)!=null&&c.trim());return e("div",{class:"border border-gray-100 rounded p-2.5 bg-gray-50 space-y-2",children:e("div",{class:"grid grid-cols-[1fr_1fr_1fr_2fr_auto] gap-2 items-start",children:[e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:["Label ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:`w-full border rounded px-2 py-1 text-xs ${i?"border-red-300 bg-red-50":"border-gray-300"}`,value:n.text,onInput:l=>s("text",l.target.value),placeholder:"Button text"}),i&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"Required"})]}),e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:"Style"}),e("select",{class:"w-full border border-gray-300 rounded px-2 py-1 text-xs",value:n.type,onChange:l=>s("type",l.target.value),children:xs.map(l=>e("option",{value:l,children:_s[l]},l))})]}),e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:"Action"}),e("select",{class:"w-full border border-gray-300 rounded px-2 py-1 text-xs",value:n.action,onChange:l=>{const p=l.target.value,d={...n,action:p,cookies:p==="custom"?n.cookies??"*":void 0,url:p==="link"?n.url??"":void 0,type:p==="link"&&n.type!=="text"?"text":n.type};t(d)},children:vs.map(l=>e("option",{value:l,children:ks[l]},l))})]}),n.action==="custom"?e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:["Cookies to grant/deny ",e("span",{class:"text-red-500",children:"*"})]}),e(oo,{value:n.cookies,onChange:l=>s("cookies",l),showSpecial:!0,extraCookies:r}),!n.cookies&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"Required for custom action"})]}):n.action==="link"?e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:["URL ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{type:"url",class:`w-full border rounded px-2 py-1 text-xs ${a?"border-red-300 bg-red-50":"border-gray-300"}`,value:n.url??"",onInput:l=>s("url",l.target.value),placeholder:"https://example.com/privacy"}),a&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"Required for link action"})]}):e("div",{}),e("div",{class:"flex items-end pb-0.5",children:e("button",{type:"button",onClick:o,class:"text-red-400 hover:text-red-600 text-lg leading-none p-0.5",title:"Remove button",children:"✕"})})]})})}function ro({buttons:n,onChange:t,cookieOptions:o}){return e("div",{class:"space-y-2",children:[n.map((r,s)=>e(ws,{btn:r,onChange:i=>t(n.map((a,c)=>c===s?i:a)),onRemove:()=>t(n.filter((i,a)=>a!==s)),extraCookies:o},s)),e("button",{type:"button",onClick:()=>t([...n,{text:"",type:"primary",action:"custom",cookies:"*"}]),class:"text-xs text-blue-600 hover:underline",children:"+ Add Button"})]})}const Cs=["top","bottom","middle","left-bottom","right-bottom"];function pt({label:n,value:t,onChange:o,cookieOptions:r}){const s=(i,a)=>o({...t,[i]:a});return e("div",{class:"space-y-5",children:[n&&e("p",{class:"text-xs text-gray-500 italic",children:n}),e("div",{class:"grid grid-cols-2 lg:grid-cols-3 gap-4",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:["Position ",e("span",{class:"text-red-500",children:"*"})]}),e("select",{class:"w-full border border-gray-300 rounded px-2 py-1.5 text-sm",value:t.position,onChange:i=>s("position",i.target.value),children:Cs.map(i=>e("option",{value:i,children:i},i))})]}),e(kn,{value:t.headingTag,onChange:i=>s("headingTag",i)}),e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:"Overlay opacity"}),e(to,{value:t.overlayOpacity,onChange:i=>s("overlayOpacity",i)}),e("p",{class:"text-xs text-gray-400 mt-0.5",children:"0% = transparent (page still interactive) · 100% = darkened"})]}),e("div",{class:"flex items-end pb-1",children:e("label",{class:"flex items-center gap-2 text-xs font-medium text-gray-600 cursor-pointer",children:[e("input",{type:"checkbox",class:"w-4 h-4",checked:t.showClose,onChange:i=>s("showClose",i.target.checked)}),"Show ",e(Ye,{size:10,className:"inline-block mx-0.5"})," close button"]})}),e("div",{class:"flex items-end pb-1",children:e("label",{class:"flex items-center gap-2 text-xs font-medium text-gray-600 cursor-pointer",children:[e("input",{type:"checkbox",class:"w-4 h-4",checked:t.showLocaleSwitcher??!1,onChange:i=>s("showLocaleSwitcher",i.target.checked)}),"🌐 Show locale switcher"]})})]}),e("div",{children:[e("h4",{class:"text-xs font-semibold text-gray-600 mb-2 uppercase tracking-wide",children:"Buttons"}),e(ro,{buttons:t.buttons,onChange:i=>s("buttons",i),cookieOptions:r})]})]})}function Ss({cat:n,onChange:t,onRemove:o,cookieOptions:r}){const s=!n.id.trim();return e("div",{class:`border rounded-lg p-3 bg-gray-50 ${s?"border-red-200":"border-gray-200"}`,children:e("div",{class:"grid grid-cols-2 lg:grid-cols-[1fr_1fr_1fr_auto] gap-3",children:[e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:["Category ID ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:`w-full border rounded px-2 py-1 font-mono text-xs ${s?"border-red-300 bg-red-50":"border-gray-300"}`,value:n.id,onInput:i=>t({id:i.target.value}),placeholder:"analytics"}),s&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"Required"})]}),e(kn,{value:n.headingTag,onChange:i=>t({headingTag:i}),label:"Heading Tag"}),e("div",{children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:"Legal Basis"}),e("select",{class:"w-full border border-gray-300 rounded px-2 py-1 text-xs",value:n.type,onChange:i=>t({type:i.target.value}),children:[e("option",{value:"consent",children:"Consent"}),e("option",{value:"legitimate_interest",children:"Legitimate Interest"})]})]}),e("div",{class:"flex items-start",children:e("button",{type:"button",onClick:o,class:"text-red-400 hover:text-red-600 text-lg leading-none p-1",title:"Remove category",children:"✕"})}),e("div",{class:"col-span-2",children:[e("label",{class:"block text-xs text-gray-500 mb-0.5",children:["Cookies in this category ",e("span",{class:"text-red-500",children:"*"})]}),e(oo,{value:n.cookies.length?n.cookies:void 0,onChange:i=>t({cookies:Array.isArray(i)?i:[]}),showSpecial:!1,extraCookies:r}),!n.cookies.length&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"Select at least one cookie"})]}),e("div",{class:"flex items-end gap-4 pb-2",children:[e("label",{class:"flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer",children:[e("input",{type:"checkbox",checked:n.mandatory,onChange:i=>t({mandatory:i.target.checked})}),"Mandatory"]}),e("label",{class:`flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer ${n.type!=="legitimate_interest"?"opacity-40":""}`,children:[e("input",{type:"checkbox",checked:n.liEnabled,disabled:n.type!=="legitimate_interest",onChange:i=>t({liEnabled:i.target.checked})}),"LI active"]})]})]})})}const Ts=["left","right","center"];function Is({value:n,onChange:t,cookieOptions:o,onCategoriesChange:r}){const s=(l,p)=>t({...n,[l]:p}),i=(l,p)=>{const d=n.categories.map((u,h)=>h===l?{...u,...p}:u);t({...n,categories:d})},a=()=>{const l=[...n.categories,{id:"",headingTag:"h3",mandatory:!1,type:"consent",liEnabled:!1,cookies:[]}];t({...n,categories:l}),r==null||r(l)},c=l=>{const p=n.categories.filter((d,u)=>u!==l);t({...n,categories:p}),r==null||r(p)};return e("div",{class:"space-y-5",children:[e("div",{class:"grid grid-cols-2 lg:grid-cols-3 gap-4",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:["Position ",e("span",{class:"text-red-500",children:"*"})]}),e("select",{class:"w-full border border-gray-300 rounded px-2 py-1.5 text-sm",value:n.position,onChange:l=>s("position",l.target.value),children:Ts.map(l=>e("option",{value:l,children:l},l))})]}),e(kn,{value:n.headingTag,onChange:l=>s("headingTag",l)}),e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-1",children:"Overlay opacity"}),e(to,{value:n.overlayOpacity,onChange:l=>s("overlayOpacity",l)}),e("p",{class:"text-xs text-gray-400 mt-0.5",children:"0% = transparent (page still interactive) · 100% = darkened"})]}),[["showClose",e("span",{class:"flex items-center gap-1",children:["Show ",e(Ye,{size:10})," close button"]})],["showLocaleSwitcher","🌐 Show locale switcher"],["persistent","Persistent (block outside-click dismiss)"],["hasSubheading","Include subheading field"]].map(([l,p])=>e("label",{class:"flex items-center gap-2 text-xs font-medium text-gray-600 cursor-pointer",children:[e("input",{type:"checkbox",class:"w-4 h-4",checked:n[l],onChange:d=>s(l,d.target.checked)}),p]},l))]}),e("div",{children:[e("h4",{class:"text-xs font-semibold text-gray-600 mb-2 uppercase tracking-wide",children:"Buttons"}),e(ro,{buttons:n.buttons,onChange:l=>s("buttons",l),cookieOptions:o})]}),e("div",{children:[e("div",{class:"flex items-center justify-between mb-2",children:e("h4",{class:"text-xs font-semibold text-gray-600 uppercase tracking-wide",children:"Consent Categories"})}),e("p",{class:"text-xs text-gray-400 mb-3",children:"Each category groups cookies shown as a toggle in the preference modal. Category ID must be unique."}),e("div",{class:"space-y-3",children:n.categories.map((l,p)=>e(Ss,{cat:l,onChange:d=>i(p,d),onRemove:()=>c(p),cookieOptions:o},p))}),e("button",{type:"button",onClick:a,class:"text-xs text-blue-600 hover:underline mt-3 block",children:"+ Add Category"})]})]})}function ut({id:n,current:t}){const o=!n||n==="new",r=ps(),[s,i]=g(""),[a,c]=g(""),[l,p]=g("main"),[d,u]=g(r.mainBanner),[h,m]=g(r.gpcBanner),[k,y]=g(r.preferenceModal),[b,v]=g(""),[w,N]=g(!1),[I,x]=g(!o);j(()=>{!o&&n&&re.get(n).then(_=>{i(_.name),u(_.mainBanner),m(_.gpcBanner),y(_.preferenceModal)}).catch(()=>v("Template not found.")).finally(()=>x(!1))},[n,o]);const $=gs(no(),{mainBanner:d,gpcBanner:h,preferenceModal:k},"en",{en:us(k.categories,{mainBanner:d,gpcBanner:h,preferenceModal:k})},{}),D=async()=>{let _=!0;s.trim()?c(""):(c("Template name is required."),_=!1);const R=[...d.buttons,...h.buttons,...k.buttons];if(R.some(C=>!C.text.trim())){v("All buttons must have a label.");return}if(R.some(C=>C.action==="custom"&&!C.cookies)){v('All "custom" action buttons must have cookies selected.');return}const U=k.categories,L=U.map(C=>C.id.trim());if(U.some(C=>!C.id.trim())){v("All category IDs are required.");return}if(new Set(L).size!==L.length){v("Category IDs must be unique.");return}if(U.some(C=>!C.cookies.length)){v("Each category must include at least one cookie.");return}if(_){if(!o&&n){const O=(await ae.list().catch(()=>[])).filter(B=>{var H;const S=B.profileJson;return S.uiTemplateId===n||((H=S._meta)==null?void 0:H.uiTemplateId)===n});if(O.length>0){const B=O.map(H=>`"${H.name}"`).join(", ");if(!window.confirm(`This template is used by ${O.length} profile${O.length!==1?"s":""}: ${B}.
|
|
2636
|
-
|
|
2637
|
-
Saving will apply UI changes to all of them. Continue?`))return}}v(""),N(!0);try{o?await re.create({name:s,mainBanner:d,gpcBanner:h,preferenceModal:k}):await re.update(n,{name:s,mainBanner:d,gpcBanner:h,preferenceModal:k}),window.location.hash="#/banners/ui-templates"}catch{v("Failed to save template.")}finally{N(!1)}}},T=[{key:"main",label:"Main Banner"},{key:"gpc",label:"GPC Banner"},{key:"modal",label:"Preference Modal"}];return I?e(F,{title:"Edit UI Template",current:t,children:e("p",{class:"text-sm text-gray-400",children:"Loading template…"})}):e(F,{title:o?"New UI Template":"Edit UI Template",current:t,children:e("div",{class:"space-y-5",children:[e("div",{class:"bg-white border border-gray-200 rounded-lg p-5",children:[e("label",{class:"block text-sm font-medium text-gray-700 mb-1",children:["Template Name ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:`w-full border rounded px-3 py-2 text-sm ${a?"border-red-400 bg-red-50":"border-gray-300"}`,value:s,onInput:_=>{i(_.target.value),c("")},placeholder:"e.g. Standard GDPR Layout"}),a&&e("p",{class:"text-xs text-red-500 mt-1",children:a})]}),e("div",{class:"bg-white border border-gray-200 rounded-lg overflow-hidden",children:[e("div",{class:"flex border-b border-gray-200",children:T.map(_=>e("button",{type:"button",onClick:()=>p(_.key),class:`px-6 py-3 text-sm font-medium transition-colors ${l===_.key?"border-b-2 border-blue-500 text-blue-600 -mb-px":"text-gray-500 hover:text-gray-700"}`,children:_.label},_.key))}),e("div",{class:"p-5",children:[l==="main"&&e(pt,{label:"Shown on first visit when no consent has been recorded.",value:d,onChange:u}),l==="gpc"&&e(pt,{label:"Shown instead of the main banner when a GPC signal is detected (autoHonorGPC enabled).",value:h,onChange:m}),l==="modal"&&e(Is,{value:k,onChange:y})]})]}),b&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2",children:b}),e("div",{class:"flex gap-3",children:[e("button",{type:"button",onClick:D,disabled:w,class:"bg-blue-600 text-white px-5 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors",children:w?"Saving…":o?"Create Template":"Save Template"}),e("a",{href:"#/banners/ui-templates",class:"px-5 py-2 rounded-lg text-sm text-gray-600 hover:bg-gray-100 transition-colors",children:"Cancel"})]}),e(gn,{draft:$,expandable:!0,previewMode:l})]})})}function wn({page:n,hasMore:t,onPage:o}){return e("div",{class:"flex items-center gap-2 mt-4",children:[e("button",{onClick:()=>o(n-1),disabled:n<=1,class:"px-3 py-1.5 text-sm rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-50 transition-colors flex items-center gap-1",children:[e(qe,{size:14})," Prev"]}),e("span",{class:"text-sm text-gray-600",children:["Page ",n]}),e("button",{onClick:()=>o(n+1),disabled:!t,class:"px-3 py-1.5 text-sm rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-50 transition-colors flex items-center gap-1",children:["Next ",e(Fe,{size:14})]})]})}const Ms={list:n=>{const t=new URLSearchParams;return n!=null&&n.profileId&&t.set("profileId",n.profileId),n!=null&&n.page&&t.set("page",String(n.page)),n!=null&&n.from&&t.set("from",n.from),n!=null&&n.to&&t.set("to",n.to),P(`/consents?${t}`)},get:n=>P(`/consents/${n}`),history:n=>P(`/consents/${n}/history`)};function Ps({current:n}){const[t,o]=g([]),[r,s]=g(1),[i,a]=g(!0),[c,l]=g(""),[p,d]=g(""),[u,h]=g(!1),m=y=>{a(!0),Ms.list({page:y,from:c||void 0,to:p||void 0}).then(b=>{const v=u?b.filter(w=>w.gpcDetected):b;o(v)}).catch(()=>{}).finally(()=>a(!1))};j(()=>{m(r)},[r,c,p,u]);const k=()=>{const y=new URLSearchParams({format:"csv"});return c&&y.set("from",c),p&&y.set("to",p),`/export/consents?${y.toString()}`};return e(F,{title:"Consent Records",current:n,children:[e("div",{class:"flex flex-wrap gap-3 items-center mb-4",children:[e("label",{class:"flex items-center gap-1 text-sm text-gray-600",children:["From",e("input",{type:"date",value:c,onInput:y=>{l(y.target.value),s(1)},class:"ml-1 border border-gray-300 rounded px-2 py-1 text-xs"})]}),e("label",{class:"flex items-center gap-1 text-sm text-gray-600",children:["To",e("input",{type:"date",value:p,onInput:y=>{d(y.target.value),s(1)},class:"ml-1 border border-gray-300 rounded px-2 py-1 text-xs"})]}),e("label",{class:"flex items-center gap-1 text-sm text-gray-600",children:[e("input",{type:"checkbox",checked:u,onChange:y=>{h(y.target.checked),s(1)}}),"GPC only"]}),e("div",{class:"ml-auto flex items-center gap-3",children:[e("span",{class:"text-sm text-gray-500",children:[t.length," records"]}),e("button",{onClick:()=>fe(k()),class:"text-sm text-blue-600 hover:underline",children:"Export CSV"})]})]}),e(ke,{loading:i,keyFn:y=>y.id,rows:t,columns:[{key:"visitorId",label:"Visitor ID",render:y=>e("span",{class:"font-mono text-xs",children:[y.visitorId.slice(0,12),"…"]})},{key:"profileId",label:"Profile",render:y=>e("span",{class:"font-mono text-xs",children:y.profileId.slice(0,8)})},{key:"locale",label:"Locale"},{key:"gpcDetected",label:"GPC",render:y=>y.gpcDetected?e("span",{class:"text-amber-600 font-medium text-xs",children:"Yes"}):""},{key:"ageVerified",label:"Age",render:y=>y.ageVerified?e("span",{class:"text-green-600 font-medium text-xs",children:"Verified"}):""},{key:"source",label:"Source"},{key:"createdAt",label:"Created",render:y=>new Date(y.createdAt).toLocaleString()}],emptyText:"No consent records yet."}),e(wn,{page:r,hasMore:t.length===50,onPage:s})]})}const Es={list:n=>{const t=new URLSearchParams;return n!=null&&n.page&&t.set("page",String(n.page)),P(`/visitors?${t}`)}};function Ls({current:n}){const[t,o]=g([]),[r,s]=g(1),[i,a]=g(!0),c=l=>{a(!0),Es.list({page:l}).then(o).catch(()=>{}).finally(()=>a(!1))};return j(()=>{c(r)},[r]),e(F,{title:"Visitors",current:n,children:[e(ke,{loading:i,keyFn:l=>l.id,rows:t,columns:[{key:"visitorId",label:"Visitor ID",render:l=>e("span",{class:"font-mono text-xs",children:[l.visitorId.slice(0,16),"…"]})},{key:"country",label:"Country",render:l=>l.country??"—"},{key:"firstSeen",label:"First Seen",render:l=>new Date(l.firstSeen).toLocaleDateString()},{key:"lastSeen",label:"Last Seen",render:l=>new Date(l.lastSeen).toLocaleDateString()}],emptyText:"No visitors recorded yet."}),e(wn,{page:r,hasMore:t.length===50,onPage:s})]})}const Re={list:()=>P("/users"),get:n=>P(`/users/${n}`),create:n=>P("/users",{method:"POST",body:JSON.stringify(n)}),update:(n,t)=>P(`/users/${n}`,{method:"PUT",body:JSON.stringify(t)}),delete:n=>P(`/users/${n}`,{method:"DELETE"}),assignRole:(n,t)=>P(`/users/${n}/roles`,{method:"POST",body:JSON.stringify({roleId:t})}),revokeRole:(n,t)=>P(`/users/${n}/roles/${t}`,{method:"DELETE"})},ee={list:()=>P("/roles"),permissions:n=>P(`/roles/${n}/permissions`),allPermissions:()=>P("/permissions"),create:n=>P("/roles",{method:"POST",body:JSON.stringify(n)}),update:(n,t)=>P(`/roles/${n}`,{method:"PUT",body:JSON.stringify(t)}),delete:n=>P(`/roles/${n}`,{method:"DELETE"}),assignPermission:(n,t)=>P(`/roles/${n}/permissions`,{method:"POST",body:JSON.stringify({permissionId:t})}),revokePermission:(n,t)=>P(`/roles/${n}/permissions/${t}`,{method:"DELETE"})};function As({current:n}){const{user:t}=pe(),[o,r]=g([]),[s,i]=g([]),[a,c]=g(!0),[l,p]=g(!1),[d,u]=g({name:"",email:"",password:"",confirmPassword:"",roleId:""}),[h,m]=g(!1),[k,y]=g(!1),[b,v]=g(""),{requestConfirm:w,dialog:N}=ue(),I=()=>{c(!0),Promise.all([Re.list(),ee.list()]).then(([T,_])=>{r(T),i(_)}).catch(()=>{}).finally(()=>c(!1))};j(I,[]);const x=async T=>{if(T.preventDefault(),d.password!==d.confirmPassword){v("Passwords do not match");return}v("");try{await Re.create({name:d.name,email:d.email,password:d.password,roleId:d.roleId||void 0}),p(!1),u({name:"",email:"",password:"",confirmPassword:"",roleId:""}),m(!1),y(!1),I()}catch{v("Failed to create user")}},$=async(T,_)=>{await w({title:`Delete user "${_}"?`,message:"This user account will be permanently removed."})&&(await Re.delete(T).catch(()=>{}),I())},D=async T=>{await Re.update(T.id,{isActive:!T.isActive}).catch(()=>{}),I()};return e(F,{title:"Users",current:n,children:[N,e("div",{class:"flex justify-between items-center mb-4",children:[e("p",{class:"text-sm text-gray-500",children:[o.length," users"]}),e(be,{perm:"user:create",children:e("button",{onClick:()=>p(!l),class:"bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors",children:l?"Cancel":"+ New User"})})]}),l&&e("form",{onSubmit:x,class:"bg-white border border-gray-200 rounded-lg p-5 mb-4 space-y-3",children:[e("h3",{class:"text-sm font-semibold text-gray-700",children:"Create User"}),e("div",{class:"grid grid-cols-2 gap-3",children:[e("input",{class:"border border-gray-300 rounded px-3 py-2 text-sm",placeholder:"Name",required:!0,value:d.name,onInput:T=>u(_=>({..._,name:T.target.value}))}),e("input",{class:"border border-gray-300 rounded px-3 py-2 text-sm",placeholder:"Email",type:"email",required:!0,value:d.email,onInput:T=>u(_=>({..._,email:T.target.value}))}),e("div",{class:"relative",children:[e("input",{class:"w-full border border-gray-300 rounded px-3 py-2 pr-9 text-sm",placeholder:"Password",type:h?"text":"password",required:!0,value:d.password,onInput:T=>u(_=>({..._,password:T.target.value}))}),e("button",{type:"button",class:"absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600",onClick:()=>m(T=>!T),title:h?"Hide password":"Show password",children:h?e(hn,{size:15}):e(pn,{size:15})})]}),e("div",{class:"relative",children:[e("input",{class:`w-full border rounded px-3 py-2 pr-9 text-sm ${d.confirmPassword&&d.password!==d.confirmPassword?"border-red-300 bg-red-50":"border-gray-300"}`,placeholder:"Confirm password",type:k?"text":"password",required:!0,value:d.confirmPassword,onInput:T=>u(_=>({..._,confirmPassword:T.target.value}))}),e("button",{type:"button",class:"absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600",onClick:()=>y(T=>!T),title:k?"Hide password":"Show password",children:k?e(hn,{size:15}):e(pn,{size:15})})]}),e("select",{class:"border border-gray-300 rounded px-3 py-2 text-sm col-span-2",value:d.roleId,onChange:T=>u(_=>({..._,roleId:T.target.value})),children:[e("option",{value:"",children:"Select role…"}),s.map(T=>e("option",{value:T.id,children:T.name},T.id))]})]}),d.confirmPassword&&d.password!==d.confirmPassword&&e("p",{class:"text-xs text-red-600",children:"Passwords do not match"}),b&&e("p",{class:"text-sm text-red-600",children:b}),e("button",{type:"submit",class:"bg-blue-600 text-white px-4 py-2 rounded text-sm",children:"Create"})]}),e(ke,{loading:a,keyFn:T=>T.id,rows:o,columns:[{key:"name",label:"Name"},{key:"email",label:"Email"},{key:"isActive",label:"Status",render:T=>{const _=T;return e("span",{class:`text-xs font-medium px-2 py-0.5 rounded-full ${_.isActive?"bg-green-100 text-green-700":"bg-gray-100 text-gray-500"}`,children:_.isActive?"Active":"Inactive"})}},{key:"createdAt",label:"Created",render:T=>new Date(T.createdAt).toLocaleDateString()},{key:"actions",label:"",render:T=>{const _=T;return _.id===(t==null?void 0:t.sub)?e("span",{class:"text-xs text-gray-400",children:"You"}):e("div",{class:"flex gap-2",children:[e(be,{perm:"user:update",children:e("button",{onClick:()=>D(_),class:"text-xs text-blue-600 hover:underline",children:_.isActive?"Deactivate":"Activate"})}),e(be,{perm:"user:delete",children:e("button",{onClick:()=>$(_.id,_.name),class:"text-xs text-red-500 hover:underline",children:"Delete"})})]})}}]})]})}function $s(n){const t={};for(const o of n){const r=o.name.includes(":")?o.name.split(":")[0]:"other";(t[r]??(t[r]=[])).push(o)}return Object.entries(t).sort(([o],[r])=>o.localeCompare(r)).map(([o,r])=>({group:o,items:r}))}function gt({initial:n,onSave:t,onCancel:o,saving:r}){const[s,i]=g((n==null?void 0:n.name)??""),[a,c]=g((n==null?void 0:n.description)??""),l=!s.trim();return e("form",{onSubmit:d=>{d.preventDefault(),!l&&t(s.trim(),a.trim())},class:"border border-blue-200 bg-blue-50 rounded-lg p-3 space-y-2",children:[e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-0.5",children:["Role name ",e("span",{class:"text-red-500",children:"*"})]}),e("input",{class:`w-full border rounded px-2 py-1.5 text-sm ${l?"border-red-300 bg-red-50":"border-gray-300 bg-white"}`,value:s,onInput:d=>i(d.target.value),placeholder:"e.g. Editor",autoFocus:!0}),l&&e("p",{class:"text-xs text-red-500 mt-0.5",children:"Required"})]}),e("div",{children:[e("label",{class:"block text-xs font-medium text-gray-600 mb-0.5",children:"Description"}),e("input",{class:"w-full border border-gray-300 bg-white rounded px-2 py-1.5 text-sm",value:a,onInput:d=>c(d.target.value),placeholder:"Optional description"})]}),e("div",{class:"flex gap-2 pt-1",children:[e("button",{type:"submit",disabled:r||l,class:"flex items-center gap-1.5 px-3 py-1.5 bg-blue-600 text-white text-xs font-medium rounded hover:bg-blue-700 disabled:opacity-50 transition-colors",children:[e(_e,{size:12})," ",r?"Saving…":"Save"]}),e("button",{type:"button",onClick:o,class:"flex items-center gap-1.5 px-3 py-1.5 text-xs text-gray-600 hover:bg-gray-100 rounded transition-colors",children:[e(Ye,{size:12})," Cancel"]})]})]})}function Bs({current:n}){const[t,o]=g([]),[r,s]=g([]),[i,a]=g(null),[c,l]=g([]),[p,d]=g(!0),[u,h]=g(!1),[m,k]=g(null),[y,b]=g(!1),[v,w]=g(""),{requestConfirm:N,dialog:I}=ue(),x=()=>{d(!0),Promise.all([ee.list(),ee.allPermissions()]).then(([L,C])=>{o(L),s(C)}).catch(()=>w("Failed to load roles.")).finally(()=>d(!1))};j(x,[]);const $=L=>{m!==L.id&&(a(L),ee.permissions(L.id).then(l).catch(()=>{}))},D=async L=>{if(!i)return;const C=c.some(O=>O.id===L.id);try{C?(await ee.revokePermission(i.id,L.id),l(O=>O.filter(B=>B.id!==L.id))):(await ee.assignPermission(i.id,L.id),l(O=>[...O,L]))}catch{w("Failed to update permission.")}},T=async(L,C)=>{b(!0),w("");try{const O=await ee.create({name:L,description:C||void 0});h(!1),x(),a(O),ee.permissions(O.id).then(l).catch(()=>{})}catch{w("Failed to create role.")}finally{b(!1)}},_=async(L,C,O)=>{b(!0),w("");try{await ee.update(L,{name:C,description:O||void 0}),k(null),x(),(i==null?void 0:i.id)===L&&a(B=>B&&{...B,name:C,description:O})}catch{w("Failed to update role.")}finally{b(!1)}},R=async L=>{if(await N({title:`Delete role "${L.name}"?`,message:"Users assigned this role will lose its permissions immediately."}))try{await ee.delete(L.id),(i==null?void 0:i.id)===L.id&&(a(null),l([])),x()}catch{w("Failed to delete role.")}},U=$s(r);return e(F,{title:"Roles & Permissions",current:n,children:[I,v&&e("p",{class:"text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2 mb-4",children:v}),p?e("p",{class:"text-gray-400 text-sm",children:"Loading…"}):e("div",{class:"grid grid-cols-1 md:grid-cols-[280px_1fr] gap-6 items-start",children:[e("div",{children:[e("div",{class:"flex items-center justify-between mb-2",children:[e("h2",{class:"text-sm font-semibold text-gray-700",children:"Roles"}),e("button",{onClick:()=>{h(!0),k(null)},class:"flex items-center gap-1 text-xs text-blue-600 hover:underline",children:[e(Xr,{size:12})," New Role"]})]}),e("div",{class:"space-y-2",children:[u&&e(gt,{onSave:T,onCancel:()=>h(!1),saving:y}),t.map(L=>{const C=(i==null?void 0:i.id)===L.id;return m===L.id?e(gt,{initial:{name:L.name,description:L.description??""},onSave:(B,S)=>_(L.id,B,S),onCancel:()=>k(null),saving:y},L.id):e("div",{class:`group flex items-start gap-2 px-3 py-2.5 rounded-lg border text-sm transition-colors cursor-pointer ${C?"border-blue-500 bg-blue-50":"border-gray-200 bg-white hover:border-gray-300"}`,onClick:()=>$(L),children:[e("div",{class:"flex-1 min-w-0",children:[e("p",{class:`font-medium ${C?"text-blue-700":"text-gray-700"}`,children:L.name}),L.description&&e("p",{class:"text-xs text-gray-400 mt-0.5 truncate",children:L.description})]}),e("div",{class:"flex gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity mt-0.5",children:[e("button",{type:"button",onClick:B=>{B.stopPropagation(),k(L.id),h(!1)},class:"p-1 text-gray-400 hover:text-blue-600 rounded transition-colors",title:"Edit role",children:e(Wr,{size:13})}),e("button",{type:"button",onClick:B=>{B.stopPropagation(),R(L)},class:"p-1 text-gray-400 hover:text-red-600 rounded transition-colors",title:"Delete role",children:e(Zt,{size:13})})]})]},L.id)}),t.length===0&&!u&&e("p",{class:"text-sm text-gray-400 text-center py-6",children:"No roles yet. Create one to get started."})]})]}),e("div",{children:[e("h2",{class:"text-sm font-semibold text-gray-700 mb-2",children:i?`Permissions — ${i.name}`:"Select a role to manage permissions"}),i?e("div",{class:"space-y-4",children:[U.map(({group:L,items:C})=>e("div",{class:"bg-white border border-gray-200 rounded-lg overflow-hidden",children:[e("div",{class:"px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between",children:[e("span",{class:"text-xs font-semibold text-gray-600 uppercase tracking-wide",children:L}),e("div",{class:"flex gap-2",children:e("button",{type:"button",class:"text-xs text-blue-600 hover:underline",onClick:()=>{(C.every(S=>c.some(H=>H.id===S.id))?C.filter(S=>c.some(H=>H.id===S.id)):C.filter(S=>!c.some(H=>H.id===S.id))).forEach(S=>D(S))},children:C.every(O=>c.some(B=>B.id===O.id))?"Revoke all":"Grant all"})})]}),e("div",{class:"divide-y divide-gray-50",children:C.map(O=>{const B=c.some(S=>S.id===O.id);return e("label",{class:"flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",children:[e("input",{type:"checkbox",checked:B,onChange:()=>D(O),class:"w-4 h-4 accent-blue-600 rounded shrink-0"}),e("div",{class:"min-w-0",children:[e("p",{class:"text-sm font-medium text-gray-700 font-mono",children:O.name}),O.description&&e("p",{class:"text-xs text-gray-400 mt-0.5",children:O.description})]}),B&&e("span",{class:"ml-auto shrink-0 text-xs text-green-600 bg-green-50 px-1.5 py-0.5 rounded-full",children:"granted"})]},O.id)})})]},L)),r.length===0&&e("p",{class:"text-sm text-gray-400",children:"No permissions defined in the system."})]}):e("div",{class:"bg-white border border-gray-200 rounded-lg p-8 text-center",children:e("p",{class:"text-sm text-gray-400",children:"Click a role on the left to view and manage its permissions."})})]})]})]})}function Ns({current:n}){const[t,o]=g([]),[r,s]=g(!0),[i,a]=g(!1),[c,l]=g(""),[p,d]=g(""),{requestConfirm:u,dialog:h}=ue(),m=()=>{s(!0),P("/tenants").then(o).catch(()=>{}).finally(()=>s(!1))};j(()=>{m()},[]);const k=async()=>{if(!(!c.trim()||!p.trim())){a(!0);try{await P("/tenants",{method:"POST",body:JSON.stringify({name:c.trim(),slug:p.trim()})}),l(""),d(""),m()}catch{}finally{a(!1)}}},y=async(b,v)=>{await u({title:`Delete "${v}"?`,message:"This site and all associated data will be permanently removed. This cannot be undone."})&&(await P(`/tenants/${b}`,{method:"DELETE"}).catch(()=>{}),m())};return e(F,{title:"Sites / Tenants",current:n,children:[h,e("div",{class:"mb-4 bg-white rounded-lg border border-gray-200 p-4",children:[e("p",{class:"text-sm font-medium text-gray-700 mb-3",children:"Create new site"}),e("div",{class:"flex gap-2 flex-wrap",children:[e("input",{type:"text",placeholder:"Site name",value:c,onInput:b=>l(b.target.value),class:"border border-gray-300 rounded px-3 py-1.5 text-sm w-40"}),e("input",{type:"text",placeholder:"slug (e.g. site-a)",value:p,onInput:b=>d(b.target.value),class:"border border-gray-300 rounded px-3 py-1.5 text-sm w-40"}),e("button",{onClick:k,disabled:i,class:"bg-blue-600 text-white text-sm px-3 py-1.5 rounded hover:bg-blue-700 disabled:opacity-50",children:"Create"})]})]}),e(ke,{loading:r,keyFn:b=>b.id,rows:t,columns:[{key:"name",label:"Name"},{key:"slug",label:"Slug",render:b=>e("span",{class:"font-mono text-xs",children:b.slug})},{key:"id",label:"ID",render:b=>e("span",{class:"font-mono text-xs text-gray-400",children:b.id})},{key:"createdAt",label:"Created",render:b=>new Date(b.createdAt).toLocaleDateString()},{key:"actions",label:"",render:b=>{const v=b;return v.id==="default"?e("span",{class:"text-xs text-gray-400",children:"default"}):e("button",{onClick:()=>y(v.id,v.name),class:"text-xs text-red-500 hover:underline",children:"Delete"})}}],emptyText:"No sites yet."})]})}function Ds(){const n=window.location.hash.match(/[?&]page=(\d+)/);return n?Math.max(1,parseInt(n[1],10)):1}function mt(n){if(!Number.isFinite(n)||n<1)return;const o=(window.location.hash.split("?")[0]??"/vendors").replace(/^#/,"");window.location.hash=n===1?o:`${o}?page=${n}`}function Rs({current:n}){const[t,o]=g([]),[r,s]=g(0),[i,a]=g(0),[c,l]=g(Ds),[p,d]=g(1),[u,h]=g(""),[m,k]=g(!0),[y,b]=g(""),v=(I,x)=>{k(!0),b("");const $=new URLSearchParams;I&&$.set("q",I),$.set("page",String(x)),$.set("limit",String(25)),P(`/tcf/vendors?${$.toString()}`).then(D=>{o(D.vendors),s(D.total),a(D.vendorListVersion),d(D.totalPages),l(Number.isFinite(D.page)&&D.page>=1?D.page:1)}).catch(D=>{b(D instanceof Error?D.message:"GVL not available. Enable tcf.enabled in server config.")}).finally(()=>k(!1))};j(()=>{v(u,c)},[]);const w=I=>{h(I),l(1),mt(1),v(I,1)},N=I=>{l(I),mt(I),v(u,I)};return e(F,{title:"TCF Vendor List",current:n,children:[e("div",{class:"mb-5 p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-900 space-y-2",children:[e("p",{class:"font-semibold",children:"What is the TCF Vendor List?"}),e("p",{children:["The ",e("strong",{children:"IAB Transparency & Consent Framework (TCF)"})," is an industry standard used by ad-tech platforms across Europe. The ",e("strong",{children:"Global Vendor List (GVL)"}),' is a public registry — maintained by the IAB — of every registered ad-tech vendor (Google, Meta, etc.) and the "purposes" they require consent for (e.g. Purpose 1 = store/access info, Purpose 3 = create personalised ads).']}),e("p",{children:["When TCF is enabled on a profile, Consenti generates a ",e("strong",{children:"TC String"})," alongside the normal consent record. Ad platforms read this string from the cookie to know which vendors and purposes the visitor accepted. Without a valid TC String, compliant ad platforms will not fire their tags."]}),e("p",{children:["This list is ",e("strong",{children:"read-only reference data"})," — it is fetched automatically from the IAB and refreshed every 7 days. You do not need to sync or edit it manually. Use it to look up a vendor's numeric ID and the purpose IDs they need, then attach those IDs to the relevant cookie in your profile config."]}),e("p",{class:"text-xs text-blue-700",children:"TCF is only relevant if your site runs IAB-registered ad-tech vendors and you need to pass a TC String to their tags. If you only need GDPR / CCPA consent for your own first-party cookies, you can ignore this page."})]}),e("div",{class:"mb-4 flex items-center gap-3",children:[e("input",{type:"text",placeholder:"Search vendors…",value:u,onInput:I=>w(I.target.value),class:"border border-gray-300 rounded px-3 py-1.5 text-sm w-64"}),i>0&&e("span",{class:"text-xs text-gray-500",children:["GVL v",i," · ",r," vendors"]})]}),y&&e("div",{class:"p-4 bg-amber-50 border border-amber-200 rounded text-sm text-amber-800",children:y}),m&&e("p",{class:"text-sm text-gray-500",children:"Loading vendor list…"}),!m&&!y&&t.length===0&&e("p",{class:"text-sm text-gray-500",children:"No vendors found."}),!m&&t.length>0&&e(Z,{children:[e("div",{class:"max-h-[60vh] overflow-y-auto rounded border border-gray-200",children:e("table",{class:"w-full text-xs",children:[e("thead",{class:"bg-gray-50",children:e("tr",{children:[e("th",{class:"text-left px-3 py-2 text-gray-600",children:"ID"}),e("th",{class:"text-left px-3 py-2 text-gray-600",children:"Name"}),e("th",{class:"text-left px-3 py-2 text-gray-600",children:"Purposes"}),e("th",{class:"text-left px-3 py-2 text-gray-600",children:"LI Purposes"}),e("th",{class:"text-left px-3 py-2 text-gray-600",children:"Special Purposes"})]})}),e("tbody",{children:t.map(I=>e("tr",{class:"border-t border-gray-100 hover:bg-gray-50",children:[e("td",{class:"px-3 py-2 font-mono text-gray-500",children:I.id}),e("td",{class:"px-3 py-2 font-medium text-gray-800",children:I.name}),e("td",{class:"px-3 py-2 text-gray-600",children:(I.purposes??[]).join(", ")||"—"}),e("td",{class:"px-3 py-2 text-gray-600",children:(I.legIntPurposes??[]).join(", ")||"—"}),e("td",{class:"px-3 py-2 text-gray-600",children:(I.specialPurposes??[]).join(", ")||"—"})]},I.id))})]})}),e("div",{class:"mt-3 flex items-center justify-between text-xs text-gray-500",children:[e("span",{children:["Page ",c," of ",p," · ",r," vendors total"]}),e("div",{class:"flex items-center gap-1",children:[e("button",{onClick:()=>N(c-1),disabled:c<=1,class:"px-2 py-1 border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed",children:"← Prev"}),Array.from({length:Math.min(5,p)},(I,x)=>{const D=Math.max(1,Math.min(c-2,p-4))+x;return e("button",{onClick:()=>N(D),class:`px-2 py-1 border rounded ${D===c?"border-blue-500 bg-blue-50 text-blue-700 font-medium":"border-gray-300 hover:bg-gray-50"}`,children:D},D)}),e("button",{onClick:()=>N(c+1),disabled:c>=p,class:"px-2 py-1 border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed",children:"Next →"})]})]})]})]})}const Os={list:n=>{const t=new URLSearchParams;return n!=null&&n.action&&t.set("action",n.action),n!=null&&n.page&&t.set("page",String(n.page)),P(`/audit?${t}`)}};function zs({current:n}){const[t,o]=g([]),[r,s]=g(1),[i,a]=g(!0),c=l=>{a(!0),Os.list({page:l}).then(o).catch(()=>{}).finally(()=>a(!1))};return j(()=>{c(r)},[r]),e(F,{title:"Audit Log",current:n,children:[e("div",{class:"flex justify-end mb-4",children:e("button",{onClick:()=>fe("/export/audit?format=csv"),class:"text-sm text-blue-600 hover:underline",children:"Export CSV"})}),e(ke,{loading:i,keyFn:l=>l.id,rows:t,columns:[{key:"createdAt",label:"Time",render:l=>new Date(l.createdAt).toLocaleString()},{key:"action",label:"Action"},{key:"resourceType",label:"Resource"},{key:"resourceId",label:"Resource ID",render:l=>{const p=l.resourceId;return p?e("span",{class:"font-mono text-xs",children:[p.slice(0,12),"…"]}):"—"}},{key:"userId",label:"Actor",render:l=>{const p=l.userId;return p?e("span",{class:"font-mono text-xs",children:p.slice(0,8)}):e("span",{class:"text-gray-400",children:"system"})}}],emptyText:"No audit events yet."}),e(wn,{page:r,hasMore:t.length===50,onPage:s})]})}function Us({current:n}){const{user:t}=pe(),[o]=g(!0);return j(()=>{},[]),e(F,{title:"Settings",current:n,children:e("div",{class:"max-w-2xl space-y-4",children:[e("div",{class:"bg-white rounded-lg border border-gray-200 p-5",children:[e("h2",{class:"text-sm font-semibold text-gray-700 mb-3",children:"Account"}),e("div",{class:"space-y-2 text-sm text-gray-600",children:[e("p",{children:[e("span",{class:"font-medium",children:"Email:"})," ",t==null?void 0:t.email]}),e("p",{children:[e("span",{class:"font-medium",children:"Roles:"})," ",t==null?void 0:t.roles.join(", ")]}),e("p",{children:[e("span",{class:"font-medium",children:"ID:"})," ",e("span",{class:"font-mono",children:t==null?void 0:t.sub})]})]})]}),e("div",{class:"bg-white rounded-lg border border-gray-200 p-5",children:[e("h2",{class:"text-sm font-semibold text-gray-700 mb-2",children:"Export Data"}),e("p",{class:"text-xs text-gray-500 mb-3",children:"Download all consent records or audit logs."}),e("div",{class:"flex flex-wrap gap-3",children:[e("button",{onClick:()=>fe("/export/consents?format=csv"),class:"text-sm text-blue-600 hover:underline",children:"Consent CSV"}),e("button",{onClick:()=>fe("/export/consents/xlsx"),class:"text-sm text-blue-600 hover:underline",children:"Consent XLSX"}),e("button",{onClick:()=>fe("/export/consents?format=json"),class:"text-sm text-blue-600 hover:underline",children:"Consent JSON"}),e("button",{onClick:()=>fe("/export/audit?format=csv"),class:"text-sm text-blue-600 hover:underline",children:"Audit CSV"})]})]}),e("div",{class:"bg-white rounded-lg border border-gray-200 p-5",children:[e("h2",{class:"text-sm font-semibold text-gray-700 mb-2",children:"About"}),e("p",{class:"text-xs text-gray-500",children:["Consenti v0.1.0 — Open-source GDPR-compliant Consent Management Platform.",e("br",{}),"Apache 2.0 License."]})]})]})})}function js({current:n}){const[t,o]=g([]),[r,s]=g(""),[i,a]=g(!1),[c,l]=g(""),[p,d]=g([]),[u,h]=g(""),[m,k]=g(null),[y,b]=g(!1),[v,w]=g(""),[N,I]=g(!1),[x,$]=g(!0),{requestConfirm:D,dialog:T}=ue();j(()=>{P("/settings").then(S=>o(S.allowedOrigins??[])).catch(()=>{}),P("/apikeys").then(d).catch(()=>{}).finally(()=>$(!1))},[]);const _=async S=>{a(!0),l("");try{await P("/settings",{method:"PATCH",body:JSON.stringify({allowedOrigins:S})}),o(S)}catch{l("Failed to save. Please try again.")}finally{a(!1)}},R=async()=>{const S=r.trim().replace(/\/$/,"");if(S){if(t.includes(S)){l("Already in the list.");return}s(""),await _([...t,S])}},U=async S=>{await _(t.filter(H=>H!==S))},L=()=>{P("/apikeys").then(d).catch(()=>{})},C=async()=>{if(!u.trim()){w("Name is required");return}w("");try{const S=await P("/apikeys",{method:"POST",body:JSON.stringify({name:u.trim()})});k(S),I(!1),b(!1),h(""),d(H=>[...H,{id:S.id,name:S.name,isActive:!0,createdAt:S.createdAt}])}catch(S){w(S instanceof Error?S.message:"Failed to create token")}},O=async S=>{if(await D({title:"Revoke this API token?",message:"Any application using this token will immediately lose access.",danger:!0}))try{await P(`/apikeys/${S}`,{method:"DELETE"}),d(X=>X.map(ge=>ge.id===S?{...ge,isActive:!1}:ge)),L()}catch{}},B=()=>{m&&navigator.clipboard.writeText(m.key).then(()=>{b(!0),setTimeout(()=>b(!1),2e3)})};return e(F,{title:"API Configuration",current:n,children:[T,e("div",{class:"max-w-2xl space-y-6",children:[e("div",{class:"bg-white rounded-lg border border-gray-200 p-5",children:[e("h2",{class:"text-sm font-semibold text-gray-700 mb-1",children:"Allowed Origins"}),e("p",{class:"text-xs text-gray-500 mb-4",children:["Only requests from these origins will receive consent data. Use ",e("code",{class:"bg-gray-100 px-1 rounded",children:"*"})," to allow all (not recommended for production)."]}),e("div",{class:"flex gap-2 mb-3",children:[e("input",{type:"text",placeholder:"https://example.com",value:r,onInput:S=>{s(S.target.value),l("")},onKeyDown:S=>S.key==="Enter"&&R(),class:"flex-1 border border-gray-300 rounded px-3 py-1.5 text-sm"}),e("button",{onClick:R,disabled:i||!r.trim(),class:"px-3 py-1.5 bg-blue-600 text-white text-sm rounded hover:bg-blue-700 disabled:opacity-50",children:"Add"})]}),c&&e("p",{class:"text-xs text-red-600 mb-2",children:c}),t.length===0?e("p",{class:"text-xs text-gray-400 py-3 text-center",children:"No origins configured — all origins are currently allowed."}):e("ul",{class:"border border-gray-200 rounded divide-y divide-gray-100 text-sm",children:t.map(S=>e("li",{class:"flex items-center justify-between px-3 py-2 group",children:[e("span",{class:"font-mono text-gray-700 text-xs",children:S}),e("button",{onClick:()=>U(S),disabled:i,class:"text-gray-400 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity disabled:opacity-30",title:"Remove",children:e(Zt,{size:13})})]},S))})]}),e("div",{class:"bg-white rounded-lg border border-gray-200 p-5",children:[e("h2",{class:"text-sm font-semibold text-gray-700 mb-1",children:"Admin API Tokens"}),e("p",{class:"text-xs text-gray-500 mb-4",children:["Bearer tokens for server-side and CI/CD access to the Consenti ",e("strong",{children:"admin"})," API. Pass as ",e("code",{class:"font-mono bg-gray-100 px-1 rounded",children:"Authorization: Bearer <token>"}),". Never expose in client-side code — these grant full admin access."]}),m&&e("div",{class:"mb-4 p-3 bg-green-50 border border-green-200 rounded",children:[e("p",{class:"text-xs font-medium text-green-800 mb-2",children:"Token created — copy it now, it won't be shown again."}),e("div",{class:"flex items-center gap-2",children:[e("code",{class:`flex-1 text-xs font-mono text-green-700 break-all ${N?"":"blur-sm select-none"}`,children:m.key}),e("button",{type:"button",onClick:()=>I(S=>!S),class:"shrink-0 text-gray-500 hover:text-gray-700",title:N?"Hide":"Show",children:N?e(hn,{size:14}):e(pn,{size:14})}),e("button",{type:"button",onClick:B,class:"shrink-0 text-gray-500 hover:text-green-700",title:"Copy to clipboard",children:y?e(_e,{size:14,className:"text-green-600"}):e(Je,{size:14})})]})]}),e("div",{class:"flex gap-2 mb-4",children:[e("input",{type:"text",placeholder:"Token name (e.g. CI deploy)",value:u,onInput:S=>h(S.target.value),onKeyDown:S=>S.key==="Enter"&&C(),class:"flex-1 border border-gray-300 rounded px-3 py-1.5 text-sm"}),e("button",{onClick:C,class:"px-3 py-1.5 bg-blue-600 text-white text-sm rounded hover:bg-blue-700",children:"Create"})]}),v&&e("p",{class:"text-xs text-red-600 mb-2",children:v}),!x&&p.length===0&&e("p",{class:"text-xs text-gray-400 py-3 text-center",children:"No tokens yet."}),p.length>0&&e("table",{class:"w-full text-xs border border-gray-200 rounded overflow-hidden",children:[e("thead",{class:"bg-gray-50",children:e("tr",{children:[e("th",{class:"text-left px-3 py-2 text-gray-600 font-medium",children:"Name"}),e("th",{class:"text-left px-3 py-2 text-gray-600 font-medium",children:"Status"}),e("th",{class:"text-left px-3 py-2 text-gray-600 font-medium",children:"Created"}),e("th",{class:"px-3 py-2"})]})}),e("tbody",{class:"divide-y divide-gray-100",children:p.map(S=>e("tr",{children:[e("td",{class:"px-3 py-2 font-mono",children:S.name}),e("td",{class:"px-3 py-2",children:e("span",{class:`font-medium ${S.isActive?"text-green-600":"text-gray-400"}`,children:S.isActive?"Active":"Revoked"})}),e("td",{class:"px-3 py-2 text-gray-500",children:new Date(S.createdAt).toLocaleDateString()}),e("td",{class:"px-3 py-2 text-right",children:S.isActive&&e("button",{onClick:()=>O(S.id),class:"text-red-500 hover:underline",children:"Revoke"})})]},S.id))})]})]})]})]})}const ft="/consenti/admin/docs";function Vs({current:n}){const[t,o]=g(0);return e(F,{title:"Endpoint Docs",current:n,children:[e("div",{class:"flex items-center justify-between mb-3",children:[e("p",{class:"text-xs text-gray-500",children:"Interactive OpenAPI documentation for the Consenti Admin API."}),e("div",{class:"flex items-center gap-3",children:[e("button",{onClick:()=>o(r=>r+1),class:"flex items-center gap-1.5 text-xs text-gray-500 hover:text-gray-700",title:"Reload",children:[e(ei,{size:13})," Reload"]}),e("a",{href:ft,target:"_blank",rel:"noopener noreferrer",class:"flex items-center gap-1.5 text-xs text-blue-600 hover:underline",children:["Open in new tab ",e(vr,{size:12})]})]})]}),e("div",{class:"rounded-lg border border-gray-200 overflow-hidden bg-white",style:"height: calc(100vh - 200px)",children:e("iframe",{src:ft,class:"w-full h-full border-0",title:"API Documentation"},t)})]})}function yt(){return window.location.hash||"#/"}function Hs(){const{user:n}=pe(),[t,o]=g(yt);return j(()=>{const r=()=>o(yt());return window.addEventListener("hashchange",r),()=>window.removeEventListener("hashchange",r)},[]),n?t==="#/login"?(window.location.hash="#/",null):t==="#/"||t==="#"?e(tt,{current:t}):t==="#/banners/profiles"?e(ji,{current:t}):t==="#/banners/profiles/new"?e(ct,{current:t}):t.startsWith("#/banners/profiles/")?e(ct,{id:t.slice(19),current:t}):t==="#/banners/cookie-templates"?e(cs,{current:t}):t==="#/banners/cookie-templates/new"?e(ht,{current:t}):t.startsWith("#/banners/cookie-templates/")?e(ht,{id:t.slice(27),current:t}):t==="#/banners/ui-templates"?e(fs,{current:t}):t==="#/banners/ui-templates/new"?e(ut,{current:t}):t.startsWith("#/banners/ui-templates/")?e(ut,{id:t.slice(23),current:t}):t==="#/consents"?e(Ps,{current:t}):t==="#/visitors"?e(Ls,{current:t}):t==="#/users"?e(As,{current:t}):t==="#/roles"?e(Bs,{current:t}):t==="#/tenants"?e(Ns,{current:t}):t==="#/vendors"||t.startsWith("#/vendors?")?e(Rs,{current:t}):t==="#/audit"?e(zs,{current:t}):t==="#/api/config"?e(js,{current:t}):t==="#/api/docs"?e(Vs,{current:t}):t==="#/settings"?e(Us,{current:t}):e(tt,{current:"#/"}):(t!=="#/login"&&(window.location.hash="#/login"),e(Eo,{}))}function Gs(){return e(Po,{children:e(Mo,{children:e(Hs,{})})})}cn(e(Gs,{}),document.getElementById("app"));
|