@product7/feedback-sdk 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).FeedbackSDK={})}(this,function(e){"use strict";class t extends Error{constructor(e,n){super(e),this.name="SDKError",this.cause=n,Error.captureStackTrace&&Error.captureStackTrace(this,t)}}class n extends Error{constructor(e,t,i){super(t),this.name="APIError",this.status=e,this.response=i,Error.captureStackTrace&&Error.captureStackTrace(this,n)}isNetworkError(){return 0===this.status}isClientError(){return this.status>=400&&this.status<500}isServerError(){return this.status>=500&&this.status<600}}class i extends Error{constructor(e,t,n){super(e),this.name="WidgetError",this.widgetType=t,this.widgetId=n,Error.captureStackTrace&&Error.captureStackTrace(this,i)}}class o extends Error{constructor(e,t){super(e),this.name="ConfigError",this.configKey=t,Error.captureStackTrace&&Error.captureStackTrace(this,o)}}class s extends Error{constructor(e,t,n){super(e),this.name="ValidationError",this.field=t,this.value=n,Error.captureStackTrace&&Error.captureStackTrace(this,s)}}class r{constructor(e={}){this.workspace=e.workspace,this.sessionToken=null,this.sessionExpiry=null,this.userContext=e.userContext||null,e.apiUrl?this.baseURL=e.apiUrl:this.workspace?this.baseURL=`https://${this.workspace}.api.staging.product7.io/api/v1`:this.baseURL="https://api.staging.product7.io/api/v1",console.log("[APIService] Using API URL:",this.baseURL),this._loadStoredSession()}async init(e=null){if(console.log("[APIService] Starting initialization..."),e&&(this.userContext=e),this.isSessionValid())return console.log("[APIService] Found valid existing session"),{sessionToken:this.sessionToken};if(this.userContext||(console.log("[APIService] No user context provided, attempting auto-detection..."),this.userContext=this._getUserContextFromStorage()),!this.userContext||!this.workspace){const e=`Missing ${this.workspace?"user context":"workspace"} for initialization`;throw console.error("[APIService]",e),new n(400,e)}console.log("[APIService] User context detected:",this.userContext);const t={workspace:this.workspace,user:this.userContext};console.log("[APIService] Making init request to:",`${this.baseURL}/widget/init`),console.log("[APIService] Payload:",t);try{const e=await this._makeRequest("/widget/init",{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}});return console.log("[APIService] Init response:",e),this.sessionToken=e.session_token,this.sessionExpiry=new Date(Date.now()+1e3*e.expires_in),this._storeSession(),{sessionToken:this.sessionToken,config:e.config||{},expiresIn:e.expires_in}}catch(e){throw console.error("[APIService] Init failed:",e),new n(e.status||500,`Failed to initialize widget: ${e.message}`,e.response)}}async submitFeedback(e){if(this.isSessionValid()||await this.init(),!this.sessionToken)throw new n(401,"No valid session token available");const t={board:e.board_id||e.board||e.boardId,title:e.title,content:e.content,attachments:e.attachments||[]};try{return await this._makeRequest("/widget/feedback",{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.sessionToken}`}})}catch(t){if(401===t.status)return this.sessionToken=null,this.sessionExpiry=null,await this.init(),this.submitFeedback(e);throw new n(t.status||500,`Failed to submit feedback: ${t.message}`,t.response)}}isSessionValid(){return this.sessionToken&&this.sessionExpiry&&new Date<this.sessionExpiry}setUserContext(e){this.userContext=e,"undefined"!=typeof localStorage&&localStorage.setItem("feedbackSDK_userContext",JSON.stringify(e))}getUserContext(){return this.userContext}clearSession(){this.sessionToken=null,this.sessionExpiry=null,"undefined"!=typeof localStorage&&localStorage.removeItem("feedbackSDK_session")}_getUserContextFromStorage(){if("undefined"==typeof localStorage)return null;console.log("[APIService] Attempting to detect user from storage...");try{const e=localStorage.getItem("feedbackSDK_userContext");if(e)return console.log("[APIService] Found user context in feedbackSDK storage"),JSON.parse(e);if("undefined"!=typeof window&&window.FeedbackSDKUserContext)return console.log("[APIService] Found user context in window.FeedbackSDKUserContext"),window.FeedbackSDKUserContext;if("undefined"!=typeof window&&window.currentUser)return console.log("[APIService] Found user context in window.currentUser"),this._mapUserData(window.currentUser);const t=["auth","currentUser","userSession","user"];for(const e of t){const t=localStorage.getItem(e);if(t){console.log(`[APIService] Found data in localStorage.${e}`);const n=JSON.parse(t),i=n.user||n;if(i&&(i.id||i.user_id||i.email))return console.log("[APIService] Successfully mapped user data"),this._mapUserData(i)}}return console.log("[APIService] No user context found in any storage location"),null}catch(e){return console.warn("[FeedbackSDK] Failed to load user context from storage:",e),null}}_mapUserData(e){console.log("[APIService] Mapping user data:",e);const t={user_id:e.id||e.user_id||e.userId,email:e.email,name:e.name||e.displayName||e.full_name,custom_fields:{},company:{}};if(e.company||e.organization){const n=e.company||e.organization;t.company={id:n.id||n.company_id,name:n.name||n.company_name,monthly_spend:n.monthly_spend||n.spend}}return["plan","role","tier","subscription"].forEach(n=>{e[n]&&(t.custom_fields[n]=e[n])}),console.log("[APIService] Mapped result:",t),t}_storeSession(){if("undefined"!=typeof localStorage)try{const e={token:this.sessionToken,expiry:this.sessionExpiry.toISOString(),workspace:this.workspace};localStorage.setItem("feedbackSDK_session",JSON.stringify(e))}catch(e){console.warn("[FeedbackSDK] Failed to store session:",e)}}_loadStoredSession(){if("undefined"==typeof localStorage)return!1;try{const e=localStorage.getItem("feedbackSDK_session");if(!e)return!1;const t=JSON.parse(e);return this.sessionToken=t.token,this.sessionExpiry=new Date(t.expiry),this.isSessionValid()}catch(e){return console.warn("[FeedbackSDK] Failed to load stored session:",e),!1}}async _makeRequest(e,t={}){const i=`${this.baseURL}${e}`;console.log("[APIService] Making request to:",i);try{const e=await fetch(i,t);if(!e.ok){let t=`HTTP ${e.status}`,i=null;try{i=await e.json(),t=i.message||i.error||t}catch(n){t=await e.text()||t}throw new n(e.status,t,i)}const o=e.headers.get("content-type");return o&&o.includes("application/json")?await e.json():await e.text()}catch(e){if(e instanceof n)throw e;throw new n(0,e.message,null)}}}class a{constructor(){this.events=new Map}on(e,t){return this.events.has(e)||this.events.set(e,[]),this.events.get(e).push(t),()=>this.off(e,t)}off(e,t){const n=this.events.get(e);if(n){const e=n.indexOf(t);e>-1&&n.splice(e,1)}}emit(e,t){const n=this.events.get(e);n&&n.forEach(e=>{try{e(t)}catch(e){console.error("[FeedbackSDK] Event callback error:",e)}})}once(e,t){const n=this.on(e,e=>{t(e),n()});return n}clear(){this.events.clear()}getListenerCount(e){const t=this.events.get(e);return t?t.length:0}}function d(e="feedback"){return`${e}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function c(e,t){const n={...e};for(const i in t)t.hasOwnProperty(i)&&(t[i]&&"object"==typeof t[i]&&!Array.isArray(t[i])?n[i]=c(e[i]||{},t[i]):n[i]=t[i]);return n}function l(){return"undefined"!=typeof window&&"undefined"!=typeof document}var u=Object.freeze({__proto__:null,debounce:function(e,t){let n;return function(...i){clearTimeout(n),n=setTimeout(()=>{clearTimeout(n),e(...i)},t)}},deepMerge:c,delay:function(e){return new Promise(t=>setTimeout(t,e))},escapeRegex:function(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")},formatFileSize:function(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,t)).toFixed(2))+" "+["Bytes","KB","MB","GB"][t]},generateId:d,getBrowserInfo:function(){return{userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language||navigator.userLanguage,cookieEnabled:navigator.cookieEnabled,screenResolution:`${screen.width}x${screen.height}`,windowSize:`${window.innerWidth}x${window.innerHeight}`,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone}},getCSSProperty:function(e,t,n=""){if(!e||!t)return n;try{return window.getComputedStyle(e).getPropertyValue(t)||n}catch(e){return n}},getCurrentTimestamp:function(){return(new Date).toISOString()},getNestedProperty:function(e,t,n=void 0){if(!e||!t)return n;const i=t.split(".");let o=e;for(const e of i){if(null==o||!(e in o))return n;o=o[e]}return o},isBrowser:l,isInViewport:function(e){if(!e)return!1;const t=e.getBoundingClientRect();return t.top>=0&&t.left>=0&&t.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&t.right<=(window.innerWidth||document.documentElement.clientWidth)},isMobile:function(){return!!l()&&(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||window.innerWidth<=768)},isValidEmail:function(e){return!(!e||"string"!=typeof e)&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim())},safeJsonParse:function(e,t=null){try{return JSON.parse(e)}catch(e){return t}},sanitizeHTML:function(e){if(!e||"string"!=typeof e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML},scrollToElement:function(e,t={}){if(!e)return;e.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest",...t})},setNestedProperty:function(e,t,n){if(!e||!t)return e;const i=t.split("."),o=i.pop();let s=e;for(const e of i)e in s&&"object"==typeof s[e]||(s[e]={}),s=s[e];return s[o]=n,e},throttle:function(e,t){let n,i;return function(...o){i?(clearTimeout(n),n=setTimeout(()=>{Date.now()-i>=t&&(e(...o),i=Date.now())},t-(Date.now()-i))):(e(...o),i=Date.now())}},validateConfig:function(e,t=[]){const n=[];for(const i of t)e[i]||n.push(i);if(n.length>0)throw new Error(`Missing required configuration: ${n.join(", ")}`);return!0}});class h{constructor(e={}){this.id=e.id,this.sdk=e.sdk,this.apiService=e.apiService,this.type=e.type||"base",this.options={container:null,position:this.sdk.config.position,theme:this.sdk.config.theme,boardId:this.sdk.config.boardId,autoShow:!1,customStyles:{},...e},this.element=null,this.modalElement=null,this.mounted=!1,this.destroyed=!1,this.state={isOpen:!1,isSubmitting:!1,title:"",content:"",email:"",attachments:[],errors:{}},this._bindMethods()}mount(e){return this.mounted||this.destroyed||("string"==typeof e&&(e=document.querySelector(e)),e||(e=document.body),this.container=e,this.element=this._render(),this.container.appendChild(this.element),this.mounted=!0,this._attachEvents(),this.onMount(),this.options.autoShow&&this.show(),this.sdk.eventBus.emit("widget:mounted",{widget:this})),this}show(){return this.element&&(this.element.style.display="block"),this}hide(){return this.element&&(this.element.style.display="none"),this}openModal(){this.state.isOpen=!0,this._renderModal()}closeModal(){this.state.isOpen=!1,this.modalElement&&(this.modalElement.remove(),this.modalElement=null),this._resetForm()}async submitFeedback(){if(!this.state.isSubmitting)try{this.state.isSubmitting=!0,this._updateSubmitButton();const e={title:this.state.title||"Feedback",content:this.state.content,email:this.state.email,board_id:this.options.boardId,attachments:this.state.attachments};if(!this.state.content.trim())return void this._showError("Please enter your feedback message.");const t=await this.apiService.submitFeedback(e);this._showSuccessMessage(),this.closeModal(),this.sdk.eventBus.emit("feedback:submitted",{widget:this,feedback:t})}catch(e){this._showError("Failed to submit feedback. Please try again."),this.sdk.eventBus.emit("feedback:error",{widget:this,error:e})}finally{this.state.isSubmitting=!1,this._updateSubmitButton()}}handleConfigUpdate(e){this.options.theme=e.theme,this.element&&this._updateTheme()}destroy(){this.destroyed||(this.onDestroy(),this.closeModal(),this.element&&this.element.parentNode&&this.element.parentNode.removeChild(this.element),this.destroyed=!0,this.mounted=!1,this.sdk.eventBus.emit("widget:destroyed",{widget:this}))}onMount(){}onDestroy(){}_render(){throw new Error("_render() must be implemented by concrete widget")}_attachEvents(){}_bindMethods(){this.openModal=this.openModal.bind(this),this.closeModal=this.closeModal.bind(this),this.submitFeedback=this.submitFeedback.bind(this)}_renderModal(){if(this.modalElement)return;this.modalElement=document.createElement("div"),this.modalElement.className=`feedback-modal theme-${this.options.theme}`,this.modalElement.innerHTML=this._getModalHTML(),document.body.appendChild(this.modalElement),this._attachModalEvents();const e=this.modalElement.querySelector("input, textarea");e&&setTimeout(()=>e.focus(),100)}_getModalHTML(){return`\n <div class="feedback-modal-overlay">\n <div class="feedback-modal-content">\n <div class="feedback-modal-header">\n <h3>Send Feedback</h3>\n <button class="feedback-modal-close" type="button">&times;</button>\n </div>\n <form class="feedback-form">\n <div class="feedback-form-group">\n <label for="feedback-title-${this.id}">Title</label>\n <input \n type="text" \n id="feedback-title-${this.id}" \n name="title" \n placeholder="Brief description of your feedback"\n value="${this.state.title}"\n />\n </div>\n <div class="feedback-form-group">\n <label for="feedback-content-${this.id}">Message *</label>\n <textarea \n id="feedback-content-${this.id}" \n name="content" \n placeholder="Tell us more about your feedback..."\n required\n >${this.state.content}</textarea>\n </div>\n <div class="feedback-form-actions">\n <button type="button" class="feedback-btn feedback-btn-cancel">Cancel</button>\n <button type="submit" class="feedback-btn feedback-btn-submit">\n ${this.state.isSubmitting?"Sending...":"Send Feedback"}\n </button>\n </div>\n <div class="feedback-error" style="display: none;"></div>\n </form>\n </div>\n </div>\n `}_attachModalEvents(){const e=this.modalElement;e.querySelector(".feedback-modal-close").addEventListener("click",this.closeModal),e.querySelector(".feedback-btn-cancel").addEventListener("click",this.closeModal),e.querySelector(".feedback-modal-overlay").addEventListener("click",e=>{e.target===e.currentTarget&&this.closeModal()});e.querySelector(".feedback-form").addEventListener("submit",e=>{e.preventDefault(),this.submitFeedback()}),e.querySelector('input[name="title"]').addEventListener("input",e=>{this.state.title=e.target.value}),e.querySelector('textarea[name="content"]').addEventListener("input",e=>{this.state.content=e.target.value})}_updateSubmitButton(){if(this.modalElement){const e=this.modalElement.querySelector(".feedback-btn-submit");e&&(e.textContent=this.state.isSubmitting?"Sending...":"Send Feedback",e.disabled=this.state.isSubmitting)}}_showError(e){if(this.modalElement){const t=this.modalElement.querySelector(".feedback-error");t.textContent=e,t.style.display="block",setTimeout(()=>{t&&(t.style.display="none")},5e3)}}_showSuccessMessage(){const e=document.createElement("div");e.className="feedback-success-notification",e.innerHTML='\n <div class="feedback-success-content">\n <span>✓ Feedback submitted successfully!</span>\n <button class="feedback-success-close">&times;</button>\n </div>\n ',document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.parentNode.removeChild(e)},3e3),e.querySelector(".feedback-success-close").addEventListener("click",()=>{e.parentNode&&e.parentNode.removeChild(e)})}_resetForm(){this.state.title="",this.state.content="",this.state.email="",this.state.errors={}}_updateTheme(){this.element&&(this.element.className=this.element.className.replace(/theme-\w+/,`theme-${this.options.theme}`)),this.modalElement&&(this.modalElement.className=this.modalElement.className.replace(/theme-\w+/,`theme-${this.options.theme}`))}}class f extends h{constructor(e){super({...e,type:"button"})}_render(){const e=document.createElement("div");return e.className=`feedback-widget feedback-widget-button theme-${this.options.theme} position-${this.options.position}`,e.innerHTML='\n <button class="feedback-trigger-btn" type="button">\n <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">\n <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>\n <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>\n </svg>\n Feedback\n </button>\n ',this.options.customStyles&&Object.assign(e.style,this.options.customStyles),e}_attachEvents(){const e=this.element.querySelector(".feedback-trigger-btn");e.addEventListener("click",this.openModal),e.addEventListener("mouseenter",()=>{this.state.isSubmitting||(e.style.transform="translateY(-2px)")}),e.addEventListener("mouseleave",()=>{e.style.transform="translateY(0)"})}updateText(e){const t=this.element?.querySelector(".feedback-trigger-btn");if(t){const n=t.childNodes[t.childNodes.length-1];n&&n.nodeType===Node.TEXT_NODE&&(n.textContent=e)}}updatePosition(e){this.options.position=e,this.element&&(this.element.className=this.element.className.replace(/position-\w+-\w+/,`position-${e}`))}}class p extends h{constructor(e){super({...e,type:"inline"})}_render(){const e=document.createElement("div");return e.className=`feedback-widget feedback-widget-inline theme-${this.options.theme}`,e.innerHTML=`\n <div class="feedback-inline-content">\n <h3>Send us your feedback</h3>\n <form class="feedback-inline-form">\n <div class="feedback-form-group">\n <input \n type="text" \n name="title" \n placeholder="Title (optional)"\n value="${this.state.title}"\n />\n </div>\n <div class="feedback-form-group">\n <textarea \n name="content" \n placeholder="Your feedback..."\n required\n >${this.state.content}</textarea>\n </div>\n <div class="feedback-form-group">\n <input \n type="email" \n name="email" \n placeholder="Email (optional)"\n value="${this.state.email}"\n />\n </div>\n <button type="submit" class="feedback-btn feedback-btn-submit">\n Send Feedback\n </button>\n <div class="feedback-error" style="display: none;"></div>\n </form>\n </div>\n `,this.options.customStyles&&Object.assign(e.style,this.options.customStyles),e}_attachEvents(){const e=this.element.querySelector(".feedback-inline-form");e.addEventListener("submit",e=>{e.preventDefault(),this.submitFeedback()}),e.querySelector('input[name="title"]').addEventListener("input",e=>{this.state.title=e.target.value}),e.querySelector('textarea[name="content"]').addEventListener("input",e=>{this.state.content=e.target.value}),e.querySelector('input[name="email"]').addEventListener("input",e=>{this.state.email=e.target.value})}openModal(){const e=this.element.querySelector('textarea[name="content"]');e&&e.focus()}closeModal(){}_showSuccessMessage(){const e=this.element.querySelector(".feedback-inline-content"),t=e.innerHTML;e.innerHTML='\n <div class="feedback-success">\n <div class="feedback-success-icon">✓</div>\n <h3>Thank you!</h3>\n <p>Your feedback has been submitted successfully.</p>\n <button class="feedback-btn feedback-btn-reset">Send Another</button>\n </div>\n ';e.querySelector(".feedback-btn-reset").addEventListener("click",()=>{e.innerHTML=t,this._attachEvents(),this._resetForm()})}_showError(e){const t=this.element.querySelector(".feedback-error");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t&&(t.style.display="none")},5e3))}_updateSubmitButton(){const e=this.element.querySelector(".feedback-btn-submit");e&&(e.textContent=this.state.isSubmitting?"Sending...":"Send Feedback",e.disabled=this.state.isSubmitting)}updateTitle(e){const t=this.element?.querySelector("h3");t&&(t.textContent=e)}setPlaceholder(e,t){const n=this.element?.querySelector(`[name="${e}"]`);n&&(n.placeholder=t)}}class m extends h{constructor(e){super({...e,type:"tab"})}_render(){const e=document.createElement("div");return e.className=`feedback-widget feedback-widget-tab theme-${this.options.theme} position-${this.options.position}`,e.innerHTML='\n <div class="feedback-tab-trigger">\n <span class="feedback-tab-text">Feedback</span>\n </div>\n ',this.options.customStyles&&Object.assign(e.style,this.options.customStyles),e}_attachEvents(){const e=this.element.querySelector(".feedback-tab-trigger");e.addEventListener("click",this.openModal),e.addEventListener("mouseenter",()=>{this.state.isSubmitting||(e.style.transform=this._getHoverTransform())}),e.addEventListener("mouseleave",()=>{e.style.transform="none"})}_getHoverTransform(){const e=this.options.position;return e.includes("right")?"translateX(-5px)":e.includes("left")?"translateX(5px)":"none"}updateText(e){const t=this.element?.querySelector(".feedback-tab-text");t&&(t.textContent=e)}updatePosition(e){this.options.position=e,this.element&&(this.element.className=this.element.className.replace(/position-\w+-\w+/,`position-${e}`))}}class b{static widgets=new Map([["button",f],["tab",m],["inline",p]]);static register(e,n){if("string"!=typeof e||!e.trim())throw new t("Widget type must be a non-empty string");if("function"!=typeof n)throw new t("Widget class must be a constructor function");this.widgets.set(e,n)}static create(e,n={}){const i=this.widgets.get(e);if(!i){const n=Array.from(this.widgets.keys()).join(", ");throw new t(`Unknown widget type: ${e}. Available types: ${n}`)}try{return new i(n)}catch(n){throw new t(`Failed to create widget of type '${e}': ${n.message}`,n)}}static getAvailableTypes(){return Array.from(this.widgets.keys())}static isTypeRegistered(e){return this.widgets.has(e)}static unregister(e){return this.widgets.delete(e)}static clear(){this.widgets.clear()}static getWidgetClass(e){return this.widgets.get(e)}}class g{constructor(e={}){this.config=this._validateAndMergeConfig(e),this.initialized=!1,this.widgets=new Map,this.eventBus=new a,this.apiService=new r({apiUrl:this.config.apiUrl,workspace:this.config.workspace,userContext:this.config.userContext}),this._bindMethods()}async init(){if(this.initialized)return{alreadyInitialized:!0};try{const e=await this.apiService.init(this.config.userContext);return e.config&&(this.config=c(this.config,e.config)),this.initialized=!0,this.eventBus.emit("sdk:initialized",{config:this.config,sessionToken:e.sessionToken}),{initialized:!0,config:e.config||{},sessionToken:e.sessionToken,expiresIn:e.expiresIn}}catch(e){throw this.eventBus.emit("sdk:error",{error:e}),new t(`Failed to initialize SDK: ${e.message}`,e)}}createWidget(e="button",n={}){if(!this.initialized)throw new t("SDK must be initialized before creating widgets. Call init() first.");const i=d("widget"),o={id:i,sdk:this,apiService:this.apiService,...this.config,...n};try{const t=b.create(e,o);return this.widgets.set(i,t),this.eventBus.emit("widget:created",{widget:t,type:e}),t}catch(e){throw new t(`Failed to create widget: ${e.message}`,e)}}getWidget(e){return this.widgets.get(e)}getAllWidgets(){return Array.from(this.widgets.values())}destroyWidget(e){const t=this.widgets.get(e);return!!t&&(t.destroy(),this.widgets.delete(e),this.eventBus.emit("widget:removed",{widgetId:e}),!0)}destroyAllWidgets(){for(const e of this.widgets.values())e.destroy();this.widgets.clear(),this.eventBus.emit("widgets:cleared")}updateConfig(e){const t={...this.config};this.config=this._validateAndMergeConfig(e,this.config);for(const e of this.widgets.values())e.handleConfigUpdate(this.config);this.eventBus.emit("config:updated",{oldConfig:t,newConfig:this.config})}setUserContext(e){this.config.userContext=e,this.apiService&&this.apiService.setUserContext(e),this.eventBus.emit("user:updated",{userContext:e})}getUserContext(){return this.config.userContext||(this.apiService?this.apiService.getUserContext():null)}async reinitialize(e=null){return this.apiService.clearSession(),this.initialized=!1,e&&this.setUserContext(e),this.init()}on(e,t){return this.eventBus.on(e,t),this}off(e,t){return this.eventBus.off(e,t),this}once(e,t){return this.eventBus.once(e,t),this}emit(e,t){return this.eventBus.emit(e,t),this}destroy(){this.destroyAllWidgets(),this.eventBus.removeAllListeners(),this.apiService.clearSession(),this.initialized=!1,this.eventBus.emit("sdk:destroyed")}_validateAndMergeConfig(e,t={}){const n=c(c({apiUrl:null,workspace:null,userContext:null,position:"bottom-right",theme:"light",boardId:"general",autoShow:!0,debug:!1},t),e),i=["workspace"].filter(e=>!n[e]);if(i.length>0)throw new o(`Missing required configuration: ${i.join(", ")}`);return n.userContext&&this._validateUserContext(n.userContext),n}_validateUserContext(e){if(!e.user_id&&!e.email)throw new o("User context must include at least user_id or email");const t={user_id:"string",email:"string",name:"string",custom_fields:"object",company:"object"};for(const[n,i]of Object.entries(t))if(e[n]&&typeof e[n]!==i)throw new o(`User context field '${n}' must be of type '${i}'`)}_bindMethods(){this.createWidget=this.createWidget.bind(this),this.destroyWidget=this.destroyWidget.bind(this),this.updateConfig=this.updateConfig.bind(this)}static create(e){return new g(e)}static async createAndInit(e){const t=new g(e);return await t.init(),t}static extractUserContextFromAuth(e){return e?{user_id:e.sub||e.id||e.user_id,email:e.email,name:e.name||e.display_name||e.full_name,custom_fields:{role:e.role,plan:e.plan||e.subscription?.plan,...e.custom_fields||{}},company:e.company||e.organization?{id:e.company?.id||e.organization?.id,name:e.company?.name||e.organization?.name,monthly_spend:e.company?.monthly_spend}:void 0}:null}}function k(){if(console.log("injectStyles called"),console.log("document exists:","undefined"!=typeof document),console.log("CSS_STYLES exists:",!0),"undefined"==typeof document||document.querySelector("#feedback-sdk-styles"))console.log("CSS already exists or document not ready");else{console.log("Injecting CSS...");const e=document.createElement("style");e.id="feedback-sdk-styles",e.textContent="\n.feedback-widget {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 14px;\n line-height: 1.4;\n z-index: 999999;\n box-sizing: border-box;\n}\n\n.feedback-widget *,\n.feedback-widget *::before,\n.feedback-widget *::after {\n box-sizing: border-box;\n}\n\n.feedback-widget-button {\n position: fixed;\n z-index: 999999;\n}\n\n.feedback-widget-button.position-bottom-right {\n bottom: 20px;\n right: 20px;\n}\n\n.feedback-widget-button.position-bottom-left {\n bottom: 20px;\n left: 20px;\n}\n\n.feedback-widget-button.position-top-right {\n top: 20px;\n right: 20px;\n}\n\n.feedback-widget-button.position-top-left {\n top: 20px;\n left: 20px;\n}\n\n.feedback-trigger-btn {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 44px;\n overflow: hidden;\n border-radius: 0.5rem;\n border: none;\n padding: 10px 16px;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.3s duration;\n color: white;\n background: #155EEF;\n box-shadow: 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n.feedback-trigger-btn:hover:not(:disabled) {\n background: #004EEB;\n box-shadow: 0 1px 2px 0 rgba(16, 24, 40, 0.1);\n}\n\n.feedback-trigger-btn:disabled {\n opacity: 0.7;\n cursor: not-allowed;\n}\n\n.feedback-trigger-btn:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n.feedback-modal {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 1000000;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n}\n\n.feedback-modal-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.feedback-modal-content {\n background: white;\n border-radius: 8px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n min-width: 460px;\n max-width: 500px;\n width: 100%;\n padding: 16px;\n max-height: 85vh;\n overflow-y: hidden;\n position: relative;\n}\n\n.feedback-modal.theme-dark .feedback-modal-content {\n background: #1F2937;\n color: white;\n}\n\n.feedback-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid #D1D5DB;\n flex-shrink: 0;\n}\n\n.feedback-modal.theme-dark .feedback-modal-header {\n border-bottom-color: #374151;\n}\n\n.feedback-modal-header h3 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n}\n\n.feedback-modal-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #6B7280;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s ease;\n}\n\n.feedback-modal-close:hover {\n color: #374151;\n}\n\n.feedback-modal-close:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n.feedback-modal.theme-dark .feedback-modal-close {\n color: #9CA3AF;\n}\n\n.feedback-modal.theme-dark .feedback-modal-close:hover {\n color: #D1D5DB;\n}\n\n.feedback-form {\n padding: 16px;\n}\n\n.feedback-form-group {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 12px;\n}\n\n.feedback-form-group:last-child {\n margin-bottom: 0;\n}\n\n.feedback-form-group label {\n font-size: 14px;\n font-weight: 500;\n line-height: 1.25;\n color: #374151;\n}\n\n.feedback-modal.theme-dark .feedback-form-group label {\n color: #D1D5DB;\n}\n\n.feedback-form-group input {\n height: 40px;\n width: 100%;\n border-radius: 6px;\n border: 1px solid #D1D5DB;\n padding: 2px 12px;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.25;\n color: #1F2937;\n font-family: inherit;\n outline: none;\n transition: all 0.2s ease;\n}\n\n.feedback-form-group input::placeholder {\n font-size: 14px;\n color: #6B7280;\n}\n\n.feedback-form-group input:focus {\n border-color: #84ADFF;\n box-shadow: 0 0 0 1px rgba(16, 24, 40, 0.05), 0 0 0 3px rgba(41, 112, 255, 0.2);\n}\n\n.feedback-form-group input:focus-visible {\n outline: none;\n}\n\n.feedback-form-group textarea {\n min-height: 100px;\n width: 100%;\n resize: both;\n border-radius: 6px;\n border: 1px solid #D1D5DB;\n padding: 2px 12px;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.25;\n color: #1F2937;\n font-family: inherit;\n outline: none;\n transition: all 0.2s ease;\n}\n\n.feedback-form-group textarea::placeholder {\n font-size: 14px;\n color: #6B7280;\n}\n\n.feedback-form-group textarea:focus {\n border-color: #84ADFF;\n box-shadow: 0 0 0 1px rgba(16, 24, 40, 0.05), 0 0 0 3px rgba(41, 112, 255, 0.2);\n}\n\n.feedback-form-group textarea:focus-visible {\n outline: none;\n}\n\n.feedback-modal.theme-dark .feedback-form-group input,\n.feedback-modal.theme-dark .feedback-form-group textarea {\n background: #374151;\n border-color: #4B5563;\n color: white;\n}\n\n.feedback-btn {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n border-radius: 6px;\n border: none;\n height: 40px;\n padding: 2px 16px;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.feedback-btn:disabled {\n opacity: 0.7;\n cursor: not-allowed;\n}\n\n.feedback-btn:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n.feedback-btn-submit {\n background: #155EEF;\n color: white;\n}\n\n.feedback-btn-submit:hover:not(:disabled) {\n background: #004EEB;\n}\n\n.feedback-btn-cancel {\n background: transparent;\n color: #6B7280;\n border: 1px solid #D1D5DB;\n}\n\n.feedback-btn-cancel:hover:not(:disabled) {\n background: #F9FAFB;\n border-color: #9CA3AF;\n color: #374151;\n}\n\n.feedback-modal.theme-dark .feedback-btn-cancel {\n color: #D1D5DB;\n border-color: #4B5563;\n}\n\n.feedback-modal.theme-dark .feedback-btn-cancel:hover:not(:disabled) {\n background: #374151;\n}\n\n.feedback-form-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-top: 16px;\n padding-top: 4px;\n}\n\n.feedback-loading {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n mask: radial-gradient(transparent 62%, white 65%);\n -webkit-mask: radial-gradient(transparent 62%, white 65%);\n animation: feedbackRotate 0.7s linear infinite;\n}\n\n.feedback-loading-white {\n background: conic-gradient(from 0deg, rgba(255, 255, 255, 0.5), white);\n}\n\n.feedback-loading-blue {\n background: conic-gradient(from 0deg, #004EEB, #eff4ff);\n}\n\n@keyframes feedbackRotate {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n.feedback-error {\n color: #F04438;\n font-size: 14px;\n font-weight: 400;\n margin-top: 4px;\n text-transform: capitalize;\n}\n\n.feedback-form-error {\n color: #F04438;\n font-size: 14px;\n margin-top: 12px;\n padding: 8px 12px;\n background: #FEE2E2;\n border: 1px solid #FECACA;\n border-radius: 6px;\n}\n\n.feedback-modal.theme-dark .feedback-form-error {\n background: #7F1D1D;\n border-color: #991B1B;\n color: #FCA5A5;\n}\n\n.feedback-form-group.error input,\n.feedback-form-group.error textarea {\n border-color: #FDA29B;\n}\n\n.feedback-form-group.error input:focus,\n.feedback-form-group.error textarea:focus {\n border-color: #FDA29B;\n box-shadow: 0 0 0 1px rgba(16, 24, 40, 0.05), 0 0 0 4px rgba(253, 162, 155, 0.3);\n}\n\n.feedback-success-notification {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 1000001;\n background: white;\n border: 1px solid #D1FAE5;\n border-radius: 8px;\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n animation: slideInRight 0.3s ease-out;\n}\n\n.feedback-success-content {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 12px;\n}\n\n.feedback-success-content span {\n color: #059669;\n font-weight: 500;\n font-size: 14px;\n}\n\n.feedback-success-close {\n background: none;\n border: none;\n color: #6B7280;\n cursor: pointer;\n font-size: 18px;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s ease;\n}\n\n.feedback-success-close:hover {\n color: #374151;\n}\n\n.feedback-success-close:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n@keyframes slideInRight {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.feedback-modal {\n animation: fadeIn 0.2s ease-out;\n}\n\n.feedback-modal-content {\n animation: slideInUp 0.3s ease-out;\n}\n\n@keyframes slideInUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@media (max-width: 640px) {\n .feedback-modal {\n padding: 8px;\n }\n \n .feedback-modal-content {\n min-width: 280px;\n max-width: 100%;\n max-height: 95vh;\n }\n \n .feedback-form {\n padding: 16px;\n }\n \n .feedback-modal-header {\n padding: 16px;\n }\n \n .feedback-modal-header h3 {\n font-size: 15px;\n }\n \n .feedback-form-actions {\n flex-direction: column;\n gap: 8px;\n }\n \n .feedback-btn {\n width: 100%;\n height: 40px;\n }\n \n .feedback-widget-button {\n bottom: 16px;\n right: 16px;\n }\n \n .feedback-widget-button.position-bottom-left {\n left: 16px;\n }\n \n .feedback-trigger-btn {\n padding: 10px 16px;\n font-size: 13px;\n }\n \n .feedback-success-notification {\n top: 8px;\n right: 8px;\n left: 8px;\n max-width: none;\n }\n\n .feedback-form-group input {\n height: 40px;\n padding: 2px 12px;\n }\n \n .feedback-form-group textarea {\n min-height: 80px;\n padding: 2px 12px;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .feedback-trigger-btn,\n .feedback-btn,\n .feedback-modal,\n .feedback-modal-content,\n .feedback-success-notification,\n .feedback-loading {\n transition: none;\n animation: none;\n }\n \n .feedback-trigger-btn:hover {\n transform: none;\n }\n}\n\n@media print {\n .feedback-widget,\n .feedback-modal,\n .feedback-success-notification {\n display: none !important;\n }\n}\n",document.head.appendChild(e),console.log("CSS injected, style element created:",!!document.querySelector("#feedback-sdk-styles"))}}function w(){if("undefined"!=typeof window&&window.FeedbackSDKConfig){k();const e={...window.FeedbackSDKConfig};e.userContext||(e.userContext=function(){if("undefined"==typeof window)return null;if(window.FeedbackSDKUserContext)return window.FeedbackSDKUserContext;const e=[()=>window.auth0?.user,()=>window.firebase?.auth()?.currentUser,()=>window.amplify?.Auth?.currentAuthenticatedUser(),()=>window.currentUser,()=>window.user,()=>window.userData,()=>window.app?.user,()=>window.store?.getState?.()?.user,()=>window.App?.currentUser];for(const t of e)try{const e=t();if(e){const n=g.extractUserContextFromAuth(e);if(n&&(n.user_id||n.email))return console.log("[FeedbackSDK] Auto-detected user context from",t.name||"unknown source"),n}}catch(e){continue}try{const e=localStorage.getItem("auth")||localStorage.getItem("user")||localStorage.getItem("session");if(e){const t=JSON.parse(e),n=g.extractUserContextFromAuth(t);if(n&&(n.user_id||n.email))return console.log("[FeedbackSDK] Auto-detected user context from localStorage"),n}}catch(e){}return console.warn("[FeedbackSDK] No user context found. Widget initialization may require manual user context setting."),null}());const t=new g(e);t.init().then(n=>{if(window.FeedbackSDK.instance=t,window.FeedbackSDKConfig.autoCreate){(Array.isArray(window.FeedbackSDKConfig.autoCreate)?window.FeedbackSDKConfig.autoCreate:[window.FeedbackSDKConfig.autoCreate]).forEach(e=>{try{t.createWidget(e.type||"button",e).mount(e.container)}catch(e){console.error("[FeedbackSDK] Failed to create widget:",e)}})}if("undefined"!=typeof CustomEvent){const i=new CustomEvent("FeedbackSDKReady",{detail:{sdk:t,config:e,initData:n}});window.dispatchEvent(i)}console.log("[FeedbackSDK] Successfully initialized with session:",n.sessionToken?"Yes":"No")}).catch(t=>{if(console.error("[FeedbackSDK] Auto-initialization failed:",t),"undefined"!=typeof CustomEvent){const n=new CustomEvent("FeedbackSDKError",{detail:{error:t,config:e,phase:"initialization"}});window.dispatchEvent(n)}})}}const x={FeedbackSDK:g,BaseWidget:h,ButtonWidget:f,TabWidget:m,InlineWidget:p,WidgetFactory:b,EventBus:a,APIService:r,SDKError:t,APIError:n,WidgetError:i,ConfigError:o,ValidationError:s,helpers:u,create:e=>(k(),new g(e)),version:"1.0.0",instance:null,isReady:()=>Boolean(x.instance),getInstance:()=>x.instance,setUserContext:e=>{x.instance?x.instance.setUserContext(e):"undefined"!=typeof window&&(window.FeedbackSDKUserContext=e)},initWithUser:async(e,t)=>{k();const n={...e,userContext:t},i=new g(n);return await i.init(),"undefined"!=typeof window&&(window.FeedbackSDK.instance=i),i},onReady:e=>{"undefined"!=typeof window&&(x.isReady()?e(x.instance):window.addEventListener("FeedbackSDKReady",t=>{e(t.detail.sdk,t.detail)},{once:!0}))},onError:e=>{"undefined"!=typeof window&&window.addEventListener("FeedbackSDKError",t=>{e(t.detail.error,t.detail)})},extractUserContext:g.extractUserContextFromAuth};"undefined"!=typeof window&&(window.FeedbackSDK=x,"undefined"!=typeof document&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",w):setTimeout(w,0))),e.APIError=n,e.APIService=r,e.BaseWidget=h,e.ButtonWidget=f,e.ConfigError=o,e.EventBus=a,e.FeedbackSDK=g,e.InlineWidget=p,e.SDKError=t,e.TabWidget=m,e.ValidationError=s,e.WidgetError=i,e.WidgetFactory=b,e.default=x,e.helpers=u,Object.defineProperty(e,"__esModule",{value:!0})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).FeedbackSDK={})}(this,function(e){"use strict";class t extends Error{constructor(e,n){super(e),this.name="SDKError",this.cause=n,Error.captureStackTrace&&Error.captureStackTrace(this,t)}}class n extends Error{constructor(e,t,i){super(t),this.name="APIError",this.status=e,this.response=i,Error.captureStackTrace&&Error.captureStackTrace(this,n)}isNetworkError(){return 0===this.status}isClientError(){return this.status>=400&&this.status<500}isServerError(){return this.status>=500&&this.status<600}}class i extends Error{constructor(e,t,n){super(e),this.name="WidgetError",this.widgetType=t,this.widgetId=n,Error.captureStackTrace&&Error.captureStackTrace(this,i)}}class o extends Error{constructor(e,t){super(e),this.name="ConfigError",this.configKey=t,Error.captureStackTrace&&Error.captureStackTrace(this,o)}}class s extends Error{constructor(e,t,n){super(e),this.name="ValidationError",this.field=t,this.value=n,Error.captureStackTrace&&Error.captureStackTrace(this,s)}}class r{constructor(e={}){this.workspace=e.workspace,this.sessionToken=null,this.sessionExpiry=null,this.userContext=e.userContext||null,e.apiUrl?this.baseURL=e.apiUrl:this.workspace?this.baseURL=`https://${this.workspace}.api.staging.product7.io/api/v1`:this.baseURL="https://api.staging.product7.io/api/v1",this._loadStoredSession()}async init(e=null){if(e&&(this.userContext=e),this.isSessionValid())return{sessionToken:this.sessionToken};if(!this.userContext||!this.workspace){const e=`Missing ${this.workspace?"user context":"workspace"} for initialization`;throw new n(400,e)}const t={workspace:this.workspace,user:this.userContext};try{const e=await this._makeRequest("/widget/init",{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}});return this.sessionToken=e.session_token,this.sessionExpiry=new Date(Date.now()+1e3*e.expires_in),this._storeSession(),{sessionToken:this.sessionToken,config:e.config||{},expiresIn:e.expires_in}}catch(e){throw new n(e.status||500,`Failed to initialize widget: ${e.message}`,e.response)}}async submitFeedback(e){if(this.isSessionValid()||await this.init(),!this.sessionToken)throw new n(401,"No valid session token available");const t={board:e.board_id||e.board||e.boardId,title:e.title,content:e.content,attachments:e.attachments||[]};try{return await this._makeRequest("/widget/feedback",{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.sessionToken}`}})}catch(t){if(401===t.status)return this.sessionToken=null,this.sessionExpiry=null,await this.init(),this.submitFeedback(e);throw new n(t.status||500,`Failed to submit feedback: ${t.message}`,t.response)}}isSessionValid(){return this.sessionToken&&this.sessionExpiry&&new Date<this.sessionExpiry}setUserContext(e){this.userContext=e,"undefined"!=typeof localStorage&&localStorage.setItem("feedbackSDK_userContext",JSON.stringify(e))}getUserContext(){return this.userContext}clearSession(){this.sessionToken=null,this.sessionExpiry=null,"undefined"!=typeof localStorage&&(localStorage.removeItem("feedbackSDK_session"),localStorage.removeItem("feedbackSDK_userContext"))}_storeSession(){if("undefined"!=typeof localStorage)try{const e={token:this.sessionToken,expiry:this.sessionExpiry.toISOString(),workspace:this.workspace};localStorage.setItem("feedbackSDK_session",JSON.stringify(e))}catch(e){}}_loadStoredSession(){if("undefined"==typeof localStorage)return!1;try{const e=localStorage.getItem("feedbackSDK_session");if(!e)return!1;const t=JSON.parse(e);return this.sessionToken=t.token,this.sessionExpiry=new Date(t.expiry),this.isSessionValid()}catch(e){return!1}}async _makeRequest(e,t={}){const i=`${this.baseURL}${e}`;try{const e=await fetch(i,t);if(!e.ok){let t=`HTTP ${e.status}`,i=null;try{i=await e.json(),t=i.message||i.error||t}catch(n){t=await e.text()||t}throw new n(e.status,t,i)}const o=e.headers.get("content-type");return o&&o.includes("application/json")?await e.json():await e.text()}catch(e){if(e instanceof n)throw e;throw new n(0,e.message,null)}}}class a{constructor(){this.events=new Map}on(e,t){return this.events.has(e)||this.events.set(e,[]),this.events.get(e).push(t),()=>this.off(e,t)}off(e,t){const n=this.events.get(e);if(n){const e=n.indexOf(t);e>-1&&n.splice(e,1)}}emit(e,t){const n=this.events.get(e);n&&n.forEach(e=>{try{e(t)}catch(e){console.error("[FeedbackSDK] Event callback error:",e)}})}once(e,t){const n=this.on(e,e=>{t(e),n()});return n}clear(){this.events.clear()}getListenerCount(e){const t=this.events.get(e);return t?t.length:0}}function d(e="feedback"){return`${e}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function c(e,t){const n={...e};for(const i in t)t.hasOwnProperty(i)&&(t[i]&&"object"==typeof t[i]&&!Array.isArray(t[i])?n[i]=c(e[i]||{},t[i]):n[i]=t[i]);return n}function l(){return"undefined"!=typeof window&&"undefined"!=typeof document}var u=Object.freeze({__proto__:null,debounce:function(e,t){let n;return function(...i){clearTimeout(n),n=setTimeout(()=>{clearTimeout(n),e(...i)},t)}},deepMerge:c,delay:function(e){return new Promise(t=>setTimeout(t,e))},escapeRegex:function(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")},formatFileSize:function(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,t)).toFixed(2))+" "+["Bytes","KB","MB","GB"][t]},generateId:d,getBrowserInfo:function(){return{userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language||navigator.userLanguage,cookieEnabled:navigator.cookieEnabled,screenResolution:`${screen.width}x${screen.height}`,windowSize:`${window.innerWidth}x${window.innerHeight}`,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone}},getCSSProperty:function(e,t,n=""){if(!e||!t)return n;try{return window.getComputedStyle(e).getPropertyValue(t)||n}catch(e){return n}},getCurrentTimestamp:function(){return(new Date).toISOString()},getNestedProperty:function(e,t,n=void 0){if(!e||!t)return n;const i=t.split(".");let o=e;for(const e of i){if(null==o||!(e in o))return n;o=o[e]}return o},isBrowser:l,isInViewport:function(e){if(!e)return!1;const t=e.getBoundingClientRect();return t.top>=0&&t.left>=0&&t.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&t.right<=(window.innerWidth||document.documentElement.clientWidth)},isMobile:function(){return!!l()&&(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||window.innerWidth<=768)},isValidEmail:function(e){return!(!e||"string"!=typeof e)&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim())},safeJsonParse:function(e,t=null){try{return JSON.parse(e)}catch(e){return t}},sanitizeHTML:function(e){if(!e||"string"!=typeof e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML},scrollToElement:function(e,t={}){if(!e)return;e.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest",...t})},setNestedProperty:function(e,t,n){if(!e||!t)return e;const i=t.split("."),o=i.pop();let s=e;for(const e of i)e in s&&"object"==typeof s[e]||(s[e]={}),s=s[e];return s[o]=n,e},throttle:function(e,t){let n,i;return function(...o){i?(clearTimeout(n),n=setTimeout(()=>{Date.now()-i>=t&&(e(...o),i=Date.now())},t-(Date.now()-i))):(e(...o),i=Date.now())}},validateConfig:function(e,t=[]){const n=[];for(const i of t)e[i]||n.push(i);if(n.length>0)throw new Error(`Missing required configuration: ${n.join(", ")}`);return!0}});class h{constructor(e={}){this.id=e.id,this.sdk=e.sdk,this.apiService=e.apiService,this.type=e.type||"base",this.options={container:null,position:this.sdk.config.position,theme:this.sdk.config.theme,boardId:this.sdk.config.boardId,autoShow:!1,customStyles:{},...e},this.element=null,this.modalElement=null,this.mounted=!1,this.destroyed=!1,this.state={isOpen:!1,isSubmitting:!1,title:"",content:"",email:"",attachments:[],errors:{}},this._bindMethods()}mount(e){return this.mounted||this.destroyed||("string"==typeof e&&(e=document.querySelector(e)),e||(e=document.body),this.container=e,this.element=this._render(),this.container.appendChild(this.element),this.mounted=!0,this._attachEvents(),this.onMount(),this.options.autoShow&&this.show(),this.sdk.eventBus.emit("widget:mounted",{widget:this})),this}show(){return this.element&&(this.element.style.display="block"),this}hide(){return this.element&&(this.element.style.display="none"),this}openModal(){this.state.isOpen=!0,this._renderModal()}closeModal(){this.state.isOpen=!1,this.modalElement&&(this.modalElement.remove(),this.modalElement=null),this._resetForm()}async submitFeedback(){if(!this.state.isSubmitting)try{this.state.isSubmitting=!0,this._updateSubmitButton();const e={title:this.state.title||"Feedback",content:this.state.content,email:this.state.email,board_id:this.options.boardId,attachments:this.state.attachments};if(!this.state.content.trim())return void this._showError("Please enter your feedback message.");const t=await this.apiService.submitFeedback(e);this._showSuccessMessage(),this.closeModal(),this.sdk.eventBus.emit("feedback:submitted",{widget:this,feedback:t})}catch(e){this._showError("Failed to submit feedback. Please try again."),this.sdk.eventBus.emit("feedback:error",{widget:this,error:e})}finally{this.state.isSubmitting=!1,this._updateSubmitButton()}}handleConfigUpdate(e){this.options.theme=e.theme,this.element&&this._updateTheme()}destroy(){this.destroyed||(this.onDestroy(),this.closeModal(),this.element&&this.element.parentNode&&this.element.parentNode.removeChild(this.element),this.destroyed=!0,this.mounted=!1,this.sdk.eventBus.emit("widget:destroyed",{widget:this}))}onMount(){}onDestroy(){}_render(){throw new Error("_render() must be implemented by concrete widget")}_attachEvents(){}_bindMethods(){this.openModal=this.openModal.bind(this),this.closeModal=this.closeModal.bind(this),this.submitFeedback=this.submitFeedback.bind(this)}_renderModal(){if(this.modalElement)return;this.modalElement=document.createElement("div"),this.modalElement.className=`feedback-modal theme-${this.options.theme}`,this.modalElement.innerHTML=this._getModalHTML(),document.body.appendChild(this.modalElement),this._attachModalEvents();const e=this.modalElement.querySelector("input, textarea");e&&setTimeout(()=>e.focus(),100)}_getModalHTML(){return`\n <div class="feedback-modal-overlay">\n <div class="feedback-modal-content">\n <div class="feedback-modal-header">\n <h3>Send Feedback</h3>\n <button class="feedback-modal-close" type="button">&times;</button>\n </div>\n <form class="feedback-form">\n <div class="feedback-form-group">\n <label for="feedback-title-${this.id}">Title</label>\n <input \n type="text" \n id="feedback-title-${this.id}" \n name="title" \n placeholder="Brief description of your feedback"\n value="${this.state.title}"\n />\n </div>\n <div class="feedback-form-group">\n <label for="feedback-content-${this.id}">Message *</label>\n <textarea \n id="feedback-content-${this.id}" \n name="content" \n placeholder="Tell us more about your feedback..."\n required\n >${this.state.content}</textarea>\n </div>\n <div class="feedback-form-actions">\n <button type="button" class="feedback-btn feedback-btn-cancel">Cancel</button>\n <button type="submit" class="feedback-btn feedback-btn-submit">\n ${this.state.isSubmitting?"Sending...":"Send Feedback"}\n </button>\n </div>\n <div class="feedback-error" style="display: none;"></div>\n </form>\n </div>\n </div>\n `}_attachModalEvents(){const e=this.modalElement;e.querySelector(".feedback-modal-close").addEventListener("click",this.closeModal),e.querySelector(".feedback-btn-cancel").addEventListener("click",this.closeModal),e.querySelector(".feedback-modal-overlay").addEventListener("click",e=>{e.target===e.currentTarget&&this.closeModal()});e.querySelector(".feedback-form").addEventListener("submit",e=>{e.preventDefault(),this.submitFeedback()}),e.querySelector('input[name="title"]').addEventListener("input",e=>{this.state.title=e.target.value}),e.querySelector('textarea[name="content"]').addEventListener("input",e=>{this.state.content=e.target.value})}_updateSubmitButton(){if(this.modalElement){const e=this.modalElement.querySelector(".feedback-btn-submit");e&&(e.textContent=this.state.isSubmitting?"Sending...":"Send Feedback",e.disabled=this.state.isSubmitting)}}_showError(e){if(this.modalElement){const t=this.modalElement.querySelector(".feedback-error");t.textContent=e,t.style.display="block",setTimeout(()=>{t&&(t.style.display="none")},5e3)}}_showSuccessMessage(){const e=document.createElement("div");e.className="feedback-success-notification",e.innerHTML='\n <div class="feedback-success-content">\n <span>✓ Feedback submitted successfully!</span>\n <button class="feedback-success-close">&times;</button>\n </div>\n ',document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.parentNode.removeChild(e)},3e3),e.querySelector(".feedback-success-close").addEventListener("click",()=>{e.parentNode&&e.parentNode.removeChild(e)})}_resetForm(){this.state.title="",this.state.content="",this.state.email="",this.state.errors={}}_updateTheme(){this.element&&(this.element.className=this.element.className.replace(/theme-\w+/,`theme-${this.options.theme}`)),this.modalElement&&(this.modalElement.className=this.modalElement.className.replace(/theme-\w+/,`theme-${this.options.theme}`))}}class f extends h{constructor(e){super({...e,type:"button"})}_render(){const e=document.createElement("div");return e.className=`feedback-widget feedback-widget-button theme-${this.options.theme} position-${this.options.position}`,e.innerHTML='\n <button class="feedback-trigger-btn" type="button">\n <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">\n <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>\n <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>\n </svg>\n Feedback\n </button>\n ',this.options.customStyles&&Object.assign(e.style,this.options.customStyles),e}_attachEvents(){const e=this.element.querySelector(".feedback-trigger-btn");e.addEventListener("click",this.openModal),e.addEventListener("mouseenter",()=>{this.state.isSubmitting||(e.style.transform="translateY(-2px)")}),e.addEventListener("mouseleave",()=>{e.style.transform="translateY(0)"})}updateText(e){const t=this.element?.querySelector(".feedback-trigger-btn");if(t){const n=t.childNodes[t.childNodes.length-1];n&&n.nodeType===Node.TEXT_NODE&&(n.textContent=e)}}updatePosition(e){this.options.position=e,this.element&&(this.element.className=this.element.className.replace(/position-\w+-\w+/,`position-${e}`))}}class p extends h{constructor(e){super({...e,type:"inline"})}_render(){const e=document.createElement("div");return e.className=`feedback-widget feedback-widget-inline theme-${this.options.theme}`,e.innerHTML=`\n <div class="feedback-inline-content">\n <h3>Send us your feedback</h3>\n <form class="feedback-inline-form">\n <div class="feedback-form-group">\n <input \n type="text" \n name="title" \n placeholder="Title (optional)"\n value="${this.state.title}"\n />\n </div>\n <div class="feedback-form-group">\n <textarea \n name="content" \n placeholder="Your feedback..."\n required\n >${this.state.content}</textarea>\n </div>\n <div class="feedback-form-group">\n <input \n type="email" \n name="email" \n placeholder="Email (optional)"\n value="${this.state.email}"\n />\n </div>\n <button type="submit" class="feedback-btn feedback-btn-submit">\n Send Feedback\n </button>\n <div class="feedback-error" style="display: none;"></div>\n </form>\n </div>\n `,this.options.customStyles&&Object.assign(e.style,this.options.customStyles),e}_attachEvents(){const e=this.element.querySelector(".feedback-inline-form");e.addEventListener("submit",e=>{e.preventDefault(),this.submitFeedback()}),e.querySelector('input[name="title"]').addEventListener("input",e=>{this.state.title=e.target.value}),e.querySelector('textarea[name="content"]').addEventListener("input",e=>{this.state.content=e.target.value}),e.querySelector('input[name="email"]').addEventListener("input",e=>{this.state.email=e.target.value})}openModal(){const e=this.element.querySelector('textarea[name="content"]');e&&e.focus()}closeModal(){}_showSuccessMessage(){const e=this.element.querySelector(".feedback-inline-content"),t=e.innerHTML;e.innerHTML='\n <div class="feedback-success">\n <div class="feedback-success-icon">✓</div>\n <h3>Thank you!</h3>\n <p>Your feedback has been submitted successfully.</p>\n <button class="feedback-btn feedback-btn-reset">Send Another</button>\n </div>\n ';e.querySelector(".feedback-btn-reset").addEventListener("click",()=>{e.innerHTML=t,this._attachEvents(),this._resetForm()})}_showError(e){const t=this.element.querySelector(".feedback-error");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t&&(t.style.display="none")},5e3))}_updateSubmitButton(){const e=this.element.querySelector(".feedback-btn-submit");e&&(e.textContent=this.state.isSubmitting?"Sending...":"Send Feedback",e.disabled=this.state.isSubmitting)}updateTitle(e){const t=this.element?.querySelector("h3");t&&(t.textContent=e)}setPlaceholder(e,t){const n=this.element?.querySelector(`[name="${e}"]`);n&&(n.placeholder=t)}}class m extends h{constructor(e){super({...e,type:"tab"})}_render(){const e=document.createElement("div");return e.className=`feedback-widget feedback-widget-tab theme-${this.options.theme} position-${this.options.position}`,e.innerHTML='\n <div class="feedback-tab-trigger">\n <span class="feedback-tab-text">Feedback</span>\n </div>\n ',this.options.customStyles&&Object.assign(e.style,this.options.customStyles),e}_attachEvents(){const e=this.element.querySelector(".feedback-tab-trigger");e.addEventListener("click",this.openModal),e.addEventListener("mouseenter",()=>{this.state.isSubmitting||(e.style.transform=this._getHoverTransform())}),e.addEventListener("mouseleave",()=>{e.style.transform="none"})}_getHoverTransform(){const e=this.options.position;return e.includes("right")?"translateX(-5px)":e.includes("left")?"translateX(5px)":"none"}updateText(e){const t=this.element?.querySelector(".feedback-tab-text");t&&(t.textContent=e)}updatePosition(e){this.options.position=e,this.element&&(this.element.className=this.element.className.replace(/position-\w+-\w+/,`position-${e}`))}}class b{static widgets=new Map([["button",f],["tab",m],["inline",p]]);static register(e,n){if("string"!=typeof e||!e.trim())throw new t("Widget type must be a non-empty string");if("function"!=typeof n)throw new t("Widget class must be a constructor function");this.widgets.set(e,n)}static create(e,n={}){const i=this.widgets.get(e);if(!i){const n=Array.from(this.widgets.keys()).join(", ");throw new t(`Unknown widget type: ${e}. Available types: ${n}`)}try{return new i(n)}catch(n){throw new t(`Failed to create widget of type '${e}': ${n.message}`,n)}}static getAvailableTypes(){return Array.from(this.widgets.keys())}static isTypeRegistered(e){return this.widgets.has(e)}static unregister(e){return this.widgets.delete(e)}static clear(){this.widgets.clear()}static getWidgetClass(e){return this.widgets.get(e)}}class g{constructor(e={}){this.config=this._validateAndMergeConfig(e),this.initialized=!1,this.widgets=new Map,this.eventBus=new a,this.apiService=new r({apiUrl:this.config.apiUrl,workspace:this.config.workspace,userContext:this.config.userContext}),this._bindMethods()}async init(){if(this.initialized)return{alreadyInitialized:!0};try{const e=await this.apiService.init(this.config.userContext);return e.config&&(this.config=c(this.config,e.config)),this.initialized=!0,this.eventBus.emit("sdk:initialized",{config:this.config,sessionToken:e.sessionToken}),{initialized:!0,config:e.config||{},sessionToken:e.sessionToken,expiresIn:e.expiresIn}}catch(e){throw this.eventBus.emit("sdk:error",{error:e}),new t(`Failed to initialize SDK: ${e.message}`,e)}}createWidget(e="button",n={}){if(!this.initialized)throw new t("SDK must be initialized before creating widgets. Call init() first.");const i=d("widget"),o={id:i,sdk:this,apiService:this.apiService,...this.config,...n};try{const t=b.create(e,o);return this.widgets.set(i,t),this.eventBus.emit("widget:created",{widget:t,type:e}),t}catch(e){throw new t(`Failed to create widget: ${e.message}`,e)}}getWidget(e){return this.widgets.get(e)}getAllWidgets(){return Array.from(this.widgets.values())}destroyWidget(e){const t=this.widgets.get(e);return!!t&&(t.destroy(),this.widgets.delete(e),this.eventBus.emit("widget:removed",{widgetId:e}),!0)}destroyAllWidgets(){for(const e of this.widgets.values())e.destroy();this.widgets.clear(),this.eventBus.emit("widgets:cleared")}updateConfig(e){const t={...this.config};this.config=this._validateAndMergeConfig(e,this.config);for(const e of this.widgets.values())e.handleConfigUpdate(this.config);this.eventBus.emit("config:updated",{oldConfig:t,newConfig:this.config})}setUserContext(e){this.config.userContext=e,this.apiService&&this.apiService.setUserContext(e),this.eventBus.emit("user:updated",{userContext:e})}getUserContext(){return this.config.userContext||(this.apiService?this.apiService.getUserContext():null)}async reinitialize(e=null){return this.apiService.clearSession(),this.initialized=!1,e&&this.setUserContext(e),this.init()}on(e,t){return this.eventBus.on(e,t),this}off(e,t){return this.eventBus.off(e,t),this}once(e,t){return this.eventBus.once(e,t),this}emit(e,t){return this.eventBus.emit(e,t),this}destroy(){this.destroyAllWidgets(),this.eventBus.removeAllListeners(),this.apiService.clearSession(),this.initialized=!1,this.eventBus.emit("sdk:destroyed")}_validateAndMergeConfig(e,t={}){const n=c(c({apiUrl:null,workspace:null,userContext:null,position:"bottom-right",theme:"light",boardId:"general",autoShow:!0,debug:!1},t),e);if(!n.workspace)throw new o("Missing required configuration: workspace");return n.userContext&&this._validateUserContext(n.userContext),n}_validateUserContext(e){if(!e.user_id&&!e.email)throw new o("User context must include at least user_id or email");const t={user_id:"string",email:"string",name:"string",custom_fields:"object",company:"object"};for(const[n,i]of Object.entries(t))if(e[n]&&typeof e[n]!==i)throw new o(`User context field '${n}' must be of type '${i}'`)}_bindMethods(){this.createWidget=this.createWidget.bind(this),this.destroyWidget=this.destroyWidget.bind(this),this.updateConfig=this.updateConfig.bind(this)}static create(e){return new g(e)}static async createAndInit(e){const t=new g(e);return await t.init(),t}static extractUserContextFromAuth(e){return e?{user_id:e.sub||e.id||e.user_id,email:e.email,name:e.name||e.display_name||e.full_name,custom_fields:{role:e.role,plan:e.plan||e.subscription?.plan,...e.custom_fields||{}},company:e.company||e.organization?{id:e.company?.id||e.organization?.id,name:e.company?.name||e.organization?.name,monthly_spend:e.company?.monthly_spend}:void 0}:null}}function k(){if("undefined"!=typeof document&&!document.querySelector("#feedback-sdk-styles")){const e=document.createElement("style");e.id="feedback-sdk-styles",e.textContent="\n.feedback-widget {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 14px;\n line-height: 1.4;\n z-index: 999999;\n box-sizing: border-box;\n}\n\n.feedback-widget *,\n.feedback-widget *::before,\n.feedback-widget *::after {\n box-sizing: border-box;\n}\n\n.feedback-widget-button {\n position: fixed;\n z-index: 999999;\n}\n\n.feedback-widget-button.position-bottom-right {\n bottom: 20px;\n right: 20px;\n}\n\n.feedback-widget-button.position-bottom-left {\n bottom: 20px;\n left: 20px;\n}\n\n.feedback-widget-button.position-top-right {\n top: 20px;\n right: 20px;\n}\n\n.feedback-widget-button.position-top-left {\n top: 20px;\n left: 20px;\n}\n\n.feedback-trigger-btn {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 44px;\n overflow: hidden;\n border-radius: 0.5rem;\n border: none;\n padding: 10px 16px;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.3s duration;\n color: white;\n background: #155EEF;\n box-shadow: 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n.feedback-trigger-btn:hover:not(:disabled) {\n background: #004EEB;\n box-shadow: 0 1px 2px 0 rgba(16, 24, 40, 0.1);\n}\n\n.feedback-trigger-btn:disabled {\n opacity: 0.7;\n cursor: not-allowed;\n}\n\n.feedback-trigger-btn:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n.feedback-modal {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 1000000;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n}\n\n.feedback-modal-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.feedback-modal-content {\n background: white;\n border-radius: 8px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n min-width: 460px;\n max-width: 500px;\n width: 100%;\n padding: 16px;\n max-height: 85vh;\n overflow-y: hidden;\n position: relative;\n}\n\n.feedback-modal.theme-dark .feedback-modal-content {\n background: #1F2937;\n color: white;\n}\n\n.feedback-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid #D1D5DB;\n flex-shrink: 0;\n}\n\n.feedback-modal.theme-dark .feedback-modal-header {\n border-bottom-color: #374151;\n}\n\n.feedback-modal-header h3 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n}\n\n.feedback-modal-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #6B7280;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s ease;\n}\n\n.feedback-modal-close:hover {\n color: #374151;\n}\n\n.feedback-modal-close:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n.feedback-modal.theme-dark .feedback-modal-close {\n color: #9CA3AF;\n}\n\n.feedback-modal.theme-dark .feedback-modal-close:hover {\n color: #D1D5DB;\n}\n\n.feedback-form {\n padding: 16px;\n}\n\n.feedback-form-group {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 12px;\n}\n\n.feedback-form-group:last-child {\n margin-bottom: 0;\n}\n\n.feedback-form-group label {\n font-size: 14px;\n font-weight: 500;\n line-height: 1.25;\n color: #374151;\n}\n\n.feedback-modal.theme-dark .feedback-form-group label {\n color: #D1D5DB;\n}\n\n.feedback-form-group input {\n height: 40px;\n width: 100%;\n border-radius: 6px;\n border: 1px solid #D1D5DB;\n padding: 2px 12px;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.25;\n color: #1F2937;\n font-family: inherit;\n outline: none;\n transition: all 0.2s ease;\n}\n\n.feedback-form-group input::placeholder {\n font-size: 14px;\n color: #6B7280;\n}\n\n.feedback-form-group input:focus {\n border-color: #84ADFF;\n box-shadow: 0 0 0 1px rgba(16, 24, 40, 0.05), 0 0 0 3px rgba(41, 112, 255, 0.2);\n}\n\n.feedback-form-group input:focus-visible {\n outline: none;\n}\n\n.feedback-form-group textarea {\n min-height: 100px;\n width: 100%;\n resize: both;\n border-radius: 6px;\n border: 1px solid #D1D5DB;\n padding: 2px 12px;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.25;\n color: #1F2937;\n font-family: inherit;\n outline: none;\n transition: all 0.2s ease;\n}\n\n.feedback-form-group textarea::placeholder {\n font-size: 14px;\n color: #6B7280;\n}\n\n.feedback-form-group textarea:focus {\n border-color: #84ADFF;\n box-shadow: 0 0 0 1px rgba(16, 24, 40, 0.05), 0 0 0 3px rgba(41, 112, 255, 0.2);\n}\n\n.feedback-form-group textarea:focus-visible {\n outline: none;\n}\n\n.feedback-modal.theme-dark .feedback-form-group input,\n.feedback-modal.theme-dark .feedback-form-group textarea {\n background: #374151;\n border-color: #4B5563;\n color: white;\n}\n\n.feedback-btn {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n border-radius: 6px;\n border: none;\n height: 40px;\n padding: 2px 16px;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.feedback-btn:disabled {\n opacity: 0.7;\n cursor: not-allowed;\n}\n\n.feedback-btn:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n.feedback-btn-submit {\n background: #155EEF;\n color: white;\n}\n\n.feedback-btn-submit:hover:not(:disabled) {\n background: #004EEB;\n}\n\n.feedback-btn-cancel {\n background: transparent;\n color: #6B7280;\n border: 1px solid #D1D5DB;\n}\n\n.feedback-btn-cancel:hover:not(:disabled) {\n background: #F9FAFB;\n border-color: #9CA3AF;\n color: #374151;\n}\n\n.feedback-modal.theme-dark .feedback-btn-cancel {\n color: #D1D5DB;\n border-color: #4B5563;\n}\n\n.feedback-modal.theme-dark .feedback-btn-cancel:hover:not(:disabled) {\n background: #374151;\n}\n\n.feedback-form-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-top: 16px;\n padding-top: 4px;\n}\n\n.feedback-loading {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n mask: radial-gradient(transparent 62%, white 65%);\n -webkit-mask: radial-gradient(transparent 62%, white 65%);\n animation: feedbackRotate 0.7s linear infinite;\n}\n\n.feedback-loading-white {\n background: conic-gradient(from 0deg, rgba(255, 255, 255, 0.5), white);\n}\n\n.feedback-loading-blue {\n background: conic-gradient(from 0deg, #004EEB, #eff4ff);\n}\n\n@keyframes feedbackRotate {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n.feedback-error {\n color: #F04438;\n font-size: 14px;\n font-weight: 400;\n margin-top: 4px;\n text-transform: capitalize;\n}\n\n.feedback-form-error {\n color: #F04438;\n font-size: 14px;\n margin-top: 12px;\n padding: 8px 12px;\n background: #FEE2E2;\n border: 1px solid #FECACA;\n border-radius: 6px;\n}\n\n.feedback-modal.theme-dark .feedback-form-error {\n background: #7F1D1D;\n border-color: #991B1B;\n color: #FCA5A5;\n}\n\n.feedback-form-group.error input,\n.feedback-form-group.error textarea {\n border-color: #FDA29B;\n}\n\n.feedback-form-group.error input:focus,\n.feedback-form-group.error textarea:focus {\n border-color: #FDA29B;\n box-shadow: 0 0 0 1px rgba(16, 24, 40, 0.05), 0 0 0 4px rgba(253, 162, 155, 0.3);\n}\n\n.feedback-success-notification {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 1000001;\n background: white;\n border: 1px solid #D1FAE5;\n border-radius: 8px;\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n animation: slideInRight 0.3s ease-out;\n}\n\n.feedback-success-content {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 12px;\n}\n\n.feedback-success-content span {\n color: #059669;\n font-weight: 500;\n font-size: 14px;\n}\n\n.feedback-success-close {\n background: none;\n border: none;\n color: #6B7280;\n cursor: pointer;\n font-size: 18px;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s ease;\n}\n\n.feedback-success-close:hover {\n color: #374151;\n}\n\n.feedback-success-close:focus-visible {\n outline: 2px solid #155EEF;\n outline-offset: 2px;\n}\n\n@keyframes slideInRight {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.feedback-modal {\n animation: fadeIn 0.2s ease-out;\n}\n\n.feedback-modal-content {\n animation: slideInUp 0.3s ease-out;\n}\n\n@keyframes slideInUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@media (max-width: 640px) {\n .feedback-modal {\n padding: 8px;\n }\n \n .feedback-modal-content {\n min-width: 280px;\n max-width: 100%;\n max-height: 95vh;\n }\n \n .feedback-form {\n padding: 16px;\n }\n \n .feedback-modal-header {\n padding: 16px;\n }\n \n .feedback-modal-header h3 {\n font-size: 15px;\n }\n \n .feedback-form-actions {\n flex-direction: column;\n gap: 8px;\n }\n \n .feedback-btn {\n width: 100%;\n height: 40px;\n }\n \n .feedback-widget-button {\n bottom: 16px;\n right: 16px;\n }\n \n .feedback-widget-button.position-bottom-left {\n left: 16px;\n }\n \n .feedback-trigger-btn {\n padding: 10px 16px;\n font-size: 13px;\n }\n \n .feedback-success-notification {\n top: 8px;\n right: 8px;\n left: 8px;\n max-width: none;\n }\n\n .feedback-form-group input {\n height: 40px;\n padding: 2px 12px;\n }\n \n .feedback-form-group textarea {\n min-height: 80px;\n padding: 2px 12px;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .feedback-trigger-btn,\n .feedback-btn,\n .feedback-modal,\n .feedback-modal-content,\n .feedback-success-notification,\n .feedback-loading {\n transition: none;\n animation: none;\n }\n \n .feedback-trigger-btn:hover {\n transform: none;\n }\n}\n\n@media print {\n .feedback-widget,\n .feedback-modal,\n .feedback-success-notification {\n display: none !important;\n }\n}\n",document.head.appendChild(e)}}function w(){if("undefined"!=typeof window&&window.FeedbackSDKConfig){k();const e={...window.FeedbackSDKConfig},t=new g(e);t.init().then(n=>{if(window.FeedbackSDK.instance=t,window.FeedbackSDKConfig.autoCreate){(Array.isArray(window.FeedbackSDKConfig.autoCreate)?window.FeedbackSDKConfig.autoCreate:[window.FeedbackSDKConfig.autoCreate]).forEach(e=>{try{t.createWidget(e.type||"button",e).mount(e.container)}catch(e){console.error("[FeedbackSDK] Failed to create widget:",e)}})}if("undefined"!=typeof CustomEvent){const i=new CustomEvent("FeedbackSDKReady",{detail:{sdk:t,config:e,initData:n}});window.dispatchEvent(i)}}).catch(t=>{if(console.error("[FeedbackSDK] Auto-initialization failed:",t),"undefined"!=typeof CustomEvent){const n=new CustomEvent("FeedbackSDKError",{detail:{error:t,config:e,phase:"initialization"}});window.dispatchEvent(n)}})}}const x={FeedbackSDK:g,BaseWidget:h,ButtonWidget:f,TabWidget:m,InlineWidget:p,WidgetFactory:b,EventBus:a,APIService:r,SDKError:t,APIError:n,WidgetError:i,ConfigError:o,ValidationError:s,helpers:u,create:e=>(k(),new g(e)),version:"1.0.0",instance:null,isReady:()=>Boolean(x.instance),getInstance:()=>x.instance,setUserContext:e=>{x.instance?x.instance.setUserContext(e):"undefined"!=typeof window&&(window.FeedbackSDKUserContext=e)},initWithUser:async(e,t)=>{k();const n={...e,userContext:t},i=new g(n);return await i.init(),"undefined"!=typeof window&&(window.FeedbackSDK.instance=i),i},onReady:e=>{"undefined"!=typeof window&&(x.isReady()?e(x.instance):window.addEventListener("FeedbackSDKReady",t=>{e(t.detail.sdk,t.detail)},{once:!0}))},onError:e=>{"undefined"!=typeof window&&window.addEventListener("FeedbackSDKError",t=>{e(t.detail.error,t.detail)})},extractUserContext:g.extractUserContextFromAuth};"undefined"!=typeof window&&(window.FeedbackSDK=x,"undefined"!=typeof document&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",w):setTimeout(w,0))),e.APIError=n,e.APIService=r,e.BaseWidget=h,e.ButtonWidget=f,e.ConfigError=o,e.EventBus=a,e.FeedbackSDK=g,e.InlineWidget=p,e.SDKError=t,e.TabWidget=m,e.ValidationError=s,e.WidgetError=i,e.WidgetFactory=b,e.default=x,e.helpers=u,Object.defineProperty(e,"__esModule",{value:!0})});
2
2
  //# sourceMappingURL=feedback-sdk.min.js.map