@escalated-dev/escalated 0.5.0 → 0.7.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 (136) hide show
  1. package/README.md +102 -0
  2. package/package.json +37 -6
  3. package/src/components/ActivityTimeline.stories.js +55 -0
  4. package/src/components/ActivityTimeline.vue +83 -66
  5. package/src/components/AdminDashboard.stories.js +1095 -0
  6. package/src/components/AgentLoadIndicator.stories.js +37 -0
  7. package/src/components/AgentLoadIndicator.vue +49 -0
  8. package/src/components/AgentPerformanceCard.vue +44 -0
  9. package/src/components/ArticleEditor.vue +82 -0
  10. package/src/components/AssigneeSelect.vue +37 -23
  11. package/src/components/AttachmentList.vue +53 -37
  12. package/src/components/AuditLogEntry.vue +95 -0
  13. package/src/components/BulkActionBar.vue +99 -30
  14. package/src/components/CategoryTree.vue +89 -0
  15. package/src/components/ChartWidget.vue +46 -0
  16. package/src/components/CollisionWarning.stories.js +30 -0
  17. package/src/components/CollisionWarning.vue +68 -0
  18. package/src/components/ConditionalFieldRules.vue +135 -0
  19. package/src/components/ContextPanel.vue +66 -0
  20. package/src/components/ContextPanelSection.vue +65 -0
  21. package/src/components/CustomFieldRenderer.vue +159 -0
  22. package/src/components/EscalatedLayout.vue +400 -69
  23. package/src/components/FileDropzone.vue +57 -41
  24. package/src/components/FollowButton.stories.js +30 -0
  25. package/src/components/FollowButton.vue +53 -28
  26. package/src/components/KeyboardShortcutHelp.vue +129 -39
  27. package/src/components/KnowledgePanel.vue +122 -0
  28. package/src/components/KpiCard.stories.js +32 -0
  29. package/src/components/KpiCard.vue +88 -0
  30. package/src/components/MacroDropdown.vue +95 -35
  31. package/src/components/PermissionMatrix.vue +107 -0
  32. package/src/components/PinnedNotes.vue +75 -33
  33. package/src/components/PresenceIndicator.vue +93 -27
  34. package/src/components/PriorityBadge.stories.js +33 -0
  35. package/src/components/PriorityBadge.vue +46 -39
  36. package/src/components/QuickFilters.vue +65 -49
  37. package/src/components/ReplyComposer.vue +273 -159
  38. package/src/components/ReplyThread.vue +78 -23
  39. package/src/components/SatisfactionRating.vue +98 -46
  40. package/src/components/ScheduleEditor.vue +104 -0
  41. package/src/components/SideConversation.vue +165 -0
  42. package/src/components/SideConversationList.vue +147 -0
  43. package/src/components/SkillTagManager.vue +79 -0
  44. package/src/components/SlaComplianceChart.vue +53 -0
  45. package/src/components/SlaTimer.stories.js +35 -0
  46. package/src/components/SlaTimer.vue +62 -49
  47. package/src/components/StatsCard.stories.js +24 -0
  48. package/src/components/StatsCard.vue +58 -46
  49. package/src/components/StatusBadge.stories.js +53 -0
  50. package/src/components/StatusBadge.vue +78 -46
  51. package/src/components/TagSelect.vue +84 -53
  52. package/src/components/TicketFilters.vue +225 -69
  53. package/src/components/TicketLinkPanel.vue +193 -0
  54. package/src/components/TicketList.vue +594 -193
  55. package/src/components/TicketMergeDialog.vue +157 -0
  56. package/src/components/TicketSidebar.vue +167 -99
  57. package/src/components/TicketTypeSelector.vue +21 -0
  58. package/src/components/TotpInput.stories.js +9 -0
  59. package/src/components/TotpInput.vue +81 -0
  60. package/src/components/TwoFactorSetup.vue +113 -0
  61. package/src/composables/useDebouncedSearch.js +38 -0
  62. package/src/composables/useI18n.js +80 -0
  63. package/src/composables/usePluginExtensions.js +95 -3
  64. package/src/composables/usePluginHooks.js +65 -0
  65. package/src/defineEscalatedPlugin.js +34 -8
  66. package/src/index.js +72 -35
  67. package/src/locales/de.json +442 -0
  68. package/src/locales/en.json +467 -0
  69. package/src/locales/es.json +442 -0
  70. package/src/locales/fr.json +442 -0
  71. package/src/locales/index.js +6 -0
  72. package/src/pages/Admin/ApiTokens/Index.vue +231 -67
  73. package/src/pages/Admin/AuditLog/Index.vue +154 -0
  74. package/src/pages/Admin/Automations/Form.vue +184 -0
  75. package/src/pages/Admin/Automations/Index.vue +129 -0
  76. package/src/pages/Admin/BusinessHours/Form.vue +172 -0
  77. package/src/pages/Admin/BusinessHours/Index.vue +113 -0
  78. package/src/pages/Admin/CannedResponses/Index.vue +127 -70
  79. package/src/pages/Admin/Capacity/Index.vue +134 -0
  80. package/src/pages/Admin/CustomFields/Form.vue +195 -0
  81. package/src/pages/Admin/CustomFields/Index.vue +134 -0
  82. package/src/pages/Admin/CustomObjects/Form.vue +202 -0
  83. package/src/pages/Admin/CustomObjects/Index.vue +127 -0
  84. package/src/pages/Admin/CustomObjects/Records.vue +228 -0
  85. package/src/pages/Admin/Departments/Form.vue +75 -51
  86. package/src/pages/Admin/Departments/Index.vue +109 -57
  87. package/src/pages/Admin/EscalationRules/Form.vue +157 -96
  88. package/src/pages/Admin/EscalationRules/Index.vue +178 -57
  89. package/src/pages/Admin/Import/Connect.vue +96 -0
  90. package/src/pages/Admin/Import/Index.vue +102 -0
  91. package/src/pages/Admin/Import/Mapping.vue +110 -0
  92. package/src/pages/Admin/Import/Progress.vue +153 -0
  93. package/src/pages/Admin/Import/Review.vue +76 -0
  94. package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -0
  95. package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -0
  96. package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -0
  97. package/src/pages/Admin/Macros/Index.vue +250 -89
  98. package/src/pages/Admin/Plugins/Index.vue +205 -75
  99. package/src/pages/Admin/Reports/AgentMetrics.vue +115 -0
  100. package/src/pages/Admin/Reports/CsatReport.vue +127 -0
  101. package/src/pages/Admin/Reports/Dashboard.vue +211 -0
  102. package/src/pages/Admin/Reports/SlaReport.vue +166 -0
  103. package/src/pages/Admin/Reports.vue +139 -34
  104. package/src/pages/Admin/Roles/Form.vue +71 -0
  105. package/src/pages/Admin/Roles/Index.vue +122 -0
  106. package/src/pages/Admin/Settings/CsatSettings.vue +165 -0
  107. package/src/pages/Admin/Settings/DataRetention.vue +182 -0
  108. package/src/pages/Admin/Settings/EmailSettings.vue +243 -0
  109. package/src/pages/Admin/Settings/Sandbox.vue +135 -0
  110. package/src/pages/Admin/Settings/SsoSettings.vue +218 -0
  111. package/src/pages/Admin/Settings/TwoFactor.vue +110 -0
  112. package/src/pages/Admin/Settings.vue +468 -260
  113. package/src/pages/Admin/Skills/Form.vue +53 -0
  114. package/src/pages/Admin/Skills/Index.vue +94 -0
  115. package/src/pages/Admin/SlaPolicies/Form.vue +118 -75
  116. package/src/pages/Admin/SlaPolicies/Index.vue +120 -59
  117. package/src/pages/Admin/Statuses/Form.vue +114 -0
  118. package/src/pages/Admin/Statuses/Index.vue +153 -0
  119. package/src/pages/Admin/Tags/Index.vue +191 -105
  120. package/src/pages/Admin/Tickets/Index.vue +91 -39
  121. package/src/pages/Admin/Tickets/Show.vue +101 -37
  122. package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -0
  123. package/src/pages/Admin/Webhooks/Form.vue +110 -0
  124. package/src/pages/Admin/Webhooks/Index.vue +136 -0
  125. package/src/pages/Agent/Dashboard.vue +311 -186
  126. package/src/pages/Agent/TicketIndex.vue +88 -38
  127. package/src/pages/Agent/TicketShow.vue +258 -51
  128. package/src/pages/Customer/Create.vue +36 -9
  129. package/src/pages/Customer/Index.vue +31 -24
  130. package/src/pages/Customer/KnowledgeBase/Article.vue +127 -0
  131. package/src/pages/Customer/KnowledgeBase/Index.vue +151 -0
  132. package/src/plugin.js +86 -1
  133. package/src/utils/constants.js +30 -0
  134. package/src/utils/formatting.js +35 -0
  135. package/src/utils/sanitizeHtml.js +64 -0
  136. package/src/pages/Admin/Plugins.vue +0 -129
@@ -1,260 +1,468 @@
1
- <script setup>
2
- import EscalatedLayout from '../../components/EscalatedLayout.vue';
3
- import { useForm, usePage } from '@inertiajs/vue3';
4
- import { computed } from 'vue';
5
-
6
- const props = defineProps({ settings: Object });
7
- const page = usePage();
8
-
9
- const form = useForm({
10
- guest_tickets_enabled: props.settings.guest_tickets_enabled,
11
- allow_customer_close: props.settings.allow_customer_close,
12
- auto_close_resolved_after_days: props.settings.auto_close_resolved_after_days,
13
- max_attachments_per_reply: props.settings.max_attachments_per_reply,
14
- max_attachment_size_kb: props.settings.max_attachment_size_kb,
15
- ticket_reference_prefix: props.settings.ticket_reference_prefix,
16
- inbound_email_enabled: props.settings.inbound_email_enabled ?? false,
17
- inbound_email_adapter: props.settings.inbound_email_adapter ?? 'mailgun',
18
- inbound_email_address: props.settings.inbound_email_address ?? '',
19
- mailgun_signing_key: props.settings.mailgun_signing_key ?? '',
20
- postmark_inbound_token: props.settings.postmark_inbound_token ?? '',
21
- ses_region: props.settings.ses_region ?? '',
22
- ses_topic_arn: props.settings.ses_topic_arn ?? '',
23
- imap_host: props.settings.imap_host ?? '',
24
- imap_port: props.settings.imap_port ?? 993,
25
- imap_encryption: props.settings.imap_encryption ?? 'ssl',
26
- imap_username: props.settings.imap_username ?? '',
27
- imap_password: props.settings.imap_password ?? '',
28
- imap_mailbox: props.settings.imap_mailbox ?? 'INBOX',
29
- });
30
-
31
- const webhookBaseUrl = computed(() => {
32
- const prefix = page.props.escalated?.prefix || 'support';
33
- const origin = typeof window !== 'undefined' ? window.location.origin : '';
34
- return `${origin}/${prefix}/inbound`;
35
- });
36
-
37
- function submit() {
38
- form.post(route('escalated.admin.settings.update'));
39
- }
40
- </script>
41
-
42
- <template>
43
- <EscalatedLayout title="Settings">
44
- <form @submit.prevent="submit" class="mx-auto max-w-2xl space-y-6">
45
- <!-- General -->
46
- <div class="rounded-xl border border-white/[0.06] bg-neutral-900/60 p-6">
47
- <h3 class="mb-5 text-sm font-semibold text-white">General</h3>
48
- <div class="space-y-5">
49
- <label class="flex items-center justify-between">
50
- <div>
51
- <span class="text-sm font-medium text-neutral-200">Guest Tickets</span>
52
- <p class="mt-0.5 text-xs text-neutral-500">Allow visitors to submit tickets without signing in</p>
53
- </div>
54
- <button type="button" @click="form.guest_tickets_enabled = !form.guest_tickets_enabled"
55
- :class="['relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
56
- form.guest_tickets_enabled ? 'bg-emerald-500' : 'bg-neutral-700']">
57
- <span :class="['pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
58
- form.guest_tickets_enabled ? 'translate-x-5' : 'translate-x-0']" />
59
- </button>
60
- </label>
61
-
62
- <label class="flex items-center justify-between">
63
- <div>
64
- <span class="text-sm font-medium text-neutral-200">Allow Customer Close</span>
65
- <p class="mt-0.5 text-xs text-neutral-500">Let customers close their own tickets</p>
66
- </div>
67
- <button type="button" @click="form.allow_customer_close = !form.allow_customer_close"
68
- :class="['relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
69
- form.allow_customer_close ? 'bg-emerald-500' : 'bg-neutral-700']">
70
- <span :class="['pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
71
- form.allow_customer_close ? 'translate-x-5' : 'translate-x-0']" />
72
- </button>
73
- </label>
74
-
75
- <div>
76
- <label class="block text-sm font-medium text-neutral-200">Auto-close resolved tickets after</label>
77
- <p class="mt-0.5 text-xs text-neutral-500">Days after resolution before auto-closing (0 to disable)</p>
78
- <div class="mt-2 flex items-center gap-2">
79
- <input v-model.number="form.auto_close_resolved_after_days" type="number" min="0" max="365"
80
- class="w-24 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
81
- <span class="text-sm text-neutral-500">days</span>
82
- </div>
83
- </div>
84
- </div>
85
- </div>
86
-
87
- <!-- Limits -->
88
- <div class="rounded-xl border border-white/[0.06] bg-neutral-900/60 p-6">
89
- <h3 class="mb-5 text-sm font-semibold text-white">Limits</h3>
90
- <div class="space-y-5">
91
- <div>
92
- <label class="block text-sm font-medium text-neutral-200">Max attachments per reply</label>
93
- <input v-model.number="form.max_attachments_per_reply" type="number" min="1" max="20"
94
- class="mt-2 w-24 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
95
- </div>
96
- <div>
97
- <label class="block text-sm font-medium text-neutral-200">Max attachment size</label>
98
- <div class="mt-2 flex items-center gap-2">
99
- <input v-model.number="form.max_attachment_size_kb" type="number" min="512" max="102400" step="512"
100
- class="w-32 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
101
- <span class="text-sm text-neutral-500">KB ({{ Math.round(form.max_attachment_size_kb / 1024) }} MB)</span>
102
- </div>
103
- </div>
104
- </div>
105
- </div>
106
-
107
- <!-- Tickets -->
108
- <div class="rounded-xl border border-white/[0.06] bg-neutral-900/60 p-6">
109
- <h3 class="mb-5 text-sm font-semibold text-white">Tickets</h3>
110
- <div class="space-y-5">
111
- <div>
112
- <label class="block text-sm font-medium text-neutral-200">Reference Prefix</label>
113
- <p class="mt-0.5 text-xs text-neutral-500">Prefix for ticket references (e.g. ESC produces ESC-00001)</p>
114
- <input v-model="form.ticket_reference_prefix" type="text" maxlength="10"
115
- class="mt-2 w-32 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 uppercase focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
116
- </div>
117
- </div>
118
- </div>
119
-
120
- <!-- Inbound Email -->
121
- <div class="rounded-xl border border-white/[0.06] bg-neutral-900/60 p-6">
122
- <h3 class="mb-5 text-sm font-semibold text-white">Inbound Email</h3>
123
- <div class="space-y-5">
124
- <label class="flex items-center justify-between">
125
- <div>
126
- <span class="text-sm font-medium text-neutral-200">Inbound Email</span>
127
- <p class="mt-0.5 text-xs text-neutral-500">Allow creating and replying to tickets via email</p>
128
- </div>
129
- <button type="button" @click="form.inbound_email_enabled = !form.inbound_email_enabled"
130
- :class="['relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors',
131
- form.inbound_email_enabled ? 'bg-emerald-500' : 'bg-neutral-700']">
132
- <span :class="['pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition-transform',
133
- form.inbound_email_enabled ? 'translate-x-5' : 'translate-x-0']" />
134
- </button>
135
- </label>
136
-
137
- <template v-if="form.inbound_email_enabled">
138
- <div>
139
- <label class="block text-sm font-medium text-neutral-200">Email Provider</label>
140
- <p class="mt-0.5 text-xs text-neutral-500">How inbound emails are received</p>
141
- <select v-model="form.inbound_email_adapter"
142
- class="mt-2 w-48 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10">
143
- <option value="mailgun">Mailgun</option>
144
- <option value="postmark">Postmark</option>
145
- <option value="ses">AWS SES</option>
146
- <option value="imap">IMAP</option>
147
- </select>
148
- </div>
149
-
150
- <div>
151
- <label class="block text-sm font-medium text-neutral-200">Support Email Address</label>
152
- <p class="mt-0.5 text-xs text-neutral-500">The email address customers will send emails to (e.g. support@example.com)</p>
153
- <input v-model="form.inbound_email_address" type="email" placeholder="support@example.com"
154
- class="mt-2 w-full max-w-sm rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
155
- </div>
156
-
157
- <!-- Mailgun -->
158
- <template v-if="form.inbound_email_adapter === 'mailgun'">
159
- <div>
160
- <label class="block text-sm font-medium text-neutral-200">Signing Key</label>
161
- <input v-model="form.mailgun_signing_key" type="password"
162
- class="mt-2 w-full max-w-sm rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
163
- </div>
164
- <div>
165
- <label class="block text-sm font-medium text-neutral-200">Webhook URL</label>
166
- <p class="mt-0.5 text-xs text-neutral-500">Configure this URL in your Mailgun dashboard</p>
167
- <input :value="`${webhookBaseUrl}/mailgun`" type="text" readonly
168
- class="mt-2 w-full max-w-lg rounded-lg border border-white/10 bg-neutral-950/50 px-3 py-2 text-sm text-neutral-400 select-all cursor-default focus:outline-none" />
169
- </div>
170
- </template>
171
-
172
- <!-- Postmark -->
173
- <template v-if="form.inbound_email_adapter === 'postmark'">
174
- <div>
175
- <label class="block text-sm font-medium text-neutral-200">Inbound Token</label>
176
- <input v-model="form.postmark_inbound_token" type="password"
177
- class="mt-2 w-full max-w-sm rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
178
- </div>
179
- <div>
180
- <label class="block text-sm font-medium text-neutral-200">Webhook URL</label>
181
- <p class="mt-0.5 text-xs text-neutral-500">Configure this URL in your Postmark server settings</p>
182
- <input :value="`${webhookBaseUrl}/postmark`" type="text" readonly
183
- class="mt-2 w-full max-w-lg rounded-lg border border-white/10 bg-neutral-950/50 px-3 py-2 text-sm text-neutral-400 select-all cursor-default focus:outline-none" />
184
- </div>
185
- </template>
186
-
187
- <!-- AWS SES -->
188
- <template v-if="form.inbound_email_adapter === 'ses'">
189
- <div>
190
- <label class="block text-sm font-medium text-neutral-200">Region</label>
191
- <input v-model="form.ses_region" type="text" placeholder="us-east-1"
192
- class="mt-2 w-48 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
193
- </div>
194
- <div>
195
- <label class="block text-sm font-medium text-neutral-200">Topic ARN</label>
196
- <input v-model="form.ses_topic_arn" type="text" placeholder="arn:aws:sns:us-east-1:123456789:ses-inbound"
197
- class="mt-2 w-full max-w-lg rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
198
- </div>
199
- <div>
200
- <label class="block text-sm font-medium text-neutral-200">Webhook URL</label>
201
- <p class="mt-0.5 text-xs text-neutral-500">Configure this URL as your SNS subscription endpoint</p>
202
- <input :value="`${webhookBaseUrl}/ses`" type="text" readonly
203
- class="mt-2 w-full max-w-lg rounded-lg border border-white/10 bg-neutral-950/50 px-3 py-2 text-sm text-neutral-400 select-all cursor-default focus:outline-none" />
204
- </div>
205
- </template>
206
-
207
- <!-- IMAP -->
208
- <template v-if="form.inbound_email_adapter === 'imap'">
209
- <div>
210
- <label class="block text-sm font-medium text-neutral-200">Host</label>
211
- <input v-model="form.imap_host" type="text" placeholder="imap.example.com"
212
- class="mt-2 w-full max-w-sm rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
213
- </div>
214
- <div class="flex items-end gap-4">
215
- <div>
216
- <label class="block text-sm font-medium text-neutral-200">Port</label>
217
- <input v-model.number="form.imap_port" type="number" min="1" max="65535"
218
- class="mt-2 w-24 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
219
- </div>
220
- <div>
221
- <label class="block text-sm font-medium text-neutral-200">Encryption</label>
222
- <select v-model="form.imap_encryption"
223
- class="mt-2 w-28 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10">
224
- <option value="ssl">SSL</option>
225
- <option value="tls">TLS</option>
226
- <option value="none">None</option>
227
- </select>
228
- </div>
229
- </div>
230
- <div>
231
- <label class="block text-sm font-medium text-neutral-200">Username</label>
232
- <input v-model="form.imap_username" type="text"
233
- class="mt-2 w-full max-w-sm rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
234
- </div>
235
- <div>
236
- <label class="block text-sm font-medium text-neutral-200">Password</label>
237
- <input v-model="form.imap_password" type="password"
238
- class="mt-2 w-full max-w-sm rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
239
- </div>
240
- <div>
241
- <label class="block text-sm font-medium text-neutral-200">Mailbox</label>
242
- <input v-model="form.imap_mailbox" type="text" placeholder="INBOX"
243
- class="mt-2 w-48 rounded-lg border border-white/10 bg-neutral-950 px-3 py-2 text-sm text-neutral-200 focus:border-white/20 focus:outline-none focus:ring-1 focus:ring-white/10" />
244
- </div>
245
- </template>
246
- </template>
247
- </div>
248
- </div>
249
-
250
- <!-- Submit -->
251
- <div class="flex items-center justify-end gap-3">
252
- <span v-if="form.recentlySuccessful" class="text-sm text-emerald-400">Saved.</span>
253
- <button type="submit" :disabled="form.processing"
254
- class="rounded-lg bg-gradient-to-r from-cyan-500 to-violet-500 px-5 py-2 text-sm font-medium text-white shadow-lg shadow-black/20 transition-all hover:from-cyan-400 hover:to-violet-400 disabled:opacity-50">
255
- {{ form.processing ? 'Saving...' : 'Save Settings' }}
256
- </button>
257
- </div>
258
- </form>
259
- </EscalatedLayout>
260
- </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 });
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>