@escalated-dev/escalated 0.7.0 → 0.7.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 (179) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +31 -0
  3. package/package.json +12 -6
  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 +29 -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 +79 -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 +80 -80
  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/ar.json +727 -0
  91. package/src/locales/de.json +678 -430
  92. package/src/locales/en.json +717 -455
  93. package/src/locales/es.json +679 -430
  94. package/src/locales/fr.json +680 -431
  95. package/src/locales/index.js +12 -2
  96. package/src/locales/it.json +727 -0
  97. package/src/locales/ja.json +727 -0
  98. package/src/locales/ko.json +727 -0
  99. package/src/locales/nl.json +727 -0
  100. package/src/locales/pl.json +727 -0
  101. package/src/locales/pt-BR.json +727 -0
  102. package/src/locales/ru.json +727 -0
  103. package/src/locales/tr.json +727 -0
  104. package/src/locales/zh-CN.json +727 -0
  105. package/src/pages/Admin/ApiTokens/Index.vue +381 -381
  106. package/src/pages/Admin/AuditLog/Index.vue +154 -154
  107. package/src/pages/Admin/BusinessHours/Form.vue +172 -172
  108. package/src/pages/Admin/BusinessHours/Index.vue +113 -113
  109. package/src/pages/Admin/CannedResponses/Index.vue +127 -127
  110. package/src/pages/Admin/Capacity/Index.vue +134 -134
  111. package/src/pages/Admin/CustomFields/Form.vue +195 -195
  112. package/src/pages/Admin/CustomFields/Index.vue +134 -134
  113. package/src/pages/Admin/CustomObjects/Form.vue +202 -202
  114. package/src/pages/Admin/CustomObjects/Index.vue +127 -127
  115. package/src/pages/Admin/CustomObjects/Records.vue +228 -228
  116. package/src/pages/Admin/Departments/Form.vue +75 -75
  117. package/src/pages/Admin/Departments/Index.vue +110 -109
  118. package/src/pages/Admin/EscalationRules/Form.vue +157 -157
  119. package/src/pages/Admin/EscalationRules/Index.vue +178 -178
  120. package/src/pages/Admin/Import/Connect.vue +0 -1
  121. package/src/pages/Admin/Import/Index.vue +1 -1
  122. package/src/pages/Admin/Import/Progress.vue +1 -1
  123. package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -142
  124. package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -195
  125. package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -193
  126. package/src/pages/Admin/Macros/Index.vue +448 -448
  127. package/src/pages/Admin/Plugins/Index.vue +327 -327
  128. package/src/pages/Admin/Reports/AgentDetail.vue +132 -0
  129. package/src/pages/Admin/Reports/AgentMetrics.vue +115 -115
  130. package/src/pages/Admin/Reports/AgentRanking.vue +246 -0
  131. package/src/pages/Admin/Reports/Cohorts.vue +180 -0
  132. package/src/pages/Admin/Reports/Comparison.vue +141 -0
  133. package/src/pages/Admin/Reports/CsatReport.vue +127 -127
  134. package/src/pages/Admin/Reports/Dashboard.vue +36 -21
  135. package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -0
  136. package/src/pages/Admin/Reports/ResponseTimes.vue +164 -0
  137. package/src/pages/Admin/Reports/SlaReport.vue +162 -166
  138. package/src/pages/Admin/Reports/SlaTrends.vue +183 -0
  139. package/src/pages/Admin/Reports.vue +206 -206
  140. package/src/pages/Admin/Roles/Form.vue +71 -71
  141. package/src/pages/Admin/Roles/Index.vue +122 -122
  142. package/src/pages/Admin/Settings/CsatSettings.vue +165 -165
  143. package/src/pages/Admin/Settings/DataRetention.vue +182 -182
  144. package/src/pages/Admin/Settings/EmailSettings.vue +243 -243
  145. package/src/pages/Admin/Settings/Sandbox.vue +135 -135
  146. package/src/pages/Admin/Settings/SsoSettings.vue +218 -218
  147. package/src/pages/Admin/Settings/TwoFactor.vue +110 -110
  148. package/src/pages/Admin/Settings.vue +1008 -468
  149. package/src/pages/Admin/Skills/Form.vue +53 -53
  150. package/src/pages/Admin/Skills/Index.vue +94 -94
  151. package/src/pages/Admin/SlaPolicies/Form.vue +118 -118
  152. package/src/pages/Admin/SlaPolicies/Index.vue +121 -120
  153. package/src/pages/Admin/Statuses/Form.vue +114 -114
  154. package/src/pages/Admin/Statuses/Index.vue +153 -153
  155. package/src/pages/Admin/Tags/Index.vue +191 -191
  156. package/src/pages/Admin/Tickets/Index.vue +126 -91
  157. package/src/pages/Admin/Tickets/Show.vue +29 -0
  158. package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -120
  159. package/src/pages/Admin/Webhooks/Form.vue +110 -110
  160. package/src/pages/Admin/Webhooks/Index.vue +136 -136
  161. package/src/pages/Admin/Workflows/Builder.vue +207 -0
  162. package/src/pages/Admin/Workflows/Index.vue +235 -0
  163. package/src/pages/Admin/Workflows/Logs.vue +286 -0
  164. package/src/pages/Agent/TicketIndex.vue +112 -88
  165. package/src/pages/Agent/TicketShow.vue +192 -60
  166. package/src/pages/Customer/Create.vue +1 -1
  167. package/src/pages/Customer/Index.vue +31 -31
  168. package/src/pages/Customer/KnowledgeBase/Article.vue +4 -2
  169. package/src/pages/Customer/KnowledgeBase/Index.vue +151 -151
  170. package/src/pages/Customer/Show.vue +69 -63
  171. package/src/pages/Guest/Create.vue +40 -15
  172. package/src/pages/Guest/Show.vue +124 -93
  173. package/src/plugin.js +25 -0
  174. package/src/utils/mentionHighlight.js +24 -0
  175. package/src/utils/sanitizeHtml.js +78 -64
  176. package/src/widget/EscalatedWidget.vue +804 -0
  177. package/src/widget/index.js +254 -0
  178. package/src/pages/Admin/Automations/Form.vue +0 -184
  179. 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>