@escalated-dev/escalated 0.8.0 → 0.11.1

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.
Files changed (124) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +388 -361
  3. package/package.json +85 -84
  4. package/src/components/ActiveChatsPanel.vue +246 -246
  5. package/src/components/AdminDashboard.stories.js +1423 -1423
  6. package/src/components/AttachmentList.vue +65 -65
  7. package/src/components/ChatActionBar.vue +226 -226
  8. package/src/components/ChatBubble.vue +162 -162
  9. package/src/components/ChatComposer.vue +308 -308
  10. package/src/components/ChatQueue.vue +222 -222
  11. package/src/components/ChatThread.vue +197 -197
  12. package/src/components/CustomTicketActions.stories.js +71 -0
  13. package/src/components/CustomTicketActions.vue +101 -0
  14. package/src/components/EscalatedLayout.vue +628 -604
  15. package/src/components/FollowButton.vue +20 -1
  16. package/src/components/MentionDropdown.vue +154 -154
  17. package/src/components/MetricComparison.vue +89 -89
  18. package/src/components/PercentileChart.vue +67 -67
  19. package/src/components/PeriodSelector.vue +98 -98
  20. package/src/components/PresenceIndicator.vue +228 -213
  21. package/src/components/ReplyComposer.vue +336 -336
  22. package/src/components/ReplyThread.vue +153 -153
  23. package/src/components/ReportChart.vue +395 -395
  24. package/src/components/ReportExportButton.vue +108 -108
  25. package/src/components/SaveViewDialog.vue +172 -172
  26. package/src/components/SavedViewSidebar.vue +208 -208
  27. package/src/components/SkillTagManager.vue +3 -0
  28. package/src/components/SnoozeButton.vue +247 -247
  29. package/src/components/TicketFilters.vue +270 -270
  30. package/src/components/TicketList.vue +655 -655
  31. package/src/components/TicketSidebar.vue +4 -0
  32. package/src/components/TicketSplitDialog.vue +98 -98
  33. package/src/components/TicketSubjects.stories.js +63 -0
  34. package/src/components/TicketSubjects.vue +107 -0
  35. package/src/components/TwoFactorSetup.vue +1 -37
  36. package/src/components/VariableTokenInput.vue +111 -111
  37. package/src/components/WorkflowActionConfig.vue +287 -287
  38. package/src/components/WorkflowActionList.vue +350 -350
  39. package/src/components/WorkflowConditionBuilder.vue +265 -265
  40. package/src/components/WorkflowTriggerSelector.vue +125 -125
  41. package/src/components/admin/newsletters/AnalyticsTiles.stories.js +14 -0
  42. package/src/components/admin/newsletters/AnalyticsTiles.vue +55 -0
  43. package/src/components/admin/newsletters/DeliveriesTable.stories.js +28 -0
  44. package/src/components/admin/newsletters/DeliveriesTable.vue +104 -0
  45. package/src/components/admin/newsletters/DynamicFilterBuilder.stories.js +11 -0
  46. package/src/components/admin/newsletters/DynamicFilterBuilder.vue +53 -0
  47. package/src/components/admin/newsletters/ListMemberTable.stories.js +12 -0
  48. package/src/components/admin/newsletters/ListMemberTable.vue +53 -0
  49. package/src/components/admin/newsletters/MarkdownEditor.stories.js +6 -0
  50. package/src/components/admin/newsletters/MarkdownEditor.vue +53 -0
  51. package/src/components/admin/newsletters/MergeFieldDropdown.stories.js +6 -0
  52. package/src/components/admin/newsletters/MergeFieldDropdown.vue +84 -0
  53. package/src/components/admin/newsletters/PreviewIframe.stories.js +7 -0
  54. package/src/components/admin/newsletters/PreviewIframe.vue +40 -0
  55. package/src/components/admin/newsletters/SummaryTiles.stories.js +8 -0
  56. package/src/components/admin/newsletters/SummaryTiles.vue +63 -0
  57. package/src/composables/useChat.js +187 -187
  58. package/src/composables/useI18n.js +102 -102
  59. package/src/composables/useMentions.js +134 -134
  60. package/src/composables/usePermissions.js +31 -0
  61. package/src/composables/useRealtime.js +182 -182
  62. package/src/index.js +85 -84
  63. package/src/locales/README.md +13 -13
  64. package/src/locales/ar.json +727 -727
  65. package/src/locales/de.json +690 -690
  66. package/src/locales/en.json +889 -729
  67. package/src/locales/es.json +691 -691
  68. package/src/locales/fr.json +691 -691
  69. package/src/locales/index.js +16 -16
  70. package/src/locales/it.json +727 -727
  71. package/src/locales/ja.json +727 -727
  72. package/src/locales/ko.json +727 -727
  73. package/src/locales/nl.json +727 -727
  74. package/src/locales/pl.json +727 -727
  75. package/src/locales/pt-BR.json +727 -727
  76. package/src/locales/ru.json +727 -727
  77. package/src/locales/tr.json +727 -727
  78. package/src/locales/zh-CN.json +727 -727
  79. package/src/pages/Admin/Automations/Form.vue +280 -0
  80. package/src/pages/Admin/Automations/Index.vue +230 -0
  81. package/src/pages/Admin/Newsletters/Compose.vue +192 -0
  82. package/src/pages/Admin/Newsletters/Edit.vue +24 -0
  83. package/src/pages/Admin/Newsletters/Index.vue +141 -0
  84. package/src/pages/Admin/Newsletters/Lists/Create.vue +64 -0
  85. package/src/pages/Admin/Newsletters/Lists/Index.vue +59 -0
  86. package/src/pages/Admin/Newsletters/Lists/Show.vue +53 -0
  87. package/src/pages/Admin/Newsletters/Settings.vue +85 -0
  88. package/src/pages/Admin/Newsletters/Show.vue +140 -0
  89. package/src/pages/Admin/Newsletters/Templates/Create.vue +11 -0
  90. package/src/pages/Admin/Newsletters/Templates/Index.vue +54 -0
  91. package/src/pages/Admin/Newsletters/Templates/Show.vue +65 -0
  92. package/src/pages/Admin/Newsletters/usePreview.js +44 -0
  93. package/src/pages/Admin/Plugins/Index.vue +10 -2
  94. package/src/pages/Admin/Reports/AgentDetail.vue +132 -132
  95. package/src/pages/Admin/Reports/AgentRanking.vue +246 -246
  96. package/src/pages/Admin/Reports/Cohorts.vue +180 -180
  97. package/src/pages/Admin/Reports/Comparison.vue +141 -141
  98. package/src/pages/Admin/Reports/Dashboard.vue +226 -226
  99. package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -164
  100. package/src/pages/Admin/Reports/ResponseTimes.vue +164 -164
  101. package/src/pages/Admin/Reports/SlaTrends.vue +183 -183
  102. package/src/pages/Admin/Settings/DatabaseConnection.vue +228 -0
  103. package/src/pages/Admin/Settings/PublicTickets.vue +163 -0
  104. package/src/pages/Admin/Settings/SsoSettings.vue +157 -4
  105. package/src/pages/Admin/Settings/TwoFactor.vue +32 -6
  106. package/src/pages/Admin/Settings.vue +1008 -1008
  107. package/src/pages/Admin/Skills/Form.vue +30 -10
  108. package/src/pages/Admin/Tickets/Index.vue +126 -126
  109. package/src/pages/Admin/Tickets/Show.vue +254 -249
  110. package/src/pages/Admin/Users/Index.vue +145 -145
  111. package/src/pages/Admin/Webhooks/DeliveryLog.vue +1 -2
  112. package/src/pages/Admin/Workflows/Builder.vue +207 -207
  113. package/src/pages/Admin/Workflows/Form.vue +24 -0
  114. package/src/pages/Admin/Workflows/Index.vue +235 -235
  115. package/src/pages/Admin/Workflows/Logs.vue +286 -286
  116. package/src/pages/Agent/TicketIndex.vue +112 -112
  117. package/src/pages/Agent/TicketShow.vue +547 -494
  118. package/src/pages/Customer/Index.vue +56 -56
  119. package/src/pages/Customer/KnowledgeBase/Article.vue +129 -129
  120. package/src/pages/Plugin/Page.vue +61 -0
  121. package/src/plugin.js +175 -175
  122. package/src/utils/mentionHighlight.js +24 -24
  123. package/src/widget/EscalatedWidget.vue +804 -804
  124. package/src/widget/index.js +254 -254
@@ -1,254 +1,254 @@
1
- import { createApp, h } from 'vue';
2
- import EscalatedWidget from './EscalatedWidget.vue';
3
-
4
- (function () {
5
- const scriptTag = document.currentScript || document.querySelector('script[data-escalated-widget]');
6
- const globalConfig = window.EscalatedWidget || {};
7
-
8
- const config = {
9
- baseUrl: scriptTag?.getAttribute('data-base-url') || globalConfig.baseUrl || '',
10
- // Host-framework-specific path prefix. Every plugin except the NestJS
11
- // reference mounts at /support/widget; NestJS mounts at
12
- // /escalated/widget. Set via data-widget-path on NestJS backends.
13
- widgetPath: scriptTag?.getAttribute('data-widget-path') || globalConfig.widgetPath || '/support/widget',
14
- color: scriptTag?.getAttribute('data-color') || globalConfig.color || '#4F46E5',
15
- position: scriptTag?.getAttribute('data-position') || globalConfig.position || 'bottom-right',
16
- };
17
-
18
- const host = document.createElement('div');
19
- host.id = 'escalated-widget-host';
20
- document.body.appendChild(host);
21
-
22
- const shadow = host.attachShadow({ mode: 'open' });
23
-
24
- const mountEl = document.createElement('div');
25
- mountEl.id = 'escalated-widget-root';
26
- shadow.appendChild(mountEl);
27
-
28
- const styleEl = document.createElement('style');
29
- styleEl.textContent = getWidgetStyles();
30
- shadow.appendChild(styleEl);
31
-
32
- const app = createApp({
33
- render() {
34
- return h(EscalatedWidget, {
35
- baseUrl: config.baseUrl,
36
- widgetPath: config.widgetPath,
37
- initialColor: config.color,
38
- initialPosition: config.position,
39
- });
40
- },
41
- });
42
-
43
- app.mount(mountEl);
44
-
45
- window.EscalatedWidget = Object.assign(window.EscalatedWidget || {}, {
46
- open() {
47
- mountEl.querySelector('[data-widget-panel]')?.classList.add('esc-open');
48
- },
49
- close() {
50
- mountEl.querySelector('[data-widget-panel]')?.classList.remove('esc-open');
51
- },
52
- });
53
- })();
54
-
55
- function getWidgetStyles() {
56
- return `
57
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
58
- #escalated-widget-root {
59
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
60
- font-size: 14px; line-height: 1.5; color: #1f2937; -webkit-font-smoothing: antialiased;
61
- }
62
- .esc-w-fab {
63
- position: fixed; z-index: 2147483646; width: 56px; height: 56px;
64
- border-radius: 50%; border: none; cursor: pointer;
65
- display: flex; align-items: center; justify-content: center;
66
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
67
- transition: transform 0.2s, box-shadow 0.2s; color: #fff;
68
- }
69
- .esc-w-fab:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(0,0,0,0.2); }
70
- .esc-w-fab.bottom-right { bottom: 20px; right: 20px; }
71
- .esc-w-fab.bottom-left { bottom: 20px; left: 20px; }
72
- .esc-w-fab svg { width: 24px; height: 24px; fill: currentColor; }
73
- .esc-w-panel {
74
- position: fixed; z-index: 2147483647;
75
- width: 380px; max-width: calc(100vw - 24px);
76
- height: 560px; max-height: calc(100vh - 100px);
77
- border-radius: 12px; background: #ffffff;
78
- box-shadow: 0 8px 30px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.08);
79
- display: flex; flex-direction: column; overflow: hidden;
80
- transform: scale(0.95) translateY(10px); opacity: 0;
81
- pointer-events: none; transition: transform 0.25s ease, opacity 0.25s ease;
82
- }
83
- .esc-w-panel.esc-open { transform: scale(1) translateY(0); opacity: 1; pointer-events: auto; }
84
- .esc-w-panel.bottom-right { bottom: 84px; right: 20px; }
85
- .esc-w-panel.bottom-left { bottom: 84px; left: 20px; }
86
- @media (max-width: 440px) {
87
- .esc-w-panel {
88
- width: calc(100vw - 16px); height: calc(100vh - 80px);
89
- max-height: calc(100vh - 80px); bottom: 8px !important;
90
- right: 8px !important; left: 8px !important; border-radius: 10px;
91
- }
92
- }
93
- .esc-w-header { padding: 16px 20px; color: #fff; flex-shrink: 0; }
94
- .esc-w-header h2 { font-size: 16px; font-weight: 600; margin-bottom: 2px; }
95
- .esc-w-header p { font-size: 13px; opacity: 0.9; }
96
- .esc-w-tabs { display: flex; border-bottom: 1px solid #e5e7eb; flex-shrink: 0; background: #f9fafb; }
97
- .esc-w-tab {
98
- flex: 1; padding: 10px 8px; font-size: 12px; font-weight: 500;
99
- text-align: center; border: none; background: none; cursor: pointer;
100
- color: #6b7280; border-bottom: 2px solid transparent;
101
- transition: color 0.15s, border-color 0.15s;
102
- }
103
- .esc-w-tab:hover { color: #374151; }
104
- .esc-w-tab.active { color: #1f2937; border-bottom-color: var(--esc-widget-color, #4F46E5); }
105
- .esc-w-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
106
- .esc-w-input {
107
- width: 100%; padding: 8px 12px; border: 1px solid #d1d5db;
108
- border-radius: 8px; font-size: 13px; outline: none;
109
- transition: border-color 0.15s; background: #fff; color: #1f2937;
110
- }
111
- .esc-w-input:focus { border-color: var(--esc-widget-color, #4F46E5); box-shadow: 0 0 0 2px rgba(79,70,229,0.1); }
112
- .esc-w-textarea {
113
- width: 100%; padding: 8px 12px; border: 1px solid #d1d5db;
114
- border-radius: 8px; font-size: 13px; outline: none;
115
- resize: vertical; min-height: 80px; font-family: inherit;
116
- transition: border-color 0.15s; background: #fff; color: #1f2937;
117
- }
118
- .esc-w-textarea:focus { border-color: var(--esc-widget-color, #4F46E5); box-shadow: 0 0 0 2px rgba(79,70,229,0.1); }
119
- .esc-w-select {
120
- width: 100%; padding: 8px 12px; border: 1px solid #d1d5db;
121
- border-radius: 8px; font-size: 13px; outline: none;
122
- background: #fff; color: #1f2937; cursor: pointer;
123
- }
124
- .esc-w-select:focus { border-color: var(--esc-widget-color, #4F46E5); }
125
- .esc-w-label { display: block; font-size: 12px; font-weight: 500; color: #374151; margin-bottom: 4px; }
126
- .esc-w-field { margin-bottom: 12px; }
127
- .esc-w-btn {
128
- width: 100%; padding: 10px 16px; border: none; border-radius: 8px;
129
- font-size: 13px; font-weight: 600; color: #fff; cursor: pointer; transition: opacity 0.15s;
130
- }
131
- .esc-w-btn:hover { opacity: 0.9; }
132
- .esc-w-btn:disabled { opacity: 0.5; cursor: not-allowed; }
133
- .esc-w-article { padding: 10px 0; border-bottom: 1px solid #f3f4f6; cursor: pointer; }
134
- .esc-w-article:last-child { border-bottom: none; }
135
- .esc-w-article:hover { background: #f9fafb; margin: 0 -20px; padding: 10px 20px; }
136
- .esc-w-article h4 { font-size: 13px; font-weight: 600; color: #1f2937; margin-bottom: 2px; }
137
- .esc-w-article p { font-size: 12px; color: #6b7280; }
138
- .esc-w-back {
139
- display: inline-flex; align-items: center; gap: 4px; font-size: 12px;
140
- color: #6b7280; cursor: pointer; margin-bottom: 12px;
141
- background: none; border: none; padding: 0;
142
- }
143
- .esc-w-back:hover { color: #374151; }
144
- .esc-w-article-detail h3 { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #1f2937; }
145
- .esc-w-article-detail .esc-w-article-body { font-size: 13px; line-height: 1.7; color: #374151; }
146
- .esc-w-article-detail .esc-w-article-body p { margin-bottom: 8px; }
147
- .esc-w-article-detail .esc-w-article-body a { color: var(--esc-widget-color, #4F46E5); }
148
- .esc-w-success { text-align: center; padding: 24px 0; }
149
- .esc-w-success svg { width: 48px; height: 48px; color: #10b981; margin: 0 auto 12px; }
150
- .esc-w-success h4 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
151
- .esc-w-success p { font-size: 13px; color: #6b7280; }
152
- .esc-w-success .esc-w-ref {
153
- display: inline-block; margin-top: 8px; padding: 4px 12px;
154
- background: #f0fdf4; border-radius: 6px; font-size: 14px; font-weight: 600; color: #059669;
155
- }
156
- .esc-w-status-card { padding: 16px; border-radius: 10px; border: 1px solid #e5e7eb; margin-bottom: 12px; }
157
- .esc-w-status-card h4 { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
158
- .esc-w-status-badge {
159
- display: inline-block; padding: 2px 8px; border-radius: 12px;
160
- font-size: 11px; font-weight: 600; color: #fff;
161
- }
162
- .esc-w-reply { padding: 10px 0; border-bottom: 1px solid #f3f4f6; }
163
- .esc-w-reply:last-child { border-bottom: none; }
164
- .esc-w-reply-author { font-size: 12px; font-weight: 600; color: #374151; }
165
- .esc-w-reply-agent {
166
- font-size: 10px; padding: 1px 6px; background: #eff6ff;
167
- color: #3b82f6; border-radius: 4px; margin-left: 4px;
168
- }
169
- .esc-w-reply-time { font-size: 11px; color: #9ca3af; }
170
- .esc-w-reply-body { font-size: 13px; color: #374151; margin-top: 4px; line-height: 1.6; }
171
- .esc-w-error {
172
- padding: 8px 12px; background: #fef2f2; border: 1px solid #fecaca;
173
- border-radius: 8px; font-size: 12px; color: #dc2626; margin-bottom: 12px;
174
- }
175
- .esc-w-empty { text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px; }
176
- .esc-w-chat-avail {
177
- display: flex; align-items: center; gap: 8px; padding: 10px 14px;
178
- background: #f0fdf4; border-radius: 8px; font-size: 13px; color: #166534; margin-bottom: 16px;
179
- }
180
- .esc-w-chat-dot-green {
181
- width: 8px; height: 8px; border-radius: 50%; background: #22c55e; flex-shrink: 0;
182
- }
183
- .esc-w-chat-header-bar {
184
- display: flex; justify-content: space-between; align-items: center;
185
- padding: 8px 0; margin-bottom: 8px; border-bottom: 1px solid #e5e7eb;
186
- }
187
- .esc-w-chat-agent-info { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; color: #1f2937; }
188
- .esc-w-chat-agent-avatar {
189
- width: 28px; height: 28px; border-radius: 50%; background: var(--esc-widget-color, #4F46E5);
190
- color: #fff; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700;
191
- }
192
- .esc-w-chat-header-actions { display: flex; align-items: center; gap: 8px; }
193
- .esc-w-chat-conn-dot { width: 6px; height: 6px; border-radius: 50%; }
194
- .esc-w-chat-conn-connected { background: #22c55e; }
195
- .esc-w-chat-conn-reconnecting { background: #eab308; }
196
- .esc-w-chat-conn-disconnected { background: #ef4444; }
197
- .esc-w-chat-end-link { background: none; border: none; color: #6b7280; font-size: 12px; cursor: pointer; }
198
- .esc-w-chat-end-link:hover { color: #ef4444; }
199
- .esc-w-chat-messages {
200
- flex: 1; overflow-y: auto; max-height: 280px; min-height: 120px;
201
- padding: 8px 0; display: flex; flex-direction: column; gap: 6px;
202
- }
203
- .esc-w-chat-bubble-row { display: flex; align-items: flex-end; gap: 6px; }
204
- .esc-w-chat-bubble-row.esc-w-chat-agent { flex-direction: row-reverse; }
205
- .esc-w-chat-bubble-row.esc-w-chat-customer { flex-direction: row; }
206
- .esc-w-chat-avatar-sm {
207
- width: 24px; height: 24px; border-radius: 50%; background: #e5e7eb;
208
- display: flex; align-items: center; justify-content: center;
209
- font-size: 9px; font-weight: 700; color: #6b7280; flex-shrink: 0;
210
- }
211
- .esc-w-chat-avatar-agent { background: var(--esc-widget-color, #4F46E5); color: #fff; }
212
- .esc-w-chat-bubble {
213
- max-width: 75%; padding: 8px 12px; border-radius: 16px; font-size: 13px; line-height: 1.5; word-break: break-word;
214
- }
215
- .esc-w-chat-bubble-customer { background: #f3f4f6; color: #1f2937; border-bottom-left-radius: 4px; }
216
- .esc-w-chat-bubble-agent { color: #fff; border-bottom-right-radius: 4px; }
217
- .esc-w-chat-typing { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #9ca3af; padding: 4px 0; }
218
- .esc-w-chat-typing-dots { display: inline-flex; gap: 3px; }
219
- .esc-w-chat-typing-dots span {
220
- width: 5px; height: 5px; border-radius: 50%; background: #9ca3af;
221
- animation: esc-chat-bounce 1.4s infinite both;
222
- }
223
- .esc-w-chat-typing-dots span:nth-child(2) { animation-delay: 0.16s; }
224
- .esc-w-chat-typing-dots span:nth-child(3) { animation-delay: 0.32s; }
225
- @keyframes esc-chat-bounce {
226
- 0%, 80%, 100% { transform: scale(0); }
227
- 40% { transform: scale(1); }
228
- }
229
- .esc-w-chat-input-bar {
230
- display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid #e5e7eb; margin-top: 8px;
231
- }
232
- .esc-w-chat-input {
233
- flex: 1; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 20px;
234
- font-size: 13px; outline: none; background: #fff; color: #1f2937;
235
- }
236
- .esc-w-chat-input:focus { border-color: var(--esc-widget-color, #4F46E5); }
237
- .esc-w-chat-send {
238
- width: 36px; height: 36px; border-radius: 50%; border: none; color: #fff;
239
- display: flex; align-items: center; justify-content: center; cursor: pointer;
240
- transition: opacity 0.15s; flex-shrink: 0;
241
- }
242
- .esc-w-chat-send:disabled { opacity: 0.4; cursor: not-allowed; }
243
- .esc-w-chat-rating { display: flex; justify-content: center; gap: 4px; margin: 12px 0; }
244
- .esc-w-chat-star { background: none; border: none; cursor: pointer; color: #d1d5db; padding: 2px; transition: color 0.15s; }
245
- .esc-w-chat-star:hover, .esc-w-chat-star-active { color: #f59e0b; }
246
- .esc-w-chat-star svg { width: 28px; height: 28px; }
247
- .esc-w-spinner {
248
- width: 20px; height: 20px; border: 2px solid #e5e7eb;
249
- border-top-color: var(--esc-widget-color, #4F46E5);
250
- border-radius: 50%; animation: esc-spin 0.6s linear infinite; margin: 24px auto;
251
- }
252
- @keyframes esc-spin { to { transform: rotate(360deg); } }
253
- `;
254
- }
1
+ import { createApp, h } from 'vue';
2
+ import EscalatedWidget from './EscalatedWidget.vue';
3
+
4
+ (function () {
5
+ const scriptTag = document.currentScript || document.querySelector('script[data-escalated-widget]');
6
+ const globalConfig = window.EscalatedWidget || {};
7
+
8
+ const config = {
9
+ baseUrl: scriptTag?.getAttribute('data-base-url') || globalConfig.baseUrl || '',
10
+ // Host-framework-specific path prefix. Every plugin except the NestJS
11
+ // reference mounts at /support/widget; NestJS mounts at
12
+ // /escalated/widget. Set via data-widget-path on NestJS backends.
13
+ widgetPath: scriptTag?.getAttribute('data-widget-path') || globalConfig.widgetPath || '/support/widget',
14
+ color: scriptTag?.getAttribute('data-color') || globalConfig.color || '#4F46E5',
15
+ position: scriptTag?.getAttribute('data-position') || globalConfig.position || 'bottom-right',
16
+ };
17
+
18
+ const host = document.createElement('div');
19
+ host.id = 'escalated-widget-host';
20
+ document.body.appendChild(host);
21
+
22
+ const shadow = host.attachShadow({ mode: 'open' });
23
+
24
+ const mountEl = document.createElement('div');
25
+ mountEl.id = 'escalated-widget-root';
26
+ shadow.appendChild(mountEl);
27
+
28
+ const styleEl = document.createElement('style');
29
+ styleEl.textContent = getWidgetStyles();
30
+ shadow.appendChild(styleEl);
31
+
32
+ const app = createApp({
33
+ render() {
34
+ return h(EscalatedWidget, {
35
+ baseUrl: config.baseUrl,
36
+ widgetPath: config.widgetPath,
37
+ initialColor: config.color,
38
+ initialPosition: config.position,
39
+ });
40
+ },
41
+ });
42
+
43
+ app.mount(mountEl);
44
+
45
+ window.EscalatedWidget = Object.assign(window.EscalatedWidget || {}, {
46
+ open() {
47
+ mountEl.querySelector('[data-widget-panel]')?.classList.add('esc-open');
48
+ },
49
+ close() {
50
+ mountEl.querySelector('[data-widget-panel]')?.classList.remove('esc-open');
51
+ },
52
+ });
53
+ })();
54
+
55
+ function getWidgetStyles() {
56
+ return `
57
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
58
+ #escalated-widget-root {
59
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
60
+ font-size: 14px; line-height: 1.5; color: #1f2937; -webkit-font-smoothing: antialiased;
61
+ }
62
+ .esc-w-fab {
63
+ position: fixed; z-index: 2147483646; width: 56px; height: 56px;
64
+ border-radius: 50%; border: none; cursor: pointer;
65
+ display: flex; align-items: center; justify-content: center;
66
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
67
+ transition: transform 0.2s, box-shadow 0.2s; color: #fff;
68
+ }
69
+ .esc-w-fab:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(0,0,0,0.2); }
70
+ .esc-w-fab.bottom-right { bottom: 20px; right: 20px; }
71
+ .esc-w-fab.bottom-left { bottom: 20px; left: 20px; }
72
+ .esc-w-fab svg { width: 24px; height: 24px; fill: currentColor; }
73
+ .esc-w-panel {
74
+ position: fixed; z-index: 2147483647;
75
+ width: 380px; max-width: calc(100vw - 24px);
76
+ height: 560px; max-height: calc(100vh - 100px);
77
+ border-radius: 12px; background: #ffffff;
78
+ box-shadow: 0 8px 30px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.08);
79
+ display: flex; flex-direction: column; overflow: hidden;
80
+ transform: scale(0.95) translateY(10px); opacity: 0;
81
+ pointer-events: none; transition: transform 0.25s ease, opacity 0.25s ease;
82
+ }
83
+ .esc-w-panel.esc-open { transform: scale(1) translateY(0); opacity: 1; pointer-events: auto; }
84
+ .esc-w-panel.bottom-right { bottom: 84px; right: 20px; }
85
+ .esc-w-panel.bottom-left { bottom: 84px; left: 20px; }
86
+ @media (max-width: 440px) {
87
+ .esc-w-panel {
88
+ width: calc(100vw - 16px); height: calc(100vh - 80px);
89
+ max-height: calc(100vh - 80px); bottom: 8px !important;
90
+ right: 8px !important; left: 8px !important; border-radius: 10px;
91
+ }
92
+ }
93
+ .esc-w-header { padding: 16px 20px; color: #fff; flex-shrink: 0; }
94
+ .esc-w-header h2 { font-size: 16px; font-weight: 600; margin-bottom: 2px; }
95
+ .esc-w-header p { font-size: 13px; opacity: 0.9; }
96
+ .esc-w-tabs { display: flex; border-bottom: 1px solid #e5e7eb; flex-shrink: 0; background: #f9fafb; }
97
+ .esc-w-tab {
98
+ flex: 1; padding: 10px 8px; font-size: 12px; font-weight: 500;
99
+ text-align: center; border: none; background: none; cursor: pointer;
100
+ color: #6b7280; border-bottom: 2px solid transparent;
101
+ transition: color 0.15s, border-color 0.15s;
102
+ }
103
+ .esc-w-tab:hover { color: #374151; }
104
+ .esc-w-tab.active { color: #1f2937; border-bottom-color: var(--esc-widget-color, #4F46E5); }
105
+ .esc-w-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
106
+ .esc-w-input {
107
+ width: 100%; padding: 8px 12px; border: 1px solid #d1d5db;
108
+ border-radius: 8px; font-size: 13px; outline: none;
109
+ transition: border-color 0.15s; background: #fff; color: #1f2937;
110
+ }
111
+ .esc-w-input:focus { border-color: var(--esc-widget-color, #4F46E5); box-shadow: 0 0 0 2px rgba(79,70,229,0.1); }
112
+ .esc-w-textarea {
113
+ width: 100%; padding: 8px 12px; border: 1px solid #d1d5db;
114
+ border-radius: 8px; font-size: 13px; outline: none;
115
+ resize: vertical; min-height: 80px; font-family: inherit;
116
+ transition: border-color 0.15s; background: #fff; color: #1f2937;
117
+ }
118
+ .esc-w-textarea:focus { border-color: var(--esc-widget-color, #4F46E5); box-shadow: 0 0 0 2px rgba(79,70,229,0.1); }
119
+ .esc-w-select {
120
+ width: 100%; padding: 8px 12px; border: 1px solid #d1d5db;
121
+ border-radius: 8px; font-size: 13px; outline: none;
122
+ background: #fff; color: #1f2937; cursor: pointer;
123
+ }
124
+ .esc-w-select:focus { border-color: var(--esc-widget-color, #4F46E5); }
125
+ .esc-w-label { display: block; font-size: 12px; font-weight: 500; color: #374151; margin-bottom: 4px; }
126
+ .esc-w-field { margin-bottom: 12px; }
127
+ .esc-w-btn {
128
+ width: 100%; padding: 10px 16px; border: none; border-radius: 8px;
129
+ font-size: 13px; font-weight: 600; color: #fff; cursor: pointer; transition: opacity 0.15s;
130
+ }
131
+ .esc-w-btn:hover { opacity: 0.9; }
132
+ .esc-w-btn:disabled { opacity: 0.5; cursor: not-allowed; }
133
+ .esc-w-article { padding: 10px 0; border-bottom: 1px solid #f3f4f6; cursor: pointer; }
134
+ .esc-w-article:last-child { border-bottom: none; }
135
+ .esc-w-article:hover { background: #f9fafb; margin: 0 -20px; padding: 10px 20px; }
136
+ .esc-w-article h4 { font-size: 13px; font-weight: 600; color: #1f2937; margin-bottom: 2px; }
137
+ .esc-w-article p { font-size: 12px; color: #6b7280; }
138
+ .esc-w-back {
139
+ display: inline-flex; align-items: center; gap: 4px; font-size: 12px;
140
+ color: #6b7280; cursor: pointer; margin-bottom: 12px;
141
+ background: none; border: none; padding: 0;
142
+ }
143
+ .esc-w-back:hover { color: #374151; }
144
+ .esc-w-article-detail h3 { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #1f2937; }
145
+ .esc-w-article-detail .esc-w-article-body { font-size: 13px; line-height: 1.7; color: #374151; }
146
+ .esc-w-article-detail .esc-w-article-body p { margin-bottom: 8px; }
147
+ .esc-w-article-detail .esc-w-article-body a { color: var(--esc-widget-color, #4F46E5); }
148
+ .esc-w-success { text-align: center; padding: 24px 0; }
149
+ .esc-w-success svg { width: 48px; height: 48px; color: #10b981; margin: 0 auto 12px; }
150
+ .esc-w-success h4 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
151
+ .esc-w-success p { font-size: 13px; color: #6b7280; }
152
+ .esc-w-success .esc-w-ref {
153
+ display: inline-block; margin-top: 8px; padding: 4px 12px;
154
+ background: #f0fdf4; border-radius: 6px; font-size: 14px; font-weight: 600; color: #059669;
155
+ }
156
+ .esc-w-status-card { padding: 16px; border-radius: 10px; border: 1px solid #e5e7eb; margin-bottom: 12px; }
157
+ .esc-w-status-card h4 { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
158
+ .esc-w-status-badge {
159
+ display: inline-block; padding: 2px 8px; border-radius: 12px;
160
+ font-size: 11px; font-weight: 600; color: #fff;
161
+ }
162
+ .esc-w-reply { padding: 10px 0; border-bottom: 1px solid #f3f4f6; }
163
+ .esc-w-reply:last-child { border-bottom: none; }
164
+ .esc-w-reply-author { font-size: 12px; font-weight: 600; color: #374151; }
165
+ .esc-w-reply-agent {
166
+ font-size: 10px; padding: 1px 6px; background: #eff6ff;
167
+ color: #3b82f6; border-radius: 4px; margin-left: 4px;
168
+ }
169
+ .esc-w-reply-time { font-size: 11px; color: #9ca3af; }
170
+ .esc-w-reply-body { font-size: 13px; color: #374151; margin-top: 4px; line-height: 1.6; }
171
+ .esc-w-error {
172
+ padding: 8px 12px; background: #fef2f2; border: 1px solid #fecaca;
173
+ border-radius: 8px; font-size: 12px; color: #dc2626; margin-bottom: 12px;
174
+ }
175
+ .esc-w-empty { text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px; }
176
+ .esc-w-chat-avail {
177
+ display: flex; align-items: center; gap: 8px; padding: 10px 14px;
178
+ background: #f0fdf4; border-radius: 8px; font-size: 13px; color: #166534; margin-bottom: 16px;
179
+ }
180
+ .esc-w-chat-dot-green {
181
+ width: 8px; height: 8px; border-radius: 50%; background: #22c55e; flex-shrink: 0;
182
+ }
183
+ .esc-w-chat-header-bar {
184
+ display: flex; justify-content: space-between; align-items: center;
185
+ padding: 8px 0; margin-bottom: 8px; border-bottom: 1px solid #e5e7eb;
186
+ }
187
+ .esc-w-chat-agent-info { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; color: #1f2937; }
188
+ .esc-w-chat-agent-avatar {
189
+ width: 28px; height: 28px; border-radius: 50%; background: var(--esc-widget-color, #4F46E5);
190
+ color: #fff; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700;
191
+ }
192
+ .esc-w-chat-header-actions { display: flex; align-items: center; gap: 8px; }
193
+ .esc-w-chat-conn-dot { width: 6px; height: 6px; border-radius: 50%; }
194
+ .esc-w-chat-conn-connected { background: #22c55e; }
195
+ .esc-w-chat-conn-reconnecting { background: #eab308; }
196
+ .esc-w-chat-conn-disconnected { background: #ef4444; }
197
+ .esc-w-chat-end-link { background: none; border: none; color: #6b7280; font-size: 12px; cursor: pointer; }
198
+ .esc-w-chat-end-link:hover { color: #ef4444; }
199
+ .esc-w-chat-messages {
200
+ flex: 1; overflow-y: auto; max-height: 280px; min-height: 120px;
201
+ padding: 8px 0; display: flex; flex-direction: column; gap: 6px;
202
+ }
203
+ .esc-w-chat-bubble-row { display: flex; align-items: flex-end; gap: 6px; }
204
+ .esc-w-chat-bubble-row.esc-w-chat-agent { flex-direction: row-reverse; }
205
+ .esc-w-chat-bubble-row.esc-w-chat-customer { flex-direction: row; }
206
+ .esc-w-chat-avatar-sm {
207
+ width: 24px; height: 24px; border-radius: 50%; background: #e5e7eb;
208
+ display: flex; align-items: center; justify-content: center;
209
+ font-size: 9px; font-weight: 700; color: #6b7280; flex-shrink: 0;
210
+ }
211
+ .esc-w-chat-avatar-agent { background: var(--esc-widget-color, #4F46E5); color: #fff; }
212
+ .esc-w-chat-bubble {
213
+ max-width: 75%; padding: 8px 12px; border-radius: 16px; font-size: 13px; line-height: 1.5; word-break: break-word;
214
+ }
215
+ .esc-w-chat-bubble-customer { background: #f3f4f6; color: #1f2937; border-bottom-left-radius: 4px; }
216
+ .esc-w-chat-bubble-agent { color: #fff; border-bottom-right-radius: 4px; }
217
+ .esc-w-chat-typing { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #9ca3af; padding: 4px 0; }
218
+ .esc-w-chat-typing-dots { display: inline-flex; gap: 3px; }
219
+ .esc-w-chat-typing-dots span {
220
+ width: 5px; height: 5px; border-radius: 50%; background: #9ca3af;
221
+ animation: esc-chat-bounce 1.4s infinite both;
222
+ }
223
+ .esc-w-chat-typing-dots span:nth-child(2) { animation-delay: 0.16s; }
224
+ .esc-w-chat-typing-dots span:nth-child(3) { animation-delay: 0.32s; }
225
+ @keyframes esc-chat-bounce {
226
+ 0%, 80%, 100% { transform: scale(0); }
227
+ 40% { transform: scale(1); }
228
+ }
229
+ .esc-w-chat-input-bar {
230
+ display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid #e5e7eb; margin-top: 8px;
231
+ }
232
+ .esc-w-chat-input {
233
+ flex: 1; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 20px;
234
+ font-size: 13px; outline: none; background: #fff; color: #1f2937;
235
+ }
236
+ .esc-w-chat-input:focus { border-color: var(--esc-widget-color, #4F46E5); }
237
+ .esc-w-chat-send {
238
+ width: 36px; height: 36px; border-radius: 50%; border: none; color: #fff;
239
+ display: flex; align-items: center; justify-content: center; cursor: pointer;
240
+ transition: opacity 0.15s; flex-shrink: 0;
241
+ }
242
+ .esc-w-chat-send:disabled { opacity: 0.4; cursor: not-allowed; }
243
+ .esc-w-chat-rating { display: flex; justify-content: center; gap: 4px; margin: 12px 0; }
244
+ .esc-w-chat-star { background: none; border: none; cursor: pointer; color: #d1d5db; padding: 2px; transition: color 0.15s; }
245
+ .esc-w-chat-star:hover, .esc-w-chat-star-active { color: #f59e0b; }
246
+ .esc-w-chat-star svg { width: 28px; height: 28px; }
247
+ .esc-w-spinner {
248
+ width: 20px; height: 20px; border: 2px solid #e5e7eb;
249
+ border-top-color: var(--esc-widget-color, #4F46E5);
250
+ border-radius: 50%; animation: esc-spin 0.6s linear infinite; margin: 24px auto;
251
+ }
252
+ @keyframes esc-spin { to { transform: rotate(360deg); } }
253
+ `;
254
+ }