@deeprocket/deep-service-desk-widget
Version:
Widget Vue.js para integração com o sistema Deep Service Desk - Controle de visibilidade por URLs - Compatível com Vue 2.7.16 e Vue 3 - Com botão flutuante automático
1,341 lines (1,163 loc) • 62.5 kB
JavaScript
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 262:
/***/ ((__unused_webpack_module, exports) => {
var __webpack_unused_export__;
__webpack_unused_export__ = ({ value: true });
// runtime helper for setting properties on components
// in a tree-shakable way
exports.A = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/publicPath */
/******/ (() => {
/******/ __webpack_require__.p = "";
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
FloatingTicketWidget: () => (/* reexport */ FloatingTicketWidget),
TicketWidget: () => (/* reexport */ TicketWidget),
"default": () => (/* binding */ entry_lib)
});
;// ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js
/* eslint-disable no-var */
// This file is imported into lib/wc client bundles.
if (typeof window !== 'undefined') {
var currentScript = window.document.currentScript
if (false) // removed by dead control flow
{ var getCurrentScript; }
var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
if (src) {
__webpack_require__.p = src[1] // eslint-disable-line
}
}
// Indicate to webpack that this file can be concatenated
/* harmony default export */ const setPublicPath = (null);
;// external {"commonjs":"vue","commonjs2":"vue","root":"Vue"}
const external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject = require("vue");
;// ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/TicketWidget.vue?vue&type=template&id=109adfc9
const _hoisted_1 = { class: "ticket-widget" }
const _hoisted_2 = { class: "popup-header" }
const _hoisted_3 = {
key: 0,
class: "uuid-config-section"
}
const _hoisted_4 = { class: "form-group" }
const _hoisted_5 = {
key: 0,
class: "error-message"
}
const _hoisted_6 = { class: "form-actions" }
const _hoisted_7 = { class: "form-group" }
const _hoisted_8 = { class: "form-group" }
const _hoisted_9 = {
key: 0,
class: "error-message"
}
const _hoisted_10 = { class: "form-actions" }
const _hoisted_11 = ["disabled"]
function render(_ctx, _cache, $props, $setup, $data, $options) {
return ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_1, [
($data.showPopup)
? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", {
key: 0,
class: "popup-overlay",
onClick: _cache[10] || (_cache[10] = (...args) => ($options.closePopup && $options.closePopup(...args)))
}, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", {
class: "popup-content",
onClick: _cache[9] || (_cache[9] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withModifiers)(() => {}, ["stop"]))
}, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_2, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", {
class: "close-btn",
onClick: _cache[0] || (_cache[0] = (...args) => ($options.closePopup && $options.closePopup(...args)))
}, "×")
]),
(!$options.computedClientUuid)
? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_3, [
_cache[12] || (_cache[12] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createStaticVNode)("<div class=\"content-header\"><h3>Precisa de ajuda?</h3><p>Fale com um de nossos especialistas</p></div><div class=\"config-header\"><span class=\"config-icon\">🔧</span><div class=\"config-details\"><div class=\"config-title\">Configuração Necessária</div><div class=\"config-subtitle\">Configure o UUID do cliente para continuar</div></div></div>", 2)),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_4, [
_cache[11] || (_cache[11] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("label", { for: "uuidInput" }, "UUID do Cliente *", -1)),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withDirectives)((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("input", {
id: "uuidInput",
"onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (($data.uuidInput) = $event)),
type: "text",
placeholder: "Digite o UUID do cliente (ex: 1c9eb621-b56c-43e4-b04b-b1a91b0a1809)",
class: "form-input uuid-input",
style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.inputFocusStyle),
onKeypress: _cache[2] || (_cache[2] = (...args) => ($options.handleUuidKeypress && $options.handleUuidKeypress(...args)))
}, null, 36), [
[external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.vModelText, $data.uuidInput]
])
]),
($data.uuidError)
? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_5, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.uuidError), 1))
: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_6, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", {
type: "button",
onClick: _cache[3] || (_cache[3] = (...args) => ($options.closePopup && $options.closePopup(...args))),
class: "btn btn-cancel"
}, " Cancelar "),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", {
type: "button",
onClick: _cache[4] || (_cache[4] = (...args) => ($options.setClientUuid && $options.setClientUuid(...args))),
class: "btn btn-submit",
style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.submitButtonStyle)
}, " ✅ Configurar UUID ", 4)
])
]))
: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true),
($options.computedClientUuid)
? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("form", {
key: 1,
onSubmit: _cache[8] || (_cache[8] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withModifiers)((...args) => ($options.submitTicket && $options.submitTicket(...args)), ["prevent"])),
class: "ticket-form"
}, [
_cache[15] || (_cache[15] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", { class: "content-header" }, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("h3", null, "Precisa de ajuda?"),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("p", null, "Fale com um de nossos especialistas")
], -1)),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_7, [
_cache[13] || (_cache[13] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("label", { for: "title" }, "Título *", -1)),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withDirectives)((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("input", {
id: "title",
"onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($data.ticket.title) = $event)),
type: "text",
required: "",
placeholder: "Digite o título do ticket",
class: "form-input",
style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.inputFocusStyle)
}, null, 4), [
[external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.vModelText, $data.ticket.title]
])
]),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_8, [
_cache[14] || (_cache[14] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("label", { for: "description" }, "Descrição *", -1)),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withDirectives)((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("textarea", {
id: "description",
"onUpdate:modelValue": _cache[6] || (_cache[6] = $event => (($data.ticket.description) = $event)),
required: "",
placeholder: "Descreva o problema ou solicitação",
class: "form-textarea",
style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.inputFocusStyle),
rows: "4"
}, null, 4), [
[external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.vModelText, $data.ticket.description]
])
]),
($data.errorMessage)
? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_9, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.errorMessage), 1))
: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_10, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", {
type: "button",
onClick: _cache[7] || (_cache[7] = (...args) => ($options.closePopup && $options.closePopup(...args))),
class: "btn btn-cancel"
}, " Cancelar "),
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", {
type: "submit",
class: "btn btn-submit",
disabled: $data.isSubmitting,
style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.submitButtonStyle)
}, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.isSubmitting ? 'Enviando...' : 'Criar Ticket'), 13, _hoisted_11)
])
], 32))
: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true)
])
]))
: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true)
]))
}
;// ./src/components/TicketWidget.vue?vue&type=template&id=109adfc9
;// ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/TicketWidget.vue?vue&type=script&lang=js
/* harmony default export */ const TicketWidgetvue_type_script_lang_js = ({
name: 'TicketWidget',
inject: {
deepServiceDeskConfig: {
from: 'deepServiceDeskConfig',
default: () => ({})
}
},
props: {
clientUuid: {
type: String,
default: null
},
brandColor: {
type: String,
default: '#3b82f6'
}
},
data() {
return {
showPopup: false,
isSubmitting: false,
userToken: null,
userEmail: null,
client: null,
errorMessage: '',
ticket: {
title: '',
description: '',
type: 'support',
priority: 'medium'
},
uuidInput: '',
uuidError: null,
configuredClientUuid: null
}
},
computed: {
// URL da API - sempre fixa
computedApiUrl() {
return 'https://servicedesk.deeprocket.com.br/api';
},
// Cores derivadas do brandColor
computedBrandColor() {
return this.brandColor || '#3b82f6';
},
computedBrandColorHover() {
// Criar uma versão mais escura da cor para hover
return this.darkenColor(this.computedBrandColor, 0.1);
},
computedBrandColorFocus() {
// Criar uma versão com transparência para o foco
return this.hexToRgba(this.computedBrandColor, 0.1);
},
// Estilos dinâmicos para os botões
submitButtonStyle() {
return {
backgroundColor: this.computedBrandColor,
borderColor: this.computedBrandColor
};
},
inputFocusStyle() {
return {
'--brand-color': this.computedBrandColor,
'--brand-color-focus': this.computedBrandColorFocus
};
},
// UUID do cliente com prioridade: configurado manualmente > prop > config global > variável de ambiente
computedClientUuid() {
let clientUuid = this.configuredClientUuid || this.clientUuid || this.deepServiceDeskConfig.clientUuid;
if (!clientUuid) {
// Verificar variáveis de ambiente de forma segura
if (false) // removed by dead control flow
{}
if (!clientUuid && typeof window !== 'undefined') {
clientUuid = window.DEEP_SERVICE_DESK_CLIENT_UUID;
}
if (!clientUuid && typeof process !== 'undefined' && ({"NODE_ENV":"production","BASE_URL":"/"})) {
clientUuid = ({"NODE_ENV":"production","BASE_URL":"/"}).VUE_APP_CLIENT_UUID || ({"NODE_ENV":"production","BASE_URL":"/"}).VITE_CLIENT_UUID;
}
}
return clientUuid || null;
}
},
mounted() {
// Escuta o evento 'new-ticket' para abrir o popup
window.addEventListener('new-ticket', this.handleNewTicketEvent)
// Tentar carregar UUID de variáveis de ambiente (opcional)
this.loadClientUuidFromEnv()
},
beforeDestroy() {
window.removeEventListener('new-ticket', this.handleNewTicketEvent)
},
methods: {
// Função para obter token de autenticação do sistema externo
getAuthTokenFromExternalSystem() {
try {
// Busca tokens em ordem de prioridade
const tokenTc = localStorage.getItem('token-tc');
if (tokenTc) return tokenTc;
const tokenDeep = localStorage.getItem('token-deep');
if (tokenDeep) return tokenDeep;
const authToken = localStorage.getItem('authToken');
if (authToken) return authToken;
return null;
} catch (error) {
console.error('Erro ao acessar token:', error);
return null;
}
},
// Função para obter email do usuário do sistema externo
getUserEmailFromExternalSystem() {
try {
// Busca o usuário do sistema externo (user-tc)
const userTcToken = localStorage.getItem('user-tc');
if (userTcToken) {
try {
const userData = JSON.parse(atob(userTcToken));
return userData.email || null;
} catch (decodeError) {
// Silenciar erro de decodificação
}
}
// Fallback para outros sistemas
const userDeep = localStorage.getItem('user-deep');
if (userDeep) {
try {
const userData = JSON.parse(atob(userDeep));
return userData.email || null;
} catch (decodeError) {
// Silenciar erro de decodificação
}
}
return null;
} catch (error) {
console.error('Erro ao acessar dados do usuário:', error);
return null;
}
},
// Função para buscar cliente pelo UUID
async fetchClientByUuid() {
if (!this.userToken || !this.computedClientUuid) return null;
try {
const response = await fetch(`${this.computedApiUrl}/clients/find-by-uuid?uuid=${encodeURIComponent(this.computedClientUuid)}`, {
headers: {
'Authorization': `Bearer ${this.userToken}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const client = await response.json();
return client;
} else if (response.status === 404) {
return null;
} else {
return null;
}
} catch (error) {
console.error('Erro ao buscar cliente por UUID:', error);
return null;
}
},
// Função para buscar cliente pelo email
async findClientByEmail(email) {
if (!email || !this.userToken) return null;
try {
const response = await fetch(`${this.computedApiUrl}/clients/find-by-email?email=${encodeURIComponent(email)}`, {
headers: {
'Authorization': `Bearer ${this.userToken}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const client = await response.json();
return client;
} else if (response.status === 404) {
// Cliente não encontrado - isso é normal
return null;
} else {
return null;
}
} catch (error) {
console.error('Erro ao buscar cliente por email:', error);
return null;
}
},
// Função para buscar clientes
async fetchClients() {
if (!this.userToken) return
try {
const response = await fetch(`${this.computedApiUrl}/clients`, {
headers: {
'Authorization': `Bearer ${this.userToken}`,
'Content-Type': 'application/json'
}
})
if (response.ok) {
const data = await response.json()
this.clients = data.data || data
}
} catch (error) {
console.error('Erro ao buscar clientes:', error)
}
},
// Função para abrir o popup
async openPopup() {
// Se UUID não estiver configurado, apenas abre o popup para configuração
if (!this.computedClientUuid) {
this.errorMessage = '';
this.showPopup = true;
return;
}
// Se UUID já estiver configurado, inicializa o widget
await this.initializeWidget();
if (this.client) {
this.errorMessage = '';
this.showPopup = true;
} else {
this.errorMessage = 'Erro ao configurar widget. Verifique o UUID e autenticação.';
}
},
// Função para fechar o popup
closePopup() {
this.showPopup = false
this.resetForm()
this.errorMessage = ''
},
// Função para resetar o formulário
resetForm() {
this.ticket.title = ''
this.ticket.description = ''
this.ticket.type = 'support'
this.ticket.priority = 'medium'
},
// Função para submeter o ticket
async submitTicket() {
if (!this.userToken) {
this.errorMessage = 'Token de autenticação não encontrado';
return;
}
if (!this.client) {
this.errorMessage = 'Cliente não configurado';
return;
}
this.isSubmitting = true;
this.errorMessage = '';
try {
const ticketData = {
title: this.ticket.title.trim(),
description: this.ticket.description.trim(),
type: this.ticket.type,
priority: this.ticket.priority,
status: 'open',
clientId: this.client.id
};
// Adicionar informações do usuário se disponível
if (this.userEmail) {
ticketData.description += `\n\n--- Informações do Usuário ---\nEmail: ${this.userEmail}`;
}
const response = await fetch(`${this.computedApiUrl}/tickets`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(ticketData)
});
const responseData = await response.json();
if (response.ok) {
// Sucesso - emite evento com dados do ticket criado
this.$emit('ticket-created', {
...responseData,
success: true,
message: 'Ticket criado com sucesso!'
});
// Fecha o popup e reseta o form
this.closePopup();
} else {
// Erro da API
const errorMsg = responseData.error || 'Erro ao criar ticket';
this.errorMessage = errorMsg;
this.$emit('ticket-error', {
message: errorMsg,
details: responseData
});
}
} catch (error) {
console.error('Erro ao criar ticket:', error);
const errorMsg = 'Erro de conexão. Verifique sua internet e tente novamente.';
this.errorMessage = errorMsg;
this.$emit('ticket-error', {
message: errorMsg,
details: error.message
});
} finally {
this.isSubmitting = false;
}
},
// Handler para o evento customizado
handleNewTicketEvent() {
this.openPopup()
},
// Método público para verificar se está aberto (para uso via ref)
isOpen() {
return this.showPopup
},
// Método para carregar o UUID do cliente de variáveis de ambiente
loadClientUuidFromEnv() {
// Tentar carregar de diferentes fontes de variáveis de ambiente
let envUuid = null;
// Verificar se estamos em ambiente de desenvolvimento com Vite
if (false) // removed by dead control flow
{}
// Verificar variável global do window
if (!envUuid && typeof window !== 'undefined') {
envUuid = window.DEEP_SERVICE_DESK_CLIENT_UUID;
}
// Verificar se process está disponível (apenas em desenvolvimento)
if (!envUuid && typeof process !== 'undefined' && ({"NODE_ENV":"production","BASE_URL":"/"})) {
envUuid = ({"NODE_ENV":"production","BASE_URL":"/"}).VUE_APP_CLIENT_UUID || ({"NODE_ENV":"production","BASE_URL":"/"}).VITE_CLIENT_UUID;
}
if (!envUuid) {
return;
}
// Validação básica de UUID
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(envUuid)) {
console.error('❌ UUID do cliente inválido:', envUuid);
return;
}
this.configuredClientUuid = envUuid;
// Inicializar widget automaticamente se UUID for carregado de variável de ambiente
this.initializeWidget();
},
handleUuidKeypress(event) {
if (event.key === 'Enter') {
this.setClientUuid();
}
},
setClientUuid() {
const uuid = this.uuidInput.trim();
if (!uuid) {
this.uuidError = 'Por favor, digite um UUID';
return;
}
// Validação básica de UUID
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(uuid)) {
this.uuidError = 'UUID inválido. Formato esperado: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
return;
}
this.configuredClientUuid = uuid;
this.uuidInput = '';
this.uuidError = null;
// Buscar cliente automaticamente após configurar UUID
this.initializeWidget();
},
async initializeWidget() {
// Busca token de autenticação
this.userToken = this.getAuthTokenFromExternalSystem();
if (!this.userToken) {
this.uuidError = 'Token de autenticação não encontrado. Faça login no sistema.';
this.configuredClientUuid = null;
return;
}
// Busca email do usuário
this.userEmail = this.getUserEmailFromExternalSystem();
// Busca cliente pelo UUID
this.client = await this.fetchClientByUuid();
if (!this.client) {
this.uuidError = 'Cliente não encontrado. Verifique se o UUID está correto.';
this.configuredClientUuid = null;
return;
}
},
resetUuid() {
this.configuredClientUuid = null;
this.client = null;
this.userToken = null;
this.userEmail = null;
this.uuidInput = '';
this.uuidError = null;
this.errorMessage = '';
},
// Métodos para manipulação de cores
hexToRgba(hex, alpha = 1) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
},
darkenColor(hex, amount) {
const num = parseInt(hex.replace('#', ''), 16);
const amt = Math.round(2.55 * amount * 100);
const R = (num >> 16) - amt;
const G = (num >> 8 & 0x00FF) - amt;
const B = (num & 0x0000FF) - amt;
return '#' + (0x1000000 + (R < 0 ? 0 : R) * 0x10000 + (G < 0 ? 0 : G) * 0x100 + (B < 0 ? 0 : B)).toString(16).slice(1);
}
}
});
;// ./src/components/TicketWidget.vue?vue&type=script&lang=js
;// ./node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/TicketWidget.vue?vue&type=style&index=0&id=109adfc9&lang=css
// extracted by mini-css-extract-plugin
;// ./src/components/TicketWidget.vue?vue&type=style&index=0&id=109adfc9&lang=css
// EXTERNAL MODULE: ./node_modules/vue-loader/dist/exportHelper.js
var exportHelper = __webpack_require__(262);
;// ./src/components/TicketWidget.vue
;
const __exports__ = /*#__PURE__*/(0,exportHelper/* default */.A)(TicketWidgetvue_type_script_lang_js, [['render',render]])
/* harmony default export */ const TicketWidget = (__exports__);
;// ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/FloatingTicketWidget.vue?vue&type=template&id=f38110dc
const FloatingTicketWidgetvue_type_template_id_f38110dc_hoisted_1 = { class: "floating-ticket-widget" }
function FloatingTicketWidgetvue_type_template_id_f38110dc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_TicketWidget = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.resolveComponent)("TicketWidget")
return ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", FloatingTicketWidgetvue_type_template_id_f38110dc_hoisted_1, [
(0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createVNode)(_component_TicketWidget, {
ref: "ticketWidget",
"brand-color": $props.brandColor,
onTicketCreated: $options.onTicketCreated,
onTicketError: $options.onTicketError
}, null, 8, ["brand-color", "onTicketCreated", "onTicketError"])
]))
}
;// ./src/components/FloatingTicketWidget.vue?vue&type=template&id=f38110dc
;// ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/FloatingTicketWidget.vue?vue&type=script&lang=js
/* harmony default export */ const FloatingTicketWidgetvue_type_script_lang_js = ({
name: 'FloatingTicketWidget',
components: {
TicketWidget: TicketWidget
},
props: {
brandColor: {
type: String,
default: '#3b82f6'
}
},
inject: {
deepServiceDeskConfig: {
from: 'deepServiceDeskConfig',
default: () => ({})
}
},
mounted() {
// Escutar evento global para abrir o widget
window.addEventListener('new-ticket', this.openWidget)
},
beforeDestroy() {
window.removeEventListener('new-ticket', this.openWidget)
},
methods: {
openWidget() {
if (this.$refs.ticketWidget) {
this.$refs.ticketWidget.openPopup()
}
},
onTicketCreated(data) {
// Emitir evento global para que a aplicação possa escutar
window.dispatchEvent(new CustomEvent('ticket-created', { detail: data }))
// Mostrar notificação de sucesso
this.showNotification('Ticket criado com sucesso!', 'success')
},
onTicketError(error) {
// Emitir evento global para que a aplicação possa escutar
window.dispatchEvent(new CustomEvent('ticket-error', { detail: error }))
// Mostrar notificação de erro
this.showNotification(error.message || 'Erro ao criar ticket', 'error')
},
showNotification(message, type = 'info') {
// Criar notificação toast simples
const notification = document.createElement('div')
notification.className = `deep-service-desk-notification ${type}`
notification.textContent = message
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'};
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 10000;
font-size: 14px;
font-weight: 500;
max-width: 300px;
word-wrap: break-word;
animation: slideInRight 0.3s ease;
`
// Adicionar animação CSS
if (!document.getElementById('deep-service-desk-notification-styles')) {
const style = document.createElement('style')
style.id = 'deep-service-desk-notification-styles'
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`
document.head.appendChild(style)
}
document.body.appendChild(notification)
// Remover após 5 segundos
setTimeout(() => {
notification.style.animation = 'slideOutRight 0.3s ease'
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification)
}
}, 300)
}, 5000)
}
}
});
;// ./src/components/FloatingTicketWidget.vue?vue&type=script&lang=js
;// ./node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/FloatingTicketWidget.vue?vue&type=style&index=0&id=f38110dc&lang=css
// extracted by mini-css-extract-plugin
;// ./src/components/FloatingTicketWidget.vue?vue&type=style&index=0&id=f38110dc&lang=css
;// ./src/components/FloatingTicketWidget.vue
;
const FloatingTicketWidget_exports_ = /*#__PURE__*/(0,exportHelper/* default */.A)(FloatingTicketWidgetvue_type_script_lang_js, [['render',FloatingTicketWidgetvue_type_template_id_f38110dc_render]])
/* harmony default export */ const FloatingTicketWidget = (FloatingTicketWidget_exports_);
;// ./src/index.js
// Variável global para armazenar a configuração
let globalConfig = null
// Variável para controlar se o monitoramento já foi configurado
let urlMonitoringConfigured = false
// Função global para forçar verificação de visibilidade (pode ser chamada externamente)
window.deepServiceDeskUpdateVisibility = function() {
if (globalConfig) {
updateButtonVisibility(globalConfig)
}
}
// Função para detectar a versão do Vue
function getVueVersion() {
if (typeof window !== 'undefined' && window.Vue) {
return window.Vue.version || '2'
}
// Em ambiente de build, tentar detectar pela estrutura
return '3' // Padrão para Vue 3
}
// Função para verificar se a URL atual deve ocultar o botão
function shouldHideButtonOnCurrentUrl(hiddenUrls) {
if (!hiddenUrls || !Array.isArray(hiddenUrls) || hiddenUrls.length === 0) {
return false
}
const currentUrl = window.location.href
const currentPath = window.location.pathname
return hiddenUrls.some(urlPattern => {
// Se é uma string simples, verificar se é substring da URL ou path
if (typeof urlPattern === 'string') {
return currentUrl.includes(urlPattern) || currentPath.includes(urlPattern)
}
// Se é uma expressão regular
if (urlPattern instanceof RegExp) {
return urlPattern.test(currentUrl) || urlPattern.test(currentPath)
}
return false
})
}
// Função para criar o botão flutuante
function createFloatingButton(config) {
// Verificar se o botão já existe
if (document.getElementById('deep-service-desk-floating-btn')) {
// Se o botão já existe, apenas atualizar a visibilidade
updateButtonVisibility(config)
return
}
// SEMPRE criar o botão, mas definir visibilidade inicial baseada na URL
const shouldHideInitially = shouldHideButtonOnCurrentUrl(config.hiddenUrls)
// Criar o botão flutuante
const button = document.createElement('button')
button.id = 'deep-service-desk-floating-btn'
button.innerHTML = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H5.17L4 17.17V4H20V16Z" fill="currentColor"/>
<path d="M7 9H17V11H7V9ZM7 12H17V14H7V12ZM7 6H17V8H7V6Z" fill="currentColor"/>
</svg>
`
// Estilos do botão (incluindo visibilidade inicial)
const brandColor = config.brandColor || '#3b82f6'
button.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
background: ${brandColor};
border: none;
border-radius: 50%;
color: white;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 9998;
display: ${shouldHideInitially ? 'none' : 'flex'};
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 0;
`
// Efeitos hover
button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.1)'
button.style.boxShadow = '0 6px 25px rgba(0, 0, 0, 0.2)'
})
button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)'
button.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.15)'
})
// Evento de clique
button.addEventListener('click', () => {
window.dispatchEvent(new CustomEvent('new-ticket'))
})
// Adicionar tooltip
button.title = 'Abrir Suporte'
// Inserir no DOM
document.body.appendChild(button)
// Adicionar estilos responsivos
const style = document.createElement('style')
style.textContent = `
@media (max-width: 768px) {
#deep-service-desk-floating-btn {
bottom: 15px !important;
right: 15px !important;
width: 50px !important;
height: 50px !important;
}
}
`
document.head.appendChild(style)
// SEMPRE configurar monitoramento de URL (não apenas quando há hiddenUrls)
setupUrlMonitoring(config, button)
}
// Função para atualizar visibilidade do botão
function updateButtonVisibility(config) {
const button = document.getElementById('deep-service-desk-floating-btn')
if (button) {
// SEMPRE verificar - se não há hiddenUrls, o botão deve ser visível
const shouldHide = shouldHideButtonOnCurrentUrl(config.hiddenUrls)
button.style.display = shouldHide ? 'none' : 'flex'
// Debug apenas em localhost
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
console.log('[Deep Service Desk] updateButtonVisibility:')
console.log(' URL atual:', window.location.href)
console.log(' hiddenUrls:', config.hiddenUrls)
console.log(' shouldHide:', shouldHide)
console.log(' display:', button.style.display)
}
}
}
// Função separada para configurar monitoramento de URL
function setupUrlMonitoring(config, button) {
// Se não há URLs ocultas, não precisa monitorar
if (!config.hiddenUrls || config.hiddenUrls.length === 0) {
return
}
// Se monitoramento já foi configurado, não configurar novamente
if (urlMonitoringConfigured) {
return
}
urlMonitoringConfigured = true
let lastUrl = window.location.href
const checkUrlAndToggleButton = () => {
const currentUrl = window.location.href
// Só verificar se a URL realmente mudou
if (currentUrl !== lastUrl) {
lastUrl = currentUrl
}
// Usar a função unificada para atualizar visibilidade
updateButtonVisibility(config)
}
// Múltiplas estratégias para detectar mudanças de URL
// 1. Interceptar pushState e replaceState
const originalPushState = history.pushState
const originalReplaceState = history.replaceState
history.pushState = function() {
originalPushState.apply(history, arguments)
setTimeout(checkUrlAndToggleButton, 50)
setTimeout(checkUrlAndToggleButton, 200) // Fallback adicional
}
history.replaceState = function() {
originalReplaceState.apply(history, arguments)
setTimeout(checkUrlAndToggleButton, 50)
setTimeout(checkUrlAndToggleButton, 200) // Fallback adicional
}
// 2. Escutar eventos de navegação
window.addEventListener('popstate', () => {
setTimeout(checkUrlAndToggleButton, 50)
})
window.addEventListener('hashchange', () => {
setTimeout(checkUrlAndToggleButton, 50)
})
// 3. Observer para mudanças no DOM (pode indicar mudança de página)
if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(() => {
// Verificar se a URL mudou
if (window.location.href !== lastUrl) {
setTimeout(checkUrlAndToggleButton, 100)
}
})
observer.observe(document.body, {
subtree: true,
childList: true
})
}
// 4. Verificação periódica como fallback (a cada 2 segundos)
setInterval(() => {
if (window.location.href !== lastUrl) {
checkUrlAndToggleButton()
}
}, 2000)
// 5. Escutar eventos customizados que podem indicar navegação
window.addEventListener('beforeunload', checkUrlAndToggleButton)
window.addEventListener('load', () => {
setTimeout(checkUrlAndToggleButton, 100)
})
// 6. Detectar frameworks populares e escutar eventos específicos
// Vue Router
if (window.Vue || document.querySelector('[data-v-]')) {
// Escutar eventos do Vue Router
window.addEventListener('vue-router-navigation', () => {
setTimeout(checkUrlAndToggleButton, 50)
})
}
// React Router
if (window.React || document.querySelector('[data-reactroot]')) {
// Escutar mudanças no React
const checkReactNavigation = () => {
setTimeout(checkUrlAndToggleButton, 50)
}
window.addEventListener('react-router-navigation', checkReactNavigation)
}
// 7. Interceptar fetch e XMLHttpRequest para detectar possíveis navegações AJAX
const originalFetch = window.fetch
if (originalFetch) {
window.fetch = function() {
const result = originalFetch.apply(this, arguments)
result.then(() => {
// Verificar após requests AJAX (pode indicar mudança de página)
setTimeout(() => {
if (window.location.href !== lastUrl) {
checkUrlAndToggleButton()
}
}, 300)
}).catch(() => {
// Mesmo em caso de erro, verificar
setTimeout(() => {
if (window.location.href !== lastUrl) {
checkUrlAndToggleButton()
}
}, 300)
})
return result
}
}
// Verificação inicial
setTimeout(checkUrlAndToggleButton, 100)
}
// Função para remover o botão flutuante
function removeFloatingButton() {
const button = document.getElementById('deep-service-desk-floating-btn')
if (button) {
button.remove()
}
}
// Variável global para armazenar a instância do Vue
let globalVueInstance = null
// Função para criar a instância global do widget
function createGlobalWidget(config, isVue3, vueApp) {
// Verificar se já existe
if (document.getElementById('deep-service-desk-global-widget')) {
return
}
// Criar container para o widget
const container = document.createElement('div')
container.id = 'deep-service-desk-global-widget'
document.body.appendChild(container)
// SEMPRE usar o widget independente com formulário completo
// Isso garante que o formulário sempre funcione, independente do Vue
createIndependentWidget(config)
}
// Função para criar um widget independente (sem Vue)
function createIndependentWidget(config) {
// Garantir que temos acesso à configuração global
const finalConfig = config || globalConfig
if (!finalConfig || !finalConfig.clientUuid) {
console.error('❌ ERRO CRÍTICO: Configuração não possui clientUuid!')
console.error('❌ Config recebida:', config)
console.error('❌ Global config:', globalConfig)
return
}
const container = document.getElementById('deep-service-desk-global-widget')
if (!container) return
// Criar HTML do widget com formulário completo
container.innerHTML = `
<div id="independent-ticket-widget" style="display: none;">
<!-- Overlay do modal -->
<div class="widget-overlay" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
">
<div class="widget-modal" style="
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
">
<!-- Header simplificado -->
<div class="widget-header" style="
display: flex;
justify-content: flex-end;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #e5e7eb;
background-color: #f9fafb;
border-radius: 8px 8px 0 0;
">
<button class="widget-close" style="
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #6b7280;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s;
">×</button>
</div>
<!-- Formulário de Ticket -->
<div class="widget-form" style="padding: 24px;">
<!-- Título e subtítulo dentro do conteúdo -->
<div style="text-align: center; margin-bottom: 24px;">
<h3 style="margin: 0 0 8px 0; color: #1f2937; font-size: 20px; font-weight: 600;">Precisa de ajuda?</h3>
<p style="margin: 0; color: #6b7280; font-size: 14px;">Fale com um de nossos especialistas</p>
</div>
<form id="ticket-form">
<!-- Título -->
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 6px; font-weight: 500; color: #374151; font-size: 14px;">
Título *
</label>
<input
type="text"
id="ticket-title"
required
style="
width: 100%;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.2s, box-shadow 0.2s;
"
placeholder="Resumo do problema ou solicitação"
/>
</div>
<!-- Descrição -->
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 6px; font-weight: 500; color: #374151; font-size: 14px;">
Descrição *
</label>
<textarea
id="ticket-description"
required
rows="6"
style="
width: 100%;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
resize: vertical;
min-height: 120px;
transition: border-color 0.2s, box-shadow 0.2s;
"
placeholder="Descreva detalhadamente o problema ou solicitação..."
></textarea>
</div>
<!-- Loading indicator -->
<div id="form-loading" style="display: none; text-align: center; margin: 16px 0;">
<div style="
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #e5e7eb;
border-top: 2px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
"></div>