@vicket/create-support 1.1.1 → 1.1.2
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.
- package/bin/create-vicket-support.js +429 -389
- package/package.json +1 -1
- package/templates/next/src/app/api/vicket/[...path]/route.ts +2 -55
- package/templates/next/src/app/components/vicket/ReplyForm.tsx +154 -0
- package/templates/next/src/app/components/vicket/SupportContent.tsx +298 -0
- package/templates/next/src/app/components/vicket/TicketDialog.tsx +3 -3
- package/templates/next/src/app/support/page.tsx +27 -353
- package/templates/next/src/app/ticket/page.tsx +110 -325
- package/templates/next/src/app/vicket.css +1325 -1325
- package/templates/nuxt/app/assets/css/vicket.css +1325 -1325
- package/templates/nuxt/app/components/VicketReplyForm.vue +154 -0
- package/templates/nuxt/app/components/VicketSupportContent.vue +255 -0
- package/templates/nuxt/app/components/VicketTicketDialog.vue +2 -2
- package/templates/nuxt/app/pages/support.vue +7 -293
- package/templates/nuxt/app/pages/ticket.vue +36 -178
- package/templates/nuxt/server/api/vicket/[...path].ts +2 -85
- package/templates/sveltekit/src/lib/vicket/ReplyForm.svelte +134 -0
- package/templates/sveltekit/src/lib/vicket/SupportContent.svelte +263 -0
- package/templates/sveltekit/src/lib/vicket/TicketDialog.svelte +457 -459
- package/templates/sveltekit/src/lib/vicket.css +1325 -1325
- package/templates/sveltekit/src/routes/api/vicket/[...path]/+server.ts +2 -76
- package/templates/sveltekit/src/routes/support/+page.server.ts +13 -0
- package/templates/sveltekit/src/routes/support/+page.svelte +3 -312
- package/templates/sveltekit/src/routes/ticket/+page.server.ts +19 -0
- package/templates/sveltekit/src/routes/ticket/+page.svelte +13 -188
- package/templates-tailwind/next/src/app/api/vicket/[...path]/route.ts +6 -0
- package/templates-tailwind/next/src/app/support/page.tsx +33 -3
- package/templates-tailwind/next/src/app/ticket/page.tsx +249 -6
- package/templates-tailwind/next/src/components/vicket/reply-form.tsx +113 -0
- package/templates-tailwind/next/src/components/vicket/support-content.tsx +265 -0
- package/templates-tailwind/next/src/components/vicket/ticket-dialog.tsx +2 -2
- package/templates-tailwind/nuxt/app/components/VicketReplyForm.vue +169 -0
- package/templates-tailwind/nuxt/app/components/{VicketSupportPage.vue → VicketSupportContent.vue} +275 -317
- package/templates-tailwind/nuxt/app/components/VicketTicketDialog.vue +3 -0
- package/templates-tailwind/nuxt/app/pages/support.vue +10 -1
- package/templates-tailwind/nuxt/app/pages/ticket.vue +298 -1
- package/templates-tailwind/nuxt/server/api/vicket/[...path].ts +2 -0
- package/templates-tailwind/sveltekit/src/lib/vicket/ReplyForm.svelte +127 -0
- package/templates-tailwind/sveltekit/src/lib/vicket/{SupportPage.svelte → SupportContent.svelte} +9 -71
- package/templates-tailwind/sveltekit/src/lib/vicket/TicketDialog.svelte +405 -406
- package/templates-tailwind/sveltekit/src/routes/api/vicket/[...path]/+server.ts +3 -0
- package/templates-tailwind/sveltekit/src/routes/support/+page.server.ts +13 -0
- package/templates-tailwind/sveltekit/src/routes/support/+page.svelte +4 -2
- package/templates-tailwind/sveltekit/src/routes/ticket/+page.server.ts +19 -0
- package/templates-tailwind/sveltekit/src/routes/ticket/+page.svelte +292 -2
- package/templates/next/src/app/utils/vicket/api.ts +0 -149
- package/templates/next/src/app/utils/vicket/types.ts +0 -85
- package/templates/next/src/app/utils/vicket/utils.ts +0 -49
- package/templates/nuxt/app/composables/useVicket.ts +0 -274
- package/templates/sveltekit/src/lib/vicket/api.ts +0 -162
- package/templates/sveltekit/src/lib/vicket/types.ts +0 -87
- package/templates/sveltekit/src/lib/vicket/utils.ts +0 -55
- package/templates-tailwind/next/src/app/api/vicket/init/route.ts +0 -24
- package/templates-tailwind/next/src/app/api/vicket/messages/route.ts +0 -36
- package/templates-tailwind/next/src/app/api/vicket/thread/route.ts +0 -27
- package/templates-tailwind/next/src/app/api/vicket/tickets/route.ts +0 -37
- package/templates-tailwind/next/src/components/vicket/support-page.tsx +0 -359
- package/templates-tailwind/next/src/components/vicket/ticket-page.tsx +0 -425
- package/templates-tailwind/next/src/lib/vicket.ts +0 -257
- package/templates-tailwind/nuxt/app/components/VicketTicketPage.vue +0 -449
- package/templates-tailwind/nuxt/app/composables/use-vicket.ts +0 -249
- package/templates-tailwind/nuxt/server/api/vicket/init.get.ts +0 -22
- package/templates-tailwind/nuxt/server/api/vicket/messages.post.ts +0 -56
- package/templates-tailwind/nuxt/server/api/vicket/thread.get.ts +0 -26
- package/templates-tailwind/nuxt/server/api/vicket/tickets.post.ts +0 -53
- package/templates-tailwind/sveltekit/src/lib/vicket/TicketPage.svelte +0 -465
- package/templates-tailwind/sveltekit/src/lib/vicket/index.ts +0 -257
- package/templates-tailwind/sveltekit/src/routes/api/vicket/init/+server.ts +0 -22
- package/templates-tailwind/sveltekit/src/routes/api/vicket/messages/+server.ts +0 -40
- package/templates-tailwind/sveltekit/src/routes/api/vicket/thread/+server.ts +0 -25
- package/templates-tailwind/sveltekit/src/routes/api/vicket/tickets/+server.ts +0 -37
package/templates-tailwind/nuxt/app/components/{VicketSupportPage.vue → VicketSupportContent.vue}
RENAMED
|
@@ -1,317 +1,275 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
</
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
>
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
</div>
|
|
277
|
-
|
|
278
|
-
<!-- No search results -->
|
|
279
|
-
<div v-if="searchQuery.trim() && !hasResults" class="pb-16 text-center">
|
|
280
|
-
<p class="text-sm text-slate-500">No results found for “{{ searchQuery }}”</p>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
<!-- Empty state -->
|
|
284
|
-
<div v-if="!hasContent && !error" class="pb-16 pt-4 text-center">
|
|
285
|
-
<div class="mx-auto max-w-sm rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
|
|
286
|
-
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-50 text-blue-600">
|
|
287
|
-
<!-- IconMessageCircle 22 -->
|
|
288
|
-
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z" /></svg>
|
|
289
|
-
</div>
|
|
290
|
-
<p class="m-0 text-sm font-medium text-slate-900">Need help? Our team is here for you.</p>
|
|
291
|
-
<button
|
|
292
|
-
type="button"
|
|
293
|
-
class="mt-5 inline-flex items-center gap-2 !rounded-full border-none bg-blue-600 !px-7 !py-3 text-sm font-semibold text-white cursor-pointer transition-all hover:bg-blue-700 hover:-translate-y-px hover:shadow-lg active:translate-y-0"
|
|
294
|
-
@click="dialogOpen = true"
|
|
295
|
-
>
|
|
296
|
-
Contact Support
|
|
297
|
-
</button>
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
<VicketTicketDialog
|
|
302
|
-
:open="dialogOpen"
|
|
303
|
-
:templates="templates"
|
|
304
|
-
@close="dialogOpen = false"
|
|
305
|
-
/>
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
</template>
|
|
309
|
-
|
|
310
|
-
<style>
|
|
311
|
-
.vk-message-content p { margin-bottom: 0.5rem; }
|
|
312
|
-
.vk-message-content p:last-child { margin-bottom: 0; }
|
|
313
|
-
.vk-message-content a { color: #2563eb; text-decoration: underline; }
|
|
314
|
-
.vk-message-content ul, .vk-message-content ol { margin: 0.25rem 0; padding-left: 1.5rem; }
|
|
315
|
-
.vk-message-content pre { overflow-x: auto; border-radius: 6px; background: #f8fafc; padding: 0.75rem; font-size: 0.75rem; }
|
|
316
|
-
.vk-message-content code { font-family: monospace; font-size: 0.85em; }
|
|
317
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Template, Article, Faq } from "vicket";
|
|
3
|
+
import { cn, stripHtml, sanitizeHtml } from "vicket";
|
|
4
|
+
|
|
5
|
+
/* ── Props ─────────────────────────────────────── */
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
initData?: { website?: { name?: string }; templates: Template[]; articles?: Article[]; faqs?: Faq[] } | null;
|
|
8
|
+
initError?: string;
|
|
9
|
+
}>();
|
|
10
|
+
|
|
11
|
+
/* ── Reactive state ─────────────────────────────── */
|
|
12
|
+
const templates = ref<Template[]>(props.initData?.templates || []);
|
|
13
|
+
const articles = ref<Article[]>(props.initData?.articles || []);
|
|
14
|
+
const faqs = ref<Faq[]>(props.initData?.faqs || []);
|
|
15
|
+
const websiteName = ref(props.initData?.website?.name || "Support");
|
|
16
|
+
const error = ref(props.initError || "");
|
|
17
|
+
const searchQuery = ref("");
|
|
18
|
+
const dialogOpen = ref(false);
|
|
19
|
+
const selectedArticle = ref<Article | null>(null);
|
|
20
|
+
|
|
21
|
+
/* FAQ accordion open state */
|
|
22
|
+
const openFaqIds = ref<Set<string>>(new Set());
|
|
23
|
+
|
|
24
|
+
/* ── Computed ───────────────────────────────────── */
|
|
25
|
+
const filteredArticles = computed(() => {
|
|
26
|
+
if (!searchQuery.value.trim()) return articles.value;
|
|
27
|
+
const q = searchQuery.value.toLowerCase();
|
|
28
|
+
return articles.value.filter(
|
|
29
|
+
(a) =>
|
|
30
|
+
a.title.toLowerCase().includes(q) ||
|
|
31
|
+
stripHtml(a.content).toLowerCase().includes(q),
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const filteredFaqs = computed(() => {
|
|
36
|
+
if (!searchQuery.value.trim()) return faqs.value;
|
|
37
|
+
const q = searchQuery.value.toLowerCase();
|
|
38
|
+
return faqs.value.filter(
|
|
39
|
+
(f) =>
|
|
40
|
+
f.question.toLowerCase().includes(q) ||
|
|
41
|
+
f.answer.toLowerCase().includes(q),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const hasContent = computed(() => articles.value.length > 0 || faqs.value.length > 0);
|
|
46
|
+
const hasResults = computed(() => filteredArticles.value.length > 0 || filteredFaqs.value.length > 0);
|
|
47
|
+
|
|
48
|
+
/* ── FAQ accordion ──────────────────────────────── */
|
|
49
|
+
const toggleFaq = (id: string) => {
|
|
50
|
+
const next = new Set(openFaqIds.value);
|
|
51
|
+
if (next.has(id)) {
|
|
52
|
+
next.delete(id);
|
|
53
|
+
} else {
|
|
54
|
+
next.add(id);
|
|
55
|
+
}
|
|
56
|
+
openFaqIds.value = next;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const isFaqOpen = (id: string) => openFaqIds.value.has(id);
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<div class="min-h-screen bg-slate-50 font-[system-ui,-apple-system,sans-serif] antialiased">
|
|
64
|
+
<!-- Article detail view -->
|
|
65
|
+
<div v-if="selectedArticle" class="mx-auto max-w-5xl px-6">
|
|
66
|
+
<div class="flex flex-col gap-4 pb-8 pt-14 md:flex-row md:items-center md:justify-between">
|
|
67
|
+
<div>
|
|
68
|
+
<h1 class="m-0 text-2xl font-bold tracking-tight text-slate-900 md:text-3xl">{{ websiteName }}</h1>
|
|
69
|
+
<p class="mt-1.5 text-slate-500">How can we help you today?</p>
|
|
70
|
+
</div>
|
|
71
|
+
<button
|
|
72
|
+
v-if="templates.length > 0"
|
|
73
|
+
type="button"
|
|
74
|
+
class="inline-flex shrink-0 items-center gap-2 !rounded-full border-none bg-blue-600 !px-7 !py-3 text-sm font-semibold text-white cursor-pointer transition-all hover:bg-blue-700 hover:-translate-y-px hover:shadow-lg active:translate-y-0"
|
|
75
|
+
@click="dialogOpen = true"
|
|
76
|
+
>
|
|
77
|
+
<!-- IconMessageCircle -->
|
|
78
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z" /></svg>
|
|
79
|
+
Contact Support
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="pb-16">
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
class="-ml-2 mb-4 inline-flex cursor-pointer items-center gap-1.5 rounded-lg border-none bg-transparent px-2 py-1 text-sm font-medium text-slate-500 transition-colors hover:bg-white hover:text-slate-900"
|
|
87
|
+
@click="selectedArticle = null"
|
|
88
|
+
>
|
|
89
|
+
<!-- IconArrowLeft -->
|
|
90
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg>
|
|
91
|
+
Back to articles
|
|
92
|
+
</button>
|
|
93
|
+
|
|
94
|
+
<div class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm md:p-8">
|
|
95
|
+
<h2 class="m-0 text-xl font-bold tracking-tight text-slate-900 md:text-2xl">{{ selectedArticle.title }}</h2>
|
|
96
|
+
<div
|
|
97
|
+
class="mt-5 text-sm leading-relaxed text-slate-500 [&_a]:text-blue-600 [&_a]:underline [&_code]:font-mono [&_code]:text-[0.85em] [&_ol]:my-1 [&_ol]:pl-6 [&_p:last-child]:mb-0 [&_p]:mb-2 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-slate-50 [&_pre]:p-3 [&_pre]:text-xs [&_ul]:my-1 [&_ul]:pl-6"
|
|
98
|
+
v-html="sanitizeHtml(selectedArticle.content)"
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<VicketTicketDialog
|
|
104
|
+
:open="dialogOpen"
|
|
105
|
+
:templates="templates"
|
|
106
|
+
@close="dialogOpen = false"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Main hub view -->
|
|
111
|
+
<div v-else class="mx-auto max-w-5xl px-6">
|
|
112
|
+
<div class="flex flex-col gap-4 pb-8 pt-14 md:flex-row md:items-center md:justify-between">
|
|
113
|
+
<div>
|
|
114
|
+
<h1 class="m-0 text-2xl font-bold tracking-tight text-slate-900 md:text-3xl">{{ websiteName }}</h1>
|
|
115
|
+
<p class="mt-1.5 text-slate-500">How can we help you today?</p>
|
|
116
|
+
</div>
|
|
117
|
+
<div v-if="templates.length > 0" class="flex items-center gap-3">
|
|
118
|
+
<span class="hidden text-sm text-slate-500 md:block">Can't find what you're looking for?</span>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
class="inline-flex shrink-0 items-center gap-2 !rounded-full border-none bg-blue-600 !px-7 !py-3 text-sm font-semibold text-white cursor-pointer transition-all hover:bg-blue-700 hover:-translate-y-px hover:shadow-lg active:translate-y-0"
|
|
122
|
+
@click="dialogOpen = true"
|
|
123
|
+
>
|
|
124
|
+
<!-- IconMessageCircle -->
|
|
125
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z" /></svg>
|
|
126
|
+
Contact Support
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- Error alert -->
|
|
132
|
+
<div v-if="error" class="pb-4">
|
|
133
|
+
<div
|
|
134
|
+
class="flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-900"
|
|
135
|
+
role="alert"
|
|
136
|
+
>
|
|
137
|
+
<span class="mt-0.5 shrink-0">
|
|
138
|
+
<!-- IconAlert -->
|
|
139
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
|
140
|
+
</span>
|
|
141
|
+
<span class="flex-1">{{ error }}</span>
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
class="shrink-0 cursor-pointer border-none bg-transparent p-0 opacity-50 transition-opacity hover:opacity-100"
|
|
145
|
+
aria-label="Dismiss"
|
|
146
|
+
@click="error = ''"
|
|
147
|
+
>
|
|
148
|
+
<!-- IconX -->
|
|
149
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<!-- Search bar -->
|
|
155
|
+
<div v-if="hasContent" class="pb-8">
|
|
156
|
+
<div class="relative">
|
|
157
|
+
<span class="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-500">
|
|
158
|
+
<!-- IconSearch -->
|
|
159
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
|
160
|
+
</span>
|
|
161
|
+
<input
|
|
162
|
+
type="text"
|
|
163
|
+
class="w-full rounded-xl border border-slate-200 bg-white py-3 pl-11 pr-4 text-sm text-slate-900 shadow-sm transition-all duration-150 placeholder:text-slate-500/60 focus:border-blue-600 focus:outline-none focus:ring-3 focus:ring-blue-600/10"
|
|
164
|
+
placeholder="Search articles and FAQs..."
|
|
165
|
+
v-model="searchQuery"
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Content grid -->
|
|
171
|
+
<div v-if="hasResults" class="grid grid-cols-1 gap-10 pb-16 md:grid-cols-2 md:gap-12">
|
|
172
|
+
<!-- Articles -->
|
|
173
|
+
<div v-if="filteredArticles.length > 0">
|
|
174
|
+
<div class="mb-4 flex items-center gap-2">
|
|
175
|
+
<span class="text-blue-600">
|
|
176
|
+
<!-- IconArticle 18 -->
|
|
177
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" /><path d="M14 2v6h6" /><path d="M16 13H8" /><path d="M16 17H8" /><path d="M10 9H8" /></svg>
|
|
178
|
+
</span>
|
|
179
|
+
<h2 class="m-0 text-base font-bold tracking-tight text-slate-900">Popular Articles</h2>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="space-y-1">
|
|
182
|
+
<button
|
|
183
|
+
v-for="article in filteredArticles"
|
|
184
|
+
:key="article.id"
|
|
185
|
+
type="button"
|
|
186
|
+
class="group flex w-full cursor-pointer items-start gap-3 rounded-xl border border-transparent bg-transparent p-3 text-left transition-all duration-150 hover:border-slate-200 hover:bg-white hover:shadow-sm"
|
|
187
|
+
@click="selectedArticle = article"
|
|
188
|
+
>
|
|
189
|
+
<div class="flex h-9 w-9 min-w-9 items-center justify-center rounded-lg bg-blue-50 text-blue-600">
|
|
190
|
+
<!-- IconArticle 16 -->
|
|
191
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" /><path d="M14 2v6h6" /><path d="M16 13H8" /><path d="M16 17H8" /><path d="M10 9H8" /></svg>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="min-w-0 flex-1">
|
|
194
|
+
<h3 class="m-0 text-sm font-semibold leading-snug text-slate-900 transition-colors group-hover:text-blue-600">{{ article.title }}</h3>
|
|
195
|
+
<p v-if="article.content" class="mt-0.5 line-clamp-2 text-xs leading-normal text-slate-500">
|
|
196
|
+
{{ stripHtml(article.content).substring(0, 150) }}
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<!-- FAQs -->
|
|
204
|
+
<div v-if="filteredFaqs.length > 0">
|
|
205
|
+
<div class="mb-4 flex items-center gap-2">
|
|
206
|
+
<span class="text-blue-600">
|
|
207
|
+
<!-- IconHelpCircle -->
|
|
208
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><path d="M12 17h.01" /></svg>
|
|
209
|
+
</span>
|
|
210
|
+
<h2 class="m-0 text-base font-bold tracking-tight text-slate-900">Frequently Asked Questions</h2>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="space-y-2">
|
|
213
|
+
<div
|
|
214
|
+
v-for="faq in filteredFaqs"
|
|
215
|
+
:key="faq.id"
|
|
216
|
+
class="rounded-xl border border-slate-200 bg-white/50 transition-colors hover:bg-white/80"
|
|
217
|
+
>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
:class="cn(
|
|
221
|
+
'flex w-full cursor-pointer items-center justify-between gap-3 border-none bg-transparent px-4 py-3.5 text-left text-sm font-semibold text-slate-900',
|
|
222
|
+
isFaqOpen(faq.id) && 'text-blue-600',
|
|
223
|
+
)"
|
|
224
|
+
:aria-expanded="isFaqOpen(faq.id)"
|
|
225
|
+
@click="toggleFaq(faq.id)"
|
|
226
|
+
>
|
|
227
|
+
{{ faq.question }}
|
|
228
|
+
<span :class="cn('shrink-0 text-slate-500 transition-transform duration-200', isFaqOpen(faq.id) && 'rotate-180')">
|
|
229
|
+
<!-- IconChevronDown -->
|
|
230
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
|
231
|
+
</span>
|
|
232
|
+
</button>
|
|
233
|
+
<div :class="cn('grid grid-rows-[0fr] transition-[grid-template-rows] duration-250', isFaqOpen(faq.id) && 'grid-rows-[1fr]')">
|
|
234
|
+
<div class="overflow-hidden">
|
|
235
|
+
<div class="whitespace-pre-wrap px-4 pb-4 text-sm leading-relaxed text-slate-500">
|
|
236
|
+
{{ faq.answer }}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<!-- No search results -->
|
|
246
|
+
<div v-if="searchQuery.trim() && !hasResults" class="pb-16 text-center">
|
|
247
|
+
<p class="text-sm text-slate-500">No results found for “{{ searchQuery }}”</p>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Empty state -->
|
|
251
|
+
<div v-if="!hasContent && !error" class="pb-16 pt-4 text-center">
|
|
252
|
+
<div class="mx-auto max-w-sm rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
|
|
253
|
+
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-50 text-blue-600">
|
|
254
|
+
<!-- IconMessageCircle 22 -->
|
|
255
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z" /></svg>
|
|
256
|
+
</div>
|
|
257
|
+
<p class="m-0 text-sm font-medium text-slate-900">Need help? Our team is here for you.</p>
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
class="mt-5 inline-flex items-center gap-2 !rounded-full border-none bg-blue-600 !px-7 !py-3 text-sm font-semibold text-white cursor-pointer transition-all hover:bg-blue-700 hover:-translate-y-px hover:shadow-lg active:translate-y-0"
|
|
261
|
+
@click="dialogOpen = true"
|
|
262
|
+
>
|
|
263
|
+
Contact Support
|
|
264
|
+
</button>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<VicketTicketDialog
|
|
269
|
+
:open="dialogOpen"
|
|
270
|
+
:templates="templates"
|
|
271
|
+
@close="dialogOpen = false"
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</template>
|