@escalated-dev/escalated 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +36 -1
  3. package/package.json +16 -7
  4. package/src/components/ActiveChatsPanel.vue +246 -0
  5. package/src/components/ActivityTimeline.stories.js +55 -55
  6. package/src/components/ActivityTimeline.vue +83 -83
  7. package/src/components/AdminDashboard.stories.js +1423 -1095
  8. package/src/components/AgentLoadIndicator.stories.js +37 -37
  9. package/src/components/AgentLoadIndicator.vue +49 -49
  10. package/src/components/AgentPerformanceCard.vue +44 -44
  11. package/src/components/ArticleEditor.vue +82 -82
  12. package/src/components/AssigneeSelect.vue +37 -37
  13. package/src/components/AttachmentList.vue +13 -1
  14. package/src/components/AuditLogEntry.vue +95 -95
  15. package/src/components/BulkActionBar.vue +166 -166
  16. package/src/components/CategoryTree.vue +89 -89
  17. package/src/components/ChartWidget.vue +46 -46
  18. package/src/components/ChatActionBar.vue +226 -0
  19. package/src/components/ChatBubble.vue +162 -0
  20. package/src/components/ChatComposer.vue +308 -0
  21. package/src/components/ChatQueue.vue +222 -0
  22. package/src/components/ChatThread.vue +197 -0
  23. package/src/components/CollisionWarning.stories.js +30 -30
  24. package/src/components/CollisionWarning.vue +68 -68
  25. package/src/components/ConditionalFieldRules.vue +135 -135
  26. package/src/components/ContextPanel.vue +66 -66
  27. package/src/components/ContextPanelSection.vue +65 -65
  28. package/src/components/CustomFieldRenderer.vue +159 -159
  29. package/src/components/EscalatedLayout.vue +35 -6
  30. package/src/components/FileDropzone.vue +57 -57
  31. package/src/components/FollowButton.stories.js +30 -30
  32. package/src/components/FollowButton.vue +88 -88
  33. package/src/components/KeyboardShortcutHelp.vue +213 -213
  34. package/src/components/KnowledgePanel.vue +122 -122
  35. package/src/components/KpiCard.stories.js +32 -32
  36. package/src/components/KpiCard.vue +88 -88
  37. package/src/components/MacroDropdown.vue +150 -150
  38. package/src/components/MentionDropdown.vue +154 -0
  39. package/src/components/MetricComparison.vue +89 -0
  40. package/src/components/PercentileChart.vue +67 -0
  41. package/src/components/PeriodSelector.vue +98 -0
  42. package/src/components/PermissionMatrix.vue +107 -107
  43. package/src/components/PinnedNotes.vue +129 -129
  44. package/src/components/PluginSlot.vue +68 -62
  45. package/src/components/PresenceIndicator.vue +46 -2
  46. package/src/components/PriorityBadge.stories.js +33 -33
  47. package/src/components/PriorityBadge.vue +46 -46
  48. package/src/components/ReplyComposer.vue +81 -18
  49. package/src/components/ReplyThread.vue +40 -2
  50. package/src/components/ReportChart.vue +395 -0
  51. package/src/components/ReportExportButton.vue +108 -0
  52. package/src/components/SatisfactionRating.vue +180 -180
  53. package/src/components/SaveViewDialog.vue +172 -0
  54. package/src/components/SavedViewSidebar.vue +208 -0
  55. package/src/components/ScheduleEditor.vue +104 -104
  56. package/src/components/SideConversation.vue +165 -165
  57. package/src/components/SideConversationList.vue +147 -147
  58. package/src/components/SkillTagManager.vue +83 -79
  59. package/src/components/SlaComplianceChart.vue +46 -53
  60. package/src/components/SlaTimer.stories.js +35 -35
  61. package/src/components/SlaTimer.vue +62 -62
  62. package/src/components/SnoozeButton.vue +247 -0
  63. package/src/components/StatsCard.stories.js +24 -24
  64. package/src/components/StatsCard.vue +58 -58
  65. package/src/components/StatusBadge.stories.js +53 -53
  66. package/src/components/StatusBadge.vue +78 -78
  67. package/src/components/TagSelect.vue +84 -84
  68. package/src/components/TicketFilters.vue +270 -225
  69. package/src/components/TicketLinkPanel.vue +193 -193
  70. package/src/components/TicketList.vue +655 -594
  71. package/src/components/TicketMergeDialog.vue +157 -157
  72. package/src/components/TicketSplitDialog.vue +98 -0
  73. package/src/components/TotpInput.stories.js +9 -9
  74. package/src/components/TotpInput.vue +81 -81
  75. package/src/components/TwoFactorSetup.vue +113 -113
  76. package/src/components/VariableTokenInput.vue +111 -0
  77. package/src/components/WorkflowActionConfig.vue +287 -0
  78. package/src/components/WorkflowActionList.vue +350 -0
  79. package/src/components/WorkflowConditionBuilder.vue +265 -0
  80. package/src/components/WorkflowTriggerSelector.vue +125 -0
  81. package/src/composables/useChat.js +187 -0
  82. package/src/composables/useI18n.js +30 -8
  83. package/src/composables/useKeyboardShortcuts.js +46 -46
  84. package/src/composables/useMentions.js +134 -0
  85. package/src/composables/usePluginExtensions.js +159 -159
  86. package/src/composables/usePluginHooks.js +65 -65
  87. package/src/composables/useRealtime.js +182 -0
  88. package/src/defineEscalatedPlugin.js +76 -76
  89. package/src/index.js +84 -72
  90. package/src/locales/README.md +13 -0
  91. package/src/locales/ar.json +727 -0
  92. package/src/locales/de.json +678 -430
  93. package/src/locales/en.json +717 -455
  94. package/src/locales/es.json +679 -430
  95. package/src/locales/fr.json +680 -431
  96. package/src/locales/index.js +12 -2
  97. package/src/locales/it.json +727 -0
  98. package/src/locales/ja.json +727 -0
  99. package/src/locales/ko.json +727 -0
  100. package/src/locales/nl.json +727 -0
  101. package/src/locales/pl.json +727 -0
  102. package/src/locales/pt-BR.json +727 -0
  103. package/src/locales/ru.json +727 -0
  104. package/src/locales/tr.json +727 -0
  105. package/src/locales/zh-CN.json +727 -0
  106. package/src/pages/Admin/ApiTokens/Index.vue +381 -381
  107. package/src/pages/Admin/AuditLog/Index.vue +154 -154
  108. package/src/pages/Admin/BusinessHours/Form.vue +172 -172
  109. package/src/pages/Admin/BusinessHours/Index.vue +113 -113
  110. package/src/pages/Admin/CannedResponses/Index.vue +127 -127
  111. package/src/pages/Admin/Capacity/Index.vue +134 -134
  112. package/src/pages/Admin/CustomFields/Form.vue +195 -195
  113. package/src/pages/Admin/CustomFields/Index.vue +134 -134
  114. package/src/pages/Admin/CustomObjects/Form.vue +202 -202
  115. package/src/pages/Admin/CustomObjects/Index.vue +127 -127
  116. package/src/pages/Admin/CustomObjects/Records.vue +228 -228
  117. package/src/pages/Admin/Departments/Form.vue +75 -75
  118. package/src/pages/Admin/Departments/Index.vue +110 -109
  119. package/src/pages/Admin/EscalationRules/Form.vue +157 -157
  120. package/src/pages/Admin/EscalationRules/Index.vue +178 -178
  121. package/src/pages/Admin/Import/Connect.vue +0 -1
  122. package/src/pages/Admin/Import/Index.vue +1 -1
  123. package/src/pages/Admin/Import/Progress.vue +1 -1
  124. package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -142
  125. package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -195
  126. package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -193
  127. package/src/pages/Admin/Macros/Index.vue +448 -448
  128. package/src/pages/Admin/Plugins/Index.vue +327 -327
  129. package/src/pages/Admin/Reports/AgentDetail.vue +132 -0
  130. package/src/pages/Admin/Reports/AgentMetrics.vue +115 -115
  131. package/src/pages/Admin/Reports/AgentRanking.vue +246 -0
  132. package/src/pages/Admin/Reports/Cohorts.vue +180 -0
  133. package/src/pages/Admin/Reports/Comparison.vue +141 -0
  134. package/src/pages/Admin/Reports/CsatReport.vue +127 -127
  135. package/src/pages/Admin/Reports/Dashboard.vue +36 -21
  136. package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -0
  137. package/src/pages/Admin/Reports/ResponseTimes.vue +164 -0
  138. package/src/pages/Admin/Reports/SlaReport.vue +162 -166
  139. package/src/pages/Admin/Reports/SlaTrends.vue +183 -0
  140. package/src/pages/Admin/Reports.vue +206 -206
  141. package/src/pages/Admin/Roles/Form.vue +71 -71
  142. package/src/pages/Admin/Roles/Index.vue +122 -122
  143. package/src/pages/Admin/Settings/CsatSettings.vue +165 -165
  144. package/src/pages/Admin/Settings/DataRetention.vue +182 -182
  145. package/src/pages/Admin/Settings/EmailSettings.vue +243 -243
  146. package/src/pages/Admin/Settings/Sandbox.vue +135 -135
  147. package/src/pages/Admin/Settings/SsoSettings.vue +218 -218
  148. package/src/pages/Admin/Settings/TwoFactor.vue +110 -110
  149. package/src/pages/Admin/Settings.vue +1008 -468
  150. package/src/pages/Admin/Skills/Form.vue +193 -53
  151. package/src/pages/Admin/Skills/Index.vue +105 -94
  152. package/src/pages/Admin/SlaPolicies/Form.vue +118 -118
  153. package/src/pages/Admin/SlaPolicies/Index.vue +121 -120
  154. package/src/pages/Admin/Statuses/Form.vue +114 -114
  155. package/src/pages/Admin/Statuses/Index.vue +153 -153
  156. package/src/pages/Admin/Tags/Index.vue +191 -191
  157. package/src/pages/Admin/Tickets/Index.vue +126 -91
  158. package/src/pages/Admin/Tickets/Show.vue +29 -0
  159. package/src/pages/Admin/Users/Index.vue +145 -0
  160. package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -120
  161. package/src/pages/Admin/Webhooks/Form.vue +110 -110
  162. package/src/pages/Admin/Webhooks/Index.vue +136 -136
  163. package/src/pages/Admin/Workflows/Builder.vue +207 -0
  164. package/src/pages/Admin/Workflows/Index.vue +235 -0
  165. package/src/pages/Admin/Workflows/Logs.vue +286 -0
  166. package/src/pages/Agent/TicketIndex.vue +112 -88
  167. package/src/pages/Agent/TicketShow.vue +192 -60
  168. package/src/pages/Customer/Create.vue +1 -1
  169. package/src/pages/Customer/Index.vue +26 -1
  170. package/src/pages/Customer/KnowledgeBase/Article.vue +4 -2
  171. package/src/pages/Customer/KnowledgeBase/Index.vue +151 -151
  172. package/src/pages/Customer/Show.vue +69 -63
  173. package/src/pages/Guest/Create.vue +40 -15
  174. package/src/pages/Guest/Show.vue +124 -93
  175. package/src/plugin.js +26 -1
  176. package/src/utils/mentionHighlight.js +24 -0
  177. package/src/utils/sanitizeHtml.js +78 -64
  178. package/src/widget/EscalatedWidget.vue +804 -0
  179. package/src/widget/index.js +254 -0
  180. package/src/pages/Admin/Automations/Form.vue +0 -184
  181. package/src/pages/Admin/Automations/Index.vue +0 -129
@@ -1,468 +1,1008 @@
1
- <script setup>
2
- import EscalatedLayout from '../../components/EscalatedLayout.vue';
3
- import PluginSlot from '../../components/PluginSlot.vue';
4
- import { useForm, usePage } from '@inertiajs/vue3';
5
- import { computed } from 'vue';
6
- import { usePluginExtensions } from '../../composables/usePluginExtensions';
7
-
8
- const props = defineProps({ settings: Object });
9
- const page = usePage();
10
-
11
- const form = useForm({
12
- guest_tickets_enabled: props.settings.guest_tickets_enabled,
13
- allow_customer_close: props.settings.allow_customer_close,
14
- auto_close_resolved_after_days: props.settings.auto_close_resolved_after_days,
15
- max_attachments_per_reply: props.settings.max_attachments_per_reply,
16
- max_attachment_size_kb: props.settings.max_attachment_size_kb,
17
- ticket_reference_prefix: props.settings.ticket_reference_prefix,
18
- inbound_email_enabled: props.settings.inbound_email_enabled ?? false,
19
- inbound_email_adapter: props.settings.inbound_email_adapter ?? 'mailgun',
20
- inbound_email_address: props.settings.inbound_email_address ?? '',
21
- mailgun_signing_key: props.settings.mailgun_signing_key ?? '',
22
- postmark_inbound_token: props.settings.postmark_inbound_token ?? '',
23
- ses_region: props.settings.ses_region ?? '',
24
- ses_topic_arn: props.settings.ses_topic_arn ?? '',
25
- imap_host: props.settings.imap_host ?? '',
26
- imap_port: props.settings.imap_port ?? 993,
27
- imap_encryption: props.settings.imap_encryption ?? 'ssl',
28
- imap_username: props.settings.imap_username ?? '',
29
- imap_password: props.settings.imap_password ?? '',
30
- imap_mailbox: props.settings.imap_mailbox ?? 'INBOX',
31
- show_powered_by: props.settings.show_powered_by ?? true,
32
- });
33
-
34
- const webhookBaseUrl = computed(() => {
35
- const prefix = page.props.escalated?.prefix || 'support';
36
- const origin = typeof window !== 'undefined' ? window.location.origin : '';
37
- return `${origin}/${prefix}/inbound`;
38
- });
39
-
40
- const { getPageComponents } = usePluginExtensions();
41
-
42
- function submit() {
43
- form.post(route('escalated.admin.settings.update'));
44
- }
45
- </script>
46
-
47
- <template>
48
- <EscalatedLayout title="Settings">
49
- <form class="mx-auto max-w-2xl space-y-6" @submit.prevent="submit">
50
- <!-- General -->
51
- <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
52
- <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">General</h3>
53
- <div class="space-y-5">
54
- <label class="flex items-center justify-between">
55
- <div>
56
- <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
57
- >Guest Tickets</span
58
- >
59
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
60
- Allow visitors to submit tickets without signing in
61
- </p>
62
- </div>
63
- <button
64
- type="button"
65
- :class="[
66
- 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
67
- form.guest_tickets_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
68
- ]"
69
- @click="form.guest_tickets_enabled = !form.guest_tickets_enabled"
70
- >
71
- <span
72
- :class="[
73
- 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
74
- form.guest_tickets_enabled ? 'translate-x-5' : 'translate-x-0',
75
- ]"
76
- />
77
- </button>
78
- </label>
79
-
80
- <label class="flex items-center justify-between">
81
- <div>
82
- <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
83
- >Allow Customer Close</span
84
- >
85
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
86
- Let customers close their own tickets
87
- </p>
88
- </div>
89
- <button
90
- type="button"
91
- :class="[
92
- 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
93
- form.allow_customer_close ? 'bg-emerald-500' : 'bg-neutral-700',
94
- ]"
95
- @click="form.allow_customer_close = !form.allow_customer_close"
96
- >
97
- <span
98
- :class="[
99
- 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
100
- form.allow_customer_close ? 'translate-x-5' : 'translate-x-0',
101
- ]"
102
- />
103
- </button>
104
- </label>
105
-
106
- <div>
107
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
108
- >Auto-close resolved tickets after</label
109
- >
110
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
111
- Days after resolution before auto-closing (0 to disable)
112
- </p>
113
- <div class="mt-2 flex items-center gap-2">
114
- <input
115
- v-model.number="form.auto_close_resolved_after_days"
116
- type="number"
117
- min="0"
118
- max="365"
119
- class="w-24 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
120
- />
121
- <span class="text-sm text-[var(--esc-panel-text-muted)]">days</span>
122
- </div>
123
- </div>
124
- </div>
125
- </div>
126
-
127
- <!-- Limits -->
128
- <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
129
- <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Limits</h3>
130
- <div class="space-y-5">
131
- <div>
132
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
133
- >Max attachments per reply</label
134
- >
135
- <input
136
- v-model.number="form.max_attachments_per_reply"
137
- type="number"
138
- min="1"
139
- max="20"
140
- class="mt-2 w-24 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
141
- />
142
- </div>
143
- <div>
144
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
145
- >Max attachment size</label
146
- >
147
- <div class="mt-2 flex items-center gap-2">
148
- <input
149
- v-model.number="form.max_attachment_size_kb"
150
- type="number"
151
- min="512"
152
- max="102400"
153
- step="512"
154
- class="w-32 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
155
- />
156
- <span class="text-sm text-[var(--esc-panel-text-muted)]"
157
- >KB ({{ Math.round(form.max_attachment_size_kb / 1024) }} MB)</span
158
- >
159
- </div>
160
- </div>
161
- </div>
162
- </div>
163
-
164
- <!-- Tickets -->
165
- <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
166
- <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Tickets</h3>
167
- <div class="space-y-5">
168
- <div>
169
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
170
- >Reference Prefix</label
171
- >
172
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
173
- Prefix for ticket references (e.g. ESC produces ESC-00001)
174
- </p>
175
- <input
176
- v-model="form.ticket_reference_prefix"
177
- type="text"
178
- maxlength="10"
179
- class="mt-2 w-32 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] uppercase focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
180
- />
181
- </div>
182
- </div>
183
- </div>
184
-
185
- <!-- Inbound Email -->
186
- <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
187
- <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Inbound Email</h3>
188
- <div class="space-y-5">
189
- <label class="flex items-center justify-between">
190
- <div>
191
- <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
192
- >Inbound Email</span
193
- >
194
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
195
- Allow creating and replying to tickets via email
196
- </p>
197
- </div>
198
- <button
199
- type="button"
200
- :class="[
201
- 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
202
- form.inbound_email_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
203
- ]"
204
- @click="form.inbound_email_enabled = !form.inbound_email_enabled"
205
- >
206
- <span
207
- :class="[
208
- 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
209
- form.inbound_email_enabled ? 'translate-x-5' : 'translate-x-0',
210
- ]"
211
- />
212
- </button>
213
- </label>
214
-
215
- <template v-if="form.inbound_email_enabled">
216
- <div>
217
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
218
- >Email Provider</label
219
- >
220
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
221
- How inbound emails are received
222
- </p>
223
- <select
224
- v-model="form.inbound_email_adapter"
225
- class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
226
- >
227
- <option value="mailgun">Mailgun</option>
228
- <option value="postmark">Postmark</option>
229
- <option value="ses">AWS SES</option>
230
- <option value="imap">IMAP</option>
231
- </select>
232
- </div>
233
-
234
- <div>
235
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
236
- >Support Email Address</label
237
- >
238
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
239
- The email address customers will send emails to (e.g. support@example.com)
240
- </p>
241
- <input
242
- v-model="form.inbound_email_address"
243
- type="email"
244
- placeholder="support@example.com"
245
- class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
246
- />
247
- </div>
248
-
249
- <!-- Mailgun -->
250
- <template v-if="form.inbound_email_adapter === 'mailgun'">
251
- <div>
252
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
253
- >Signing Key</label
254
- >
255
- <input
256
- v-model="form.mailgun_signing_key"
257
- type="password"
258
- class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
259
- />
260
- </div>
261
- <div>
262
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
263
- >Webhook URL</label
264
- >
265
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
266
- Configure this URL in your Mailgun dashboard
267
- </p>
268
- <input
269
- :value="`${webhookBaseUrl}/mailgun`"
270
- type="text"
271
- readonly
272
- class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-tertiary)] select-all cursor-default focus:outline-none"
273
- />
274
- </div>
275
- </template>
276
-
277
- <!-- Postmark -->
278
- <template v-if="form.inbound_email_adapter === 'postmark'">
279
- <div>
280
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
281
- >Inbound Token</label
282
- >
283
- <input
284
- v-model="form.postmark_inbound_token"
285
- type="password"
286
- class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
287
- />
288
- </div>
289
- <div>
290
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
291
- >Webhook URL</label
292
- >
293
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
294
- Configure this URL in your Postmark server settings
295
- </p>
296
- <input
297
- :value="`${webhookBaseUrl}/postmark`"
298
- type="text"
299
- readonly
300
- class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-tertiary)] select-all cursor-default focus:outline-none"
301
- />
302
- </div>
303
- </template>
304
-
305
- <!-- AWS SES -->
306
- <template v-if="form.inbound_email_adapter === 'ses'">
307
- <div>
308
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
309
- >Region</label
310
- >
311
- <input
312
- v-model="form.ses_region"
313
- type="text"
314
- placeholder="us-east-1"
315
- class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
316
- />
317
- </div>
318
- <div>
319
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
320
- >Topic ARN</label
321
- >
322
- <input
323
- v-model="form.ses_topic_arn"
324
- type="text"
325
- placeholder="arn:aws:sns:us-east-1:123456789:ses-inbound"
326
- class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
327
- />
328
- </div>
329
- <div>
330
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
331
- >Webhook URL</label
332
- >
333
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
334
- Configure this URL as your SNS subscription endpoint
335
- </p>
336
- <input
337
- :value="`${webhookBaseUrl}/ses`"
338
- type="text"
339
- readonly
340
- class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-tertiary)] select-all cursor-default focus:outline-none"
341
- />
342
- </div>
343
- </template>
344
-
345
- <!-- IMAP -->
346
- <template v-if="form.inbound_email_adapter === 'imap'">
347
- <div>
348
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
349
- >Host</label
350
- >
351
- <input
352
- v-model="form.imap_host"
353
- type="text"
354
- placeholder="imap.example.com"
355
- class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
356
- />
357
- </div>
358
- <div class="flex items-end gap-4">
359
- <div>
360
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
361
- >Port</label
362
- >
363
- <input
364
- v-model.number="form.imap_port"
365
- type="number"
366
- min="1"
367
- max="65535"
368
- class="mt-2 w-24 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
369
- />
370
- </div>
371
- <div>
372
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
373
- >Encryption</label
374
- >
375
- <select
376
- v-model="form.imap_encryption"
377
- class="mt-2 w-28 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
378
- >
379
- <option value="ssl">SSL</option>
380
- <option value="tls">TLS</option>
381
- <option value="none">None</option>
382
- </select>
383
- </div>
384
- </div>
385
- <div>
386
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
387
- >Username</label
388
- >
389
- <input
390
- v-model="form.imap_username"
391
- type="text"
392
- class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
393
- />
394
- </div>
395
- <div>
396
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
397
- >Password</label
398
- >
399
- <input
400
- v-model="form.imap_password"
401
- type="password"
402
- class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
403
- />
404
- </div>
405
- <div>
406
- <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
407
- >Mailbox</label
408
- >
409
- <input
410
- v-model="form.imap_mailbox"
411
- type="text"
412
- placeholder="INBOX"
413
- class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
414
- />
415
- </div>
416
- </template>
417
- </template>
418
- </div>
419
- </div>
420
-
421
- <!-- Branding -->
422
- <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
423
- <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Branding</h3>
424
- <div class="space-y-5">
425
- <label class="flex items-center justify-between">
426
- <div>
427
- <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
428
- >Show "Powered by Escalated"</span
429
- >
430
- <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
431
- Display attribution badge in the support portal footer
432
- </p>
433
- </div>
434
- <button
435
- type="button"
436
- :class="[
437
- 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
438
- form.show_powered_by ? 'bg-emerald-500' : 'bg-neutral-700',
439
- ]"
440
- @click="form.show_powered_by = !form.show_powered_by"
441
- >
442
- <span
443
- :class="[
444
- 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
445
- form.show_powered_by ? 'translate-x-5' : 'translate-x-0',
446
- ]"
447
- />
448
- </button>
449
- </label>
450
- </div>
451
- </div>
452
-
453
- <PluginSlot slot="admin.settings.extra" :components="getPageComponents('admin.settings', 'extra')" />
454
-
455
- <!-- Submit -->
456
- <div class="flex items-center justify-end gap-3">
457
- <span v-if="form.recentlySuccessful" class="text-sm text-emerald-400">Saved.</span>
458
- <button
459
- type="submit"
460
- :disabled="form.processing"
461
- class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 py-2 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)] disabled:opacity-50"
462
- >
463
- {{ form.processing ? 'Saving...' : 'Save Settings' }}
464
- </button>
465
- </div>
466
- </form>
467
- </EscalatedLayout>
468
- </template>
1
+ <script setup>
2
+ import EscalatedLayout from '../../components/EscalatedLayout.vue';
3
+ import PluginSlot from '../../components/PluginSlot.vue';
4
+ import { useForm, usePage } from '@inertiajs/vue3';
5
+ import { computed } from 'vue';
6
+ import { usePluginExtensions } from '../../composables/usePluginExtensions';
7
+
8
+ const props = defineProps({ settings: Object, departments: Array });
9
+ const page = usePage();
10
+
11
+ const form = useForm({
12
+ guest_tickets_enabled: props.settings.guest_tickets_enabled,
13
+ allow_customer_close: props.settings.allow_customer_close,
14
+ auto_close_resolved_after_days: props.settings.auto_close_resolved_after_days,
15
+ max_attachments_per_reply: props.settings.max_attachments_per_reply,
16
+ max_attachment_size_kb: props.settings.max_attachment_size_kb,
17
+ ticket_reference_prefix: props.settings.ticket_reference_prefix,
18
+ inbound_email_enabled: props.settings.inbound_email_enabled ?? false,
19
+ inbound_email_adapter: props.settings.inbound_email_adapter ?? 'mailgun',
20
+ inbound_email_address: props.settings.inbound_email_address ?? '',
21
+ mailgun_signing_key: props.settings.mailgun_signing_key ?? '',
22
+ postmark_inbound_token: props.settings.postmark_inbound_token ?? '',
23
+ ses_region: props.settings.ses_region ?? '',
24
+ ses_topic_arn: props.settings.ses_topic_arn ?? '',
25
+ imap_host: props.settings.imap_host ?? '',
26
+ imap_port: props.settings.imap_port ?? 993,
27
+ imap_encryption: props.settings.imap_encryption ?? 'ssl',
28
+ imap_username: props.settings.imap_username ?? '',
29
+ imap_password: props.settings.imap_password ?? '',
30
+ imap_mailbox: props.settings.imap_mailbox ?? 'INBOX',
31
+ show_powered_by: props.settings.show_powered_by ?? true,
32
+ widget_enabled: props.settings.widget_enabled ?? false,
33
+ widget_color: props.settings.widget_color ?? '#4F46E5',
34
+ widget_position: props.settings.widget_position ?? 'bottom-right',
35
+ widget_greeting: props.settings.widget_greeting ?? 'Hi there! How can we help?',
36
+ widget_departments: props.settings.widget_departments ?? '',
37
+ knowledge_base_enabled: props.settings.knowledge_base_enabled ?? true,
38
+ knowledge_base_public: props.settings.knowledge_base_public ?? true,
39
+ knowledge_base_feedback_enabled: props.settings.knowledge_base_feedback_enabled ?? true,
40
+ // Chat settings
41
+ chat_enabled: props.settings.chat_enabled ?? false,
42
+ chat_sound_notifications: props.settings.chat_sound_notifications ?? true,
43
+ chat_pre_chat_name: props.settings.chat_pre_chat_name ?? true,
44
+ chat_pre_chat_email: props.settings.chat_pre_chat_email ?? true,
45
+ chat_pre_chat_department: props.settings.chat_pre_chat_department ?? false,
46
+ chat_routing_strategy: props.settings.chat_routing_strategy ?? 'round_robin',
47
+ chat_offline_behavior: props.settings.chat_offline_behavior ?? 'ticket_form',
48
+ chat_max_queue: props.settings.chat_max_queue ?? 10,
49
+ chat_concurrent_limit: props.settings.chat_concurrent_limit ?? 5,
50
+ chat_auto_close_minutes: props.settings.chat_auto_close_minutes ?? 30,
51
+ chat_queue_message: props.settings.chat_queue_message ?? '',
52
+ chat_offline_message: props.settings.chat_offline_message ?? '',
53
+ // Widget theme
54
+ widget_primary_color: props.settings.widget_primary_color ?? '#4F46E5',
55
+ widget_background_color: props.settings.widget_background_color ?? '#ffffff',
56
+ widget_text_color: props.settings.widget_text_color ?? '#1f2937',
57
+ widget_agent_bubble_color: props.settings.widget_agent_bubble_color ?? '#4F46E5',
58
+ widget_customer_bubble_color: props.settings.widget_customer_bubble_color ?? '#f3f4f6',
59
+ widget_header_background: props.settings.widget_header_background ?? '#4F46E5',
60
+ widget_font_family: props.settings.widget_font_family ?? 'system',
61
+ widget_border_radius: props.settings.widget_border_radius ?? 12,
62
+ widget_launcher_size: props.settings.widget_launcher_size ?? 'medium',
63
+ widget_show_branding: props.settings.widget_show_branding ?? true,
64
+ widget_custom_css: props.settings.widget_custom_css ?? '',
65
+ widget_pre_chat_message: props.settings.widget_pre_chat_message ?? '',
66
+ widget_chat_header_text: props.settings.widget_chat_header_text ?? 'Support',
67
+ });
68
+
69
+ const webhookBaseUrl = computed(() => {
70
+ const prefix = page.props.escalated?.prefix || 'support';
71
+ const origin = typeof window !== 'undefined' ? window.location.origin : '';
72
+ return `${origin}/${prefix}/inbound`;
73
+ });
74
+
75
+ const { getPageComponents } = usePluginExtensions();
76
+
77
+ function submit() {
78
+ form.post(route('escalated.admin.settings.update'));
79
+ }
80
+ </script>
81
+
82
+ <template>
83
+ <EscalatedLayout title="Settings">
84
+ <form class="mx-auto max-w-2xl space-y-6" @submit.prevent="submit">
85
+ <!-- General -->
86
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
87
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">General</h3>
88
+ <div class="space-y-5">
89
+ <label class="flex items-center justify-between">
90
+ <div>
91
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
92
+ >Guest Tickets</span
93
+ >
94
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
95
+ Allow visitors to submit tickets without signing in
96
+ </p>
97
+ </div>
98
+ <button
99
+ type="button"
100
+ :class="[
101
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
102
+ form.guest_tickets_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
103
+ ]"
104
+ @click="form.guest_tickets_enabled = !form.guest_tickets_enabled"
105
+ >
106
+ <span
107
+ :class="[
108
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
109
+ form.guest_tickets_enabled ? 'translate-x-5' : 'translate-x-0',
110
+ ]"
111
+ />
112
+ </button>
113
+ </label>
114
+
115
+ <label class="flex items-center justify-between">
116
+ <div>
117
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
118
+ >Allow Customer Close</span
119
+ >
120
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
121
+ Let customers close their own tickets
122
+ </p>
123
+ </div>
124
+ <button
125
+ type="button"
126
+ :class="[
127
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
128
+ form.allow_customer_close ? 'bg-emerald-500' : 'bg-neutral-700',
129
+ ]"
130
+ @click="form.allow_customer_close = !form.allow_customer_close"
131
+ >
132
+ <span
133
+ :class="[
134
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
135
+ form.allow_customer_close ? 'translate-x-5' : 'translate-x-0',
136
+ ]"
137
+ />
138
+ </button>
139
+ </label>
140
+
141
+ <div>
142
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
143
+ >Auto-close resolved tickets after</label
144
+ >
145
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
146
+ Days after resolution before auto-closing (0 to disable)
147
+ </p>
148
+ <div class="mt-2 flex items-center gap-2">
149
+ <input
150
+ v-model.number="form.auto_close_resolved_after_days"
151
+ type="number"
152
+ min="0"
153
+ max="365"
154
+ class="w-24 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
155
+ />
156
+ <span class="text-sm text-[var(--esc-panel-text-muted)]">days</span>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+
162
+ <!-- Limits -->
163
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
164
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Limits</h3>
165
+ <div class="space-y-5">
166
+ <div>
167
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
168
+ >Max attachments per reply</label
169
+ >
170
+ <input
171
+ v-model.number="form.max_attachments_per_reply"
172
+ type="number"
173
+ min="1"
174
+ max="20"
175
+ class="mt-2 w-24 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
176
+ />
177
+ </div>
178
+ <div>
179
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
180
+ >Max attachment size</label
181
+ >
182
+ <div class="mt-2 flex items-center gap-2">
183
+ <input
184
+ v-model.number="form.max_attachment_size_kb"
185
+ type="number"
186
+ min="512"
187
+ max="102400"
188
+ step="512"
189
+ class="w-32 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
190
+ />
191
+ <span class="text-sm text-[var(--esc-panel-text-muted)]"
192
+ >KB ({{ Math.round(form.max_attachment_size_kb / 1024) }} MB)</span
193
+ >
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+
199
+ <!-- Tickets -->
200
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
201
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Tickets</h3>
202
+ <div class="space-y-5">
203
+ <div>
204
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
205
+ >Reference Prefix</label
206
+ >
207
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
208
+ Prefix for ticket references (e.g. ESC produces ESC-00001)
209
+ </p>
210
+ <input
211
+ v-model="form.ticket_reference_prefix"
212
+ type="text"
213
+ maxlength="10"
214
+ class="mt-2 w-32 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] uppercase focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
215
+ />
216
+ </div>
217
+ </div>
218
+ </div>
219
+
220
+ <!-- Inbound Email -->
221
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
222
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Inbound Email</h3>
223
+ <div class="space-y-5">
224
+ <label class="flex items-center justify-between">
225
+ <div>
226
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
227
+ >Inbound Email</span
228
+ >
229
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
230
+ Allow creating and replying to tickets via email
231
+ </p>
232
+ </div>
233
+ <button
234
+ type="button"
235
+ :class="[
236
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
237
+ form.inbound_email_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
238
+ ]"
239
+ @click="form.inbound_email_enabled = !form.inbound_email_enabled"
240
+ >
241
+ <span
242
+ :class="[
243
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
244
+ form.inbound_email_enabled ? 'translate-x-5' : 'translate-x-0',
245
+ ]"
246
+ />
247
+ </button>
248
+ </label>
249
+
250
+ <template v-if="form.inbound_email_enabled">
251
+ <div>
252
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
253
+ >Email Provider</label
254
+ >
255
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
256
+ How inbound emails are received
257
+ </p>
258
+ <select
259
+ v-model="form.inbound_email_adapter"
260
+ class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
261
+ >
262
+ <option value="mailgun">Mailgun</option>
263
+ <option value="postmark">Postmark</option>
264
+ <option value="ses">AWS SES</option>
265
+ <option value="imap">IMAP</option>
266
+ </select>
267
+ </div>
268
+
269
+ <div>
270
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
271
+ >Support Email Address</label
272
+ >
273
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
274
+ The email address customers will send emails to (e.g. support@example.com)
275
+ </p>
276
+ <input
277
+ v-model="form.inbound_email_address"
278
+ type="email"
279
+ placeholder="support@example.com"
280
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
281
+ />
282
+ </div>
283
+
284
+ <!-- Mailgun -->
285
+ <template v-if="form.inbound_email_adapter === 'mailgun'">
286
+ <div>
287
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
288
+ >Signing Key</label
289
+ >
290
+ <input
291
+ v-model="form.mailgun_signing_key"
292
+ type="password"
293
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
294
+ />
295
+ </div>
296
+ <div>
297
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
298
+ >Webhook URL</label
299
+ >
300
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
301
+ Configure this URL in your Mailgun dashboard
302
+ </p>
303
+ <input
304
+ :value="`${webhookBaseUrl}/mailgun`"
305
+ type="text"
306
+ readonly
307
+ class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-tertiary)] select-all cursor-default focus:outline-none"
308
+ />
309
+ </div>
310
+ </template>
311
+
312
+ <!-- Postmark -->
313
+ <template v-if="form.inbound_email_adapter === 'postmark'">
314
+ <div>
315
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
316
+ >Inbound Token</label
317
+ >
318
+ <input
319
+ v-model="form.postmark_inbound_token"
320
+ type="password"
321
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
322
+ />
323
+ </div>
324
+ <div>
325
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
326
+ >Webhook URL</label
327
+ >
328
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
329
+ Configure this URL in your Postmark server settings
330
+ </p>
331
+ <input
332
+ :value="`${webhookBaseUrl}/postmark`"
333
+ type="text"
334
+ readonly
335
+ class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-tertiary)] select-all cursor-default focus:outline-none"
336
+ />
337
+ </div>
338
+ </template>
339
+
340
+ <!-- AWS SES -->
341
+ <template v-if="form.inbound_email_adapter === 'ses'">
342
+ <div>
343
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
344
+ >Region</label
345
+ >
346
+ <input
347
+ v-model="form.ses_region"
348
+ type="text"
349
+ placeholder="us-east-1"
350
+ class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
351
+ />
352
+ </div>
353
+ <div>
354
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
355
+ >Topic ARN</label
356
+ >
357
+ <input
358
+ v-model="form.ses_topic_arn"
359
+ type="text"
360
+ placeholder="arn:aws:sns:us-east-1:123456789:ses-inbound"
361
+ class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
362
+ />
363
+ </div>
364
+ <div>
365
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
366
+ >Webhook URL</label
367
+ >
368
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
369
+ Configure this URL as your SNS subscription endpoint
370
+ </p>
371
+ <input
372
+ :value="`${webhookBaseUrl}/ses`"
373
+ type="text"
374
+ readonly
375
+ class="mt-2 w-full max-w-lg rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-tertiary)] select-all cursor-default focus:outline-none"
376
+ />
377
+ </div>
378
+ </template>
379
+
380
+ <!-- IMAP -->
381
+ <template v-if="form.inbound_email_adapter === 'imap'">
382
+ <div>
383
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
384
+ >Host</label
385
+ >
386
+ <input
387
+ v-model="form.imap_host"
388
+ type="text"
389
+ placeholder="imap.example.com"
390
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
391
+ />
392
+ </div>
393
+ <div class="flex items-end gap-4">
394
+ <div>
395
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
396
+ >Port</label
397
+ >
398
+ <input
399
+ v-model.number="form.imap_port"
400
+ type="number"
401
+ min="1"
402
+ max="65535"
403
+ class="mt-2 w-24 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
404
+ />
405
+ </div>
406
+ <div>
407
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
408
+ >Encryption</label
409
+ >
410
+ <select
411
+ v-model="form.imap_encryption"
412
+ class="mt-2 w-28 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
413
+ >
414
+ <option value="ssl">SSL</option>
415
+ <option value="tls">TLS</option>
416
+ <option value="none">None</option>
417
+ </select>
418
+ </div>
419
+ </div>
420
+ <div>
421
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
422
+ >Username</label
423
+ >
424
+ <input
425
+ v-model="form.imap_username"
426
+ type="text"
427
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
428
+ />
429
+ </div>
430
+ <div>
431
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
432
+ >Password</label
433
+ >
434
+ <input
435
+ v-model="form.imap_password"
436
+ type="password"
437
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
438
+ />
439
+ </div>
440
+ <div>
441
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
442
+ >Mailbox</label
443
+ >
444
+ <input
445
+ v-model="form.imap_mailbox"
446
+ type="text"
447
+ placeholder="INBOX"
448
+ class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
449
+ />
450
+ </div>
451
+ </template>
452
+ </template>
453
+ </div>
454
+ </div>
455
+
456
+ <!-- Knowledge Base -->
457
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
458
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Knowledge Base</h3>
459
+ <div class="space-y-5">
460
+ <label class="flex items-center justify-between">
461
+ <div>
462
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
463
+ >Enable Knowledge Base</span
464
+ >
465
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
466
+ Show the knowledge base to customers and visitors
467
+ </p>
468
+ </div>
469
+ <button
470
+ type="button"
471
+ :class="[
472
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
473
+ form.knowledge_base_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
474
+ ]"
475
+ @click="form.knowledge_base_enabled = !form.knowledge_base_enabled"
476
+ >
477
+ <span
478
+ :class="[
479
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
480
+ form.knowledge_base_enabled ? 'translate-x-5' : 'translate-x-0',
481
+ ]"
482
+ />
483
+ </button>
484
+ </label>
485
+ <label class="flex items-center justify-between">
486
+ <div>
487
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
488
+ >Public Access</span
489
+ >
490
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
491
+ Allow unauthenticated visitors to browse the knowledge base
492
+ </p>
493
+ </div>
494
+ <button
495
+ type="button"
496
+ :class="[
497
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
498
+ form.knowledge_base_public ? 'bg-emerald-500' : 'bg-neutral-700',
499
+ ]"
500
+ @click="form.knowledge_base_public = !form.knowledge_base_public"
501
+ >
502
+ <span
503
+ :class="[
504
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
505
+ form.knowledge_base_public ? 'translate-x-5' : 'translate-x-0',
506
+ ]"
507
+ />
508
+ </button>
509
+ </label>
510
+ <label class="flex items-center justify-between">
511
+ <div>
512
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
513
+ >Article Feedback</span
514
+ >
515
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
516
+ Show helpful / not helpful buttons on articles
517
+ </p>
518
+ </div>
519
+ <button
520
+ type="button"
521
+ :class="[
522
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
523
+ form.knowledge_base_feedback_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
524
+ ]"
525
+ @click="form.knowledge_base_feedback_enabled = !form.knowledge_base_feedback_enabled"
526
+ >
527
+ <span
528
+ :class="[
529
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
530
+ form.knowledge_base_feedback_enabled ? 'translate-x-5' : 'translate-x-0',
531
+ ]"
532
+ />
533
+ </button>
534
+ </label>
535
+ </div>
536
+ </div>
537
+
538
+ <!-- Branding -->
539
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
540
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Branding</h3>
541
+ <div class="space-y-5">
542
+ <label class="flex items-center justify-between">
543
+ <div>
544
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
545
+ >Show "Powered by Escalated"</span
546
+ >
547
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
548
+ Display attribution badge in the support portal footer
549
+ </p>
550
+ </div>
551
+ <button
552
+ type="button"
553
+ :class="[
554
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
555
+ form.show_powered_by ? 'bg-emerald-500' : 'bg-neutral-700',
556
+ ]"
557
+ @click="form.show_powered_by = !form.show_powered_by"
558
+ >
559
+ <span
560
+ :class="[
561
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
562
+ form.show_powered_by ? 'translate-x-5' : 'translate-x-0',
563
+ ]"
564
+ />
565
+ </button>
566
+ </label>
567
+ </div>
568
+ </div>
569
+
570
+ <!-- Support Widget -->
571
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
572
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Support Widget</h3>
573
+ <div class="space-y-5">
574
+ <label class="flex items-center justify-between">
575
+ <div>
576
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
577
+ >Enable Widget</span
578
+ >
579
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
580
+ Show an embeddable support widget on your site
581
+ </p>
582
+ </div>
583
+ <button
584
+ type="button"
585
+ :class="[
586
+ 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
587
+ form.widget_enabled ? 'bg-emerald-500' : 'bg-neutral-700',
588
+ ]"
589
+ @click="form.widget_enabled = !form.widget_enabled"
590
+ >
591
+ <span
592
+ :class="[
593
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
594
+ form.widget_enabled ? 'translate-x-5' : 'translate-x-0',
595
+ ]"
596
+ />
597
+ </button>
598
+ </label>
599
+ <template v-if="form.widget_enabled">
600
+ <div>
601
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
602
+ >Widget Color</label
603
+ >
604
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
605
+ Primary color for the widget button and header
606
+ </p>
607
+ <div class="mt-2 flex items-center gap-3">
608
+ <input
609
+ v-model="form.widget_color"
610
+ type="color"
611
+ class="h-9 w-12 cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] p-0.5"
612
+ />
613
+ <input
614
+ v-model="form.widget_color"
615
+ type="text"
616
+ maxlength="7"
617
+ class="w-28 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] uppercase focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
618
+ />
619
+ </div>
620
+ </div>
621
+ <div>
622
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
623
+ >Position</label
624
+ >
625
+ <select
626
+ v-model="form.widget_position"
627
+ class="mt-2 w-48 rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
628
+ >
629
+ <option value="bottom-right">Bottom Right</option>
630
+ <option value="bottom-left">Bottom Left</option>
631
+ </select>
632
+ </div>
633
+ <div>
634
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
635
+ >Greeting Message</label
636
+ >
637
+ <input
638
+ v-model="form.widget_greeting"
639
+ type="text"
640
+ maxlength="255"
641
+ class="mt-2 w-full max-w-sm rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
642
+ />
643
+ </div>
644
+ <div v-if="departments?.length">
645
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
646
+ >Departments</label
647
+ >
648
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
649
+ Select which departments appear in the widget (leave empty for all)
650
+ </p>
651
+ <div class="mt-2 space-y-2">
652
+ <label v-for="dept in departments" :key="dept.id" class="flex items-center gap-2">
653
+ <input
654
+ type="checkbox"
655
+ :value="dept.id"
656
+ :checked="
657
+ form.widget_departments.split(',').filter(Boolean).includes(String(dept.id))
658
+ "
659
+ class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] text-emerald-500 focus:ring-emerald-500"
660
+ @change="
661
+ (e) => {
662
+ const ids = form.widget_departments.split(',').filter(Boolean);
663
+ if (e.target.checked) {
664
+ ids.push(String(dept.id));
665
+ } else {
666
+ const idx = ids.indexOf(String(dept.id));
667
+ if (idx > -1) ids.splice(idx, 1);
668
+ }
669
+ form.widget_departments = ids.join(',');
670
+ }
671
+ "
672
+ />
673
+ <span class="text-sm text-[var(--esc-panel-text-secondary)]">{{ dept.name }}</span>
674
+ </label>
675
+ </div>
676
+ </div>
677
+ <div
678
+ class="rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface-alt)] p-4"
679
+ >
680
+ <label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
681
+ >Embed Code</label
682
+ >
683
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
684
+ Add this snippet to your website to show the support widget
685
+ </p>
686
+ <code
687
+ class="mt-2 block whitespace-pre-wrap break-all rounded-lg bg-[var(--esc-panel-bg)] p-3 text-xs text-[var(--esc-panel-text-tertiary)] select-all"
688
+ >&lt;script src="/{{ page.props.escalated?.prefix || 'support' }}/widget/embed.js"
689
+ data-color="{{ form.widget_color }}" data-position="{{
690
+ form.widget_position
691
+ }}"&gt;&lt;/script&gt;</code
692
+ >
693
+ </div>
694
+ </template>
695
+ </div>
696
+ </div>
697
+
698
+ <!-- Live Chat -->
699
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
700
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Live Chat</h3>
701
+ <div class="space-y-5">
702
+ <label class="flex items-center justify-between">
703
+ <div>
704
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
705
+ >Enable Live Chat</span
706
+ >
707
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
708
+ Allow customers to start real-time chat sessions
709
+ </p>
710
+ </div>
711
+ <input
712
+ v-model="form.chat_enabled"
713
+ type="checkbox"
714
+ :true-value="true"
715
+ :false-value="false"
716
+ class="h-4 w-4 rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] text-[var(--esc-panel-accent)] focus:ring-[var(--esc-panel-accent)]/20"
717
+ />
718
+ </label>
719
+ <label class="flex items-center justify-between">
720
+ <div>
721
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]"
722
+ >Sound Notifications</span
723
+ >
724
+ <p class="mt-0.5 text-xs text-[var(--esc-panel-text-muted)]">
725
+ Play sound on new chat messages
726
+ </p>
727
+ </div>
728
+ <input
729
+ v-model="form.chat_sound_notifications"
730
+ type="checkbox"
731
+ :true-value="true"
732
+ :false-value="false"
733
+ class="h-4 w-4 rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] text-[var(--esc-panel-accent)] focus:ring-[var(--esc-panel-accent)]/20"
734
+ />
735
+ </label>
736
+ </div>
737
+ </div>
738
+
739
+ <!-- Chat Routing -->
740
+ <div
741
+ v-if="form.chat_enabled"
742
+ class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
743
+ >
744
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Chat Routing</h3>
745
+ <div class="space-y-5">
746
+ <div>
747
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
748
+ >Routing Strategy</label
749
+ >
750
+ <select
751
+ v-model="form.chat_routing_strategy"
752
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)]"
753
+ >
754
+ <option value="round_robin">Round Robin</option>
755
+ <option value="least_active">Least Active</option>
756
+ <option value="manual">Manual (Queue)</option>
757
+ </select>
758
+ </div>
759
+ <div>
760
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
761
+ >Offline Behavior</label
762
+ >
763
+ <select
764
+ v-model="form.chat_offline_behavior"
765
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)]"
766
+ >
767
+ <option value="ticket_form">Show Ticket Form</option>
768
+ <option value="offline_message">Show Offline Message</option>
769
+ <option value="hide">Hide Chat</option>
770
+ </select>
771
+ </div>
772
+ <div class="grid grid-cols-2 gap-4">
773
+ <div>
774
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
775
+ >Max Queue Size</label
776
+ >
777
+ <input
778
+ v-model.number="form.chat_max_queue"
779
+ type="number"
780
+ min="1"
781
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)]"
782
+ />
783
+ </div>
784
+ <div>
785
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
786
+ >Concurrent Limit</label
787
+ >
788
+ <input
789
+ v-model.number="form.chat_concurrent_limit"
790
+ type="number"
791
+ min="1"
792
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)]"
793
+ />
794
+ </div>
795
+ </div>
796
+ <div>
797
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
798
+ >Auto-close after (minutes)</label
799
+ >
800
+ <input
801
+ v-model.number="form.chat_auto_close_minutes"
802
+ type="number"
803
+ min="1"
804
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)]"
805
+ />
806
+ </div>
807
+ <div>
808
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
809
+ >Queue Message</label
810
+ >
811
+ <input
812
+ v-model="form.chat_queue_message"
813
+ type="text"
814
+ placeholder="You're in the queue..."
815
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)]"
816
+ />
817
+ </div>
818
+ <div>
819
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
820
+ >Offline Message</label
821
+ >
822
+ <input
823
+ v-model="form.chat_offline_message"
824
+ type="text"
825
+ placeholder="Sorry, we're not available right now..."
826
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)]"
827
+ />
828
+ </div>
829
+ </div>
830
+ </div>
831
+
832
+ <!-- Widget Theme -->
833
+ <div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
834
+ <h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Widget Theme</h3>
835
+ <div class="space-y-5">
836
+ <div class="grid grid-cols-2 gap-4">
837
+ <div>
838
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
839
+ >Primary Color</label
840
+ >
841
+ <input
842
+ v-model="form.widget_primary_color"
843
+ type="color"
844
+ class="h-10 w-full cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)]"
845
+ />
846
+ </div>
847
+ <div>
848
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
849
+ >Header Background</label
850
+ >
851
+ <input
852
+ v-model="form.widget_header_background"
853
+ type="color"
854
+ class="h-10 w-full cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)]"
855
+ />
856
+ </div>
857
+ <div>
858
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
859
+ >Agent Bubble Color</label
860
+ >
861
+ <input
862
+ v-model="form.widget_agent_bubble_color"
863
+ type="color"
864
+ class="h-10 w-full cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)]"
865
+ />
866
+ </div>
867
+ <div>
868
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
869
+ >Customer Bubble Color</label
870
+ >
871
+ <input
872
+ v-model="form.widget_customer_bubble_color"
873
+ type="color"
874
+ class="h-10 w-full cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)]"
875
+ />
876
+ </div>
877
+ <div>
878
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
879
+ >Background Color</label
880
+ >
881
+ <input
882
+ v-model="form.widget_background_color"
883
+ type="color"
884
+ class="h-10 w-full cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)]"
885
+ />
886
+ </div>
887
+ <div>
888
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
889
+ >Text Color</label
890
+ >
891
+ <input
892
+ v-model="form.widget_text_color"
893
+ type="color"
894
+ class="h-10 w-full cursor-pointer rounded-lg border border-[var(--esc-panel-border-input)]"
895
+ />
896
+ </div>
897
+ </div>
898
+ <div>
899
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
900
+ >Font Family</label
901
+ >
902
+ <select
903
+ v-model="form.widget_font_family"
904
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)]"
905
+ >
906
+ <option value="system">System Default</option>
907
+ <option value="Inter">Inter</option>
908
+ <option value="Roboto">Roboto</option>
909
+ <option value="Open Sans">Open Sans</option>
910
+ <option value="Lato">Lato</option>
911
+ <option value="Poppins">Poppins</option>
912
+ <option value="Nunito">Nunito</option>
913
+ </select>
914
+ </div>
915
+ <div>
916
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
917
+ >Border Radius ({{ form.widget_border_radius }}px)</label
918
+ >
919
+ <input
920
+ v-model.number="form.widget_border_radius"
921
+ type="range"
922
+ min="0"
923
+ max="20"
924
+ class="w-full"
925
+ />
926
+ </div>
927
+ <div>
928
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
929
+ >Launcher Size</label
930
+ >
931
+ <div class="flex gap-3">
932
+ <label
933
+ v-for="size in ['small', 'medium', 'large']"
934
+ :key="size"
935
+ class="flex items-center gap-2"
936
+ >
937
+ <input
938
+ v-model="form.widget_launcher_size"
939
+ type="radio"
940
+ :value="size"
941
+ class="text-[var(--esc-panel-accent)] focus:ring-[var(--esc-panel-accent)]/20"
942
+ />
943
+ <span class="text-sm capitalize text-[var(--esc-panel-text-secondary)]">{{
944
+ size
945
+ }}</span>
946
+ </label>
947
+ </div>
948
+ </div>
949
+ <label class="flex items-center justify-between">
950
+ <span class="text-sm font-medium text-[var(--esc-panel-text-secondary)]">Show Branding</span>
951
+ <input
952
+ v-model="form.widget_show_branding"
953
+ type="checkbox"
954
+ :true-value="true"
955
+ :false-value="false"
956
+ class="h-4 w-4 rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] text-[var(--esc-panel-accent)] focus:ring-[var(--esc-panel-accent)]/20"
957
+ />
958
+ </label>
959
+ <div>
960
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
961
+ >Pre-chat Message</label
962
+ >
963
+ <input
964
+ v-model="form.widget_pre_chat_message"
965
+ type="text"
966
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)]"
967
+ />
968
+ </div>
969
+ <div>
970
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
971
+ >Chat Header Text</label
972
+ >
973
+ <input
974
+ v-model="form.widget_chat_header_text"
975
+ type="text"
976
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)]"
977
+ />
978
+ </div>
979
+ <div>
980
+ <label class="mb-1 block text-sm font-medium text-[var(--esc-panel-text-secondary)]"
981
+ >Custom CSS</label
982
+ >
983
+ <textarea
984
+ v-model="form.widget_custom_css"
985
+ rows="4"
986
+ placeholder=".esc-w-panel { /* custom styles */ }"
987
+ class="w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 font-mono text-xs text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)]"
988
+ ></textarea>
989
+ </div>
990
+ </div>
991
+ </div>
992
+
993
+ <PluginSlot slot="admin.settings.extra" :components="getPageComponents('admin.settings', 'extra')" />
994
+
995
+ <!-- Submit -->
996
+ <div class="flex items-center justify-end gap-3">
997
+ <span v-if="form.recentlySuccessful" class="text-sm text-emerald-400">Saved.</span>
998
+ <button
999
+ type="submit"
1000
+ :disabled="form.processing"
1001
+ class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 py-2 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)] disabled:opacity-50"
1002
+ >
1003
+ {{ form.processing ? 'Saving...' : 'Save Settings' }}
1004
+ </button>
1005
+ </div>
1006
+ </form>
1007
+ </EscalatedLayout>
1008
+ </template>