@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { cn, sendReply } from "vicket";
|
|
3
|
+
|
|
4
|
+
/* ---------------------------------------------- */
|
|
5
|
+
/* Props & Emits */
|
|
6
|
+
/* ---------------------------------------------- */
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
token: string;
|
|
9
|
+
}>();
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{
|
|
12
|
+
replied: [];
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
/* ---------------------------------------------- */
|
|
16
|
+
/* Reactive state */
|
|
17
|
+
/* ---------------------------------------------- */
|
|
18
|
+
const content = ref("");
|
|
19
|
+
const files = ref<File[]>([]);
|
|
20
|
+
const isSending = ref(false);
|
|
21
|
+
const error = ref("");
|
|
22
|
+
const success = ref("");
|
|
23
|
+
|
|
24
|
+
/* ---------------------------------------------- */
|
|
25
|
+
/* Helpers */
|
|
26
|
+
/* ---------------------------------------------- */
|
|
27
|
+
const removeFile = (index: number) => {
|
|
28
|
+
files.value = files.value.filter((_, i) => i !== index);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const onFileChange = (event: Event) => {
|
|
32
|
+
const input = event.target as HTMLInputElement;
|
|
33
|
+
const newFiles = Array.from(input.files || []);
|
|
34
|
+
files.value = [...files.value, ...newFiles];
|
|
35
|
+
input.value = "";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const submitReply = async () => {
|
|
39
|
+
error.value = "";
|
|
40
|
+
success.value = "";
|
|
41
|
+
|
|
42
|
+
if (!content.value.trim() && files.value.length === 0) {
|
|
43
|
+
error.value = "Reply content is required.";
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!props.token.trim()) {
|
|
48
|
+
error.value = "Missing ticket token.";
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isSending.value = true;
|
|
53
|
+
try {
|
|
54
|
+
await sendReply(props.token, content.value.trim(), files.value);
|
|
55
|
+
content.value = "";
|
|
56
|
+
files.value = [];
|
|
57
|
+
success.value = "Reply sent.";
|
|
58
|
+
emit("replied");
|
|
59
|
+
} catch (replyError) {
|
|
60
|
+
error.value = replyError instanceof Error ? replyError.message : "Unexpected error.";
|
|
61
|
+
} finally {
|
|
62
|
+
isSending.value = false;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<!-- Alerts -->
|
|
69
|
+
<div
|
|
70
|
+
v-if="error"
|
|
71
|
+
:class="cn('vk-alert vk-slide-up', 'error')"
|
|
72
|
+
role="alert"
|
|
73
|
+
>
|
|
74
|
+
<span>⚠</span>
|
|
75
|
+
<span>{{ error }}</span>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
class="vk-alert-dismiss"
|
|
79
|
+
aria-label="Dismiss"
|
|
80
|
+
@click="error = ''"
|
|
81
|
+
>
|
|
82
|
+
✕
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
<div
|
|
86
|
+
v-if="success"
|
|
87
|
+
:class="cn('vk-alert vk-slide-up', 'success')"
|
|
88
|
+
role="alert"
|
|
89
|
+
>
|
|
90
|
+
<span>✓</span>
|
|
91
|
+
<span>{{ success }}</span>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="vk-alert-dismiss"
|
|
95
|
+
aria-label="Dismiss"
|
|
96
|
+
@click="success = ''"
|
|
97
|
+
>
|
|
98
|
+
✕
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Compose area -->
|
|
103
|
+
<div class="vk-compose">
|
|
104
|
+
<form class="vk-stack" @submit.prevent="submitReply">
|
|
105
|
+
<textarea
|
|
106
|
+
v-model="content"
|
|
107
|
+
class="vk-textarea"
|
|
108
|
+
placeholder="Write your reply..."
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<div class="vk-compose-row">
|
|
112
|
+
<!-- File input -->
|
|
113
|
+
<div class="vk-compose-files">
|
|
114
|
+
<label class="vk-browse-btn">
|
|
115
|
+
📎 Browse files
|
|
116
|
+
<input
|
|
117
|
+
type="file"
|
|
118
|
+
multiple
|
|
119
|
+
@change="onFileChange"
|
|
120
|
+
/>
|
|
121
|
+
</label>
|
|
122
|
+
<span
|
|
123
|
+
v-for="(file, i) in files"
|
|
124
|
+
:key="`${file.name}-${i}`"
|
|
125
|
+
class="vk-file-chip"
|
|
126
|
+
>
|
|
127
|
+
📎
|
|
128
|
+
<span class="vk-file-chip-name">{{ file.name }}</span>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
:aria-label="`Remove ${file.name}`"
|
|
132
|
+
@click="removeFile(i)"
|
|
133
|
+
>
|
|
134
|
+
✕
|
|
135
|
+
</button>
|
|
136
|
+
</span>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<!-- Send -->
|
|
140
|
+
<button
|
|
141
|
+
class="vk-button primary"
|
|
142
|
+
:disabled="isSending"
|
|
143
|
+
type="submit"
|
|
144
|
+
>
|
|
145
|
+
<template v-if="isSending">
|
|
146
|
+
<span class="vk-spinner" />
|
|
147
|
+
Sending...
|
|
148
|
+
</template>
|
|
149
|
+
<template v-else>Send</template>
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
</form>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Template, Article, Faq } from "vicket";
|
|
3
|
+
import { cn, stripHtml, sanitizeHtml } from "vicket";
|
|
4
|
+
|
|
5
|
+
/* ---------------------------------------------- */
|
|
6
|
+
/* Props */
|
|
7
|
+
/* ---------------------------------------------- */
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
initData?: { website?: { name?: string }; templates: Template[]; articles?: Article[]; faqs?: Faq[] } | null;
|
|
10
|
+
initError?: string;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
/* ---------------------------------------------- */
|
|
14
|
+
/* Reactive state */
|
|
15
|
+
/* ---------------------------------------------- */
|
|
16
|
+
const templates = computed(() => props.initData?.templates || []);
|
|
17
|
+
const articles = computed(() => props.initData?.articles || []);
|
|
18
|
+
const faqs = computed(() => props.initData?.faqs || []);
|
|
19
|
+
const websiteName = computed(() => props.initData?.website?.name || "Support");
|
|
20
|
+
const error = ref(props.initError || "");
|
|
21
|
+
const searchQuery = ref("");
|
|
22
|
+
const dialogOpen = ref(false);
|
|
23
|
+
const selectedArticle = ref<Article | null>(null);
|
|
24
|
+
|
|
25
|
+
/* FAQ open state */
|
|
26
|
+
const openFaqIds = ref<Set<string>>(new Set());
|
|
27
|
+
|
|
28
|
+
/* ---------------------------------------------- */
|
|
29
|
+
/* Computed */
|
|
30
|
+
/* ---------------------------------------------- */
|
|
31
|
+
const filteredArticles = computed(() => {
|
|
32
|
+
if (!searchQuery.value.trim()) return articles.value;
|
|
33
|
+
const q = searchQuery.value.toLowerCase();
|
|
34
|
+
return articles.value.filter(
|
|
35
|
+
(a) =>
|
|
36
|
+
a.title.toLowerCase().includes(q) ||
|
|
37
|
+
stripHtml(a.content).toLowerCase().includes(q),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const filteredFaqs = computed(() => {
|
|
42
|
+
if (!searchQuery.value.trim()) return faqs.value;
|
|
43
|
+
const q = searchQuery.value.toLowerCase();
|
|
44
|
+
return faqs.value.filter(
|
|
45
|
+
(f) =>
|
|
46
|
+
f.question.toLowerCase().includes(q) ||
|
|
47
|
+
f.answer.toLowerCase().includes(q),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const hasContent = computed(() => articles.value.length > 0 || faqs.value.length > 0);
|
|
52
|
+
const hasResults = computed(() => filteredArticles.value.length > 0 || filteredFaqs.value.length > 0);
|
|
53
|
+
|
|
54
|
+
/* ---------------------------------------------- */
|
|
55
|
+
/* FAQ accordion */
|
|
56
|
+
/* ---------------------------------------------- */
|
|
57
|
+
const toggleFaq = (id: string) => {
|
|
58
|
+
const next = new Set(openFaqIds.value);
|
|
59
|
+
if (next.has(id)) {
|
|
60
|
+
next.delete(id);
|
|
61
|
+
} else {
|
|
62
|
+
next.add(id);
|
|
63
|
+
}
|
|
64
|
+
openFaqIds.value = next;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const isFaqOpen = (id: string) => openFaqIds.value.has(id);
|
|
68
|
+
|
|
69
|
+
/* ---------------------------------------------- */
|
|
70
|
+
/* Dialog helpers */
|
|
71
|
+
/* ---------------------------------------------- */
|
|
72
|
+
const openDialog = () => {
|
|
73
|
+
dialogOpen.value = true;
|
|
74
|
+
};
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<!-- Article viewer -->
|
|
79
|
+
<div v-if="selectedArticle" class="vk-shell">
|
|
80
|
+
<div class="vk-page vk-animate-in">
|
|
81
|
+
<!-- Hero stays visible -->
|
|
82
|
+
<div class="vk-hero-row">
|
|
83
|
+
<div>
|
|
84
|
+
<h1 class="vk-hero-title">{{ websiteName }}</h1>
|
|
85
|
+
<p class="vk-hero-subtitle">How can we help you today?</p>
|
|
86
|
+
</div>
|
|
87
|
+
<button
|
|
88
|
+
v-if="templates.length > 0"
|
|
89
|
+
type="button"
|
|
90
|
+
class="vk-button primary pill"
|
|
91
|
+
@click="openDialog"
|
|
92
|
+
>
|
|
93
|
+
💬 Contact Support
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Article content -->
|
|
98
|
+
<div class="vk-article-viewer">
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class="vk-back-button"
|
|
102
|
+
@click="selectedArticle = null"
|
|
103
|
+
>
|
|
104
|
+
← Back to articles
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
<div class="vk-article-viewer-card">
|
|
108
|
+
<h2 class="vk-article-viewer-title">{{ selectedArticle.title }}</h2>
|
|
109
|
+
<div
|
|
110
|
+
class="vk-article-viewer-content vk-message-html"
|
|
111
|
+
v-html="sanitizeHtml(selectedArticle.content)"
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<VicketTicketDialog
|
|
118
|
+
v-model="dialogOpen"
|
|
119
|
+
:templates="templates"
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Home view -->
|
|
124
|
+
<div v-else class="vk-shell">
|
|
125
|
+
<div class="vk-page vk-animate-in">
|
|
126
|
+
<!-- Row 1 - Hero -->
|
|
127
|
+
<div class="vk-hero-row">
|
|
128
|
+
<div>
|
|
129
|
+
<h1 class="vk-hero-title">{{ websiteName }}</h1>
|
|
130
|
+
<p class="vk-hero-subtitle">How can we help you today?</p>
|
|
131
|
+
</div>
|
|
132
|
+
<div v-if="templates.length > 0" class="vk-hero-cta">
|
|
133
|
+
<span class="vk-hero-cta-hint">Can't find what you're looking for?</span>
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
class="vk-button primary pill"
|
|
137
|
+
@click="openDialog"
|
|
138
|
+
>
|
|
139
|
+
💬 Contact Support
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Alerts -->
|
|
145
|
+
<div
|
|
146
|
+
v-if="error"
|
|
147
|
+
:class="cn('vk-alert vk-slide-up', 'error')"
|
|
148
|
+
role="alert"
|
|
149
|
+
>
|
|
150
|
+
<span>⚠</span>
|
|
151
|
+
<span>{{ error }}</span>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
class="vk-alert-dismiss"
|
|
155
|
+
aria-label="Dismiss"
|
|
156
|
+
@click="error = ''"
|
|
157
|
+
>
|
|
158
|
+
✕
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<!-- Row 2 - Search bar -->
|
|
163
|
+
<div v-if="hasContent" class="vk-search-wrap">
|
|
164
|
+
<span class="vk-search-icon">🔍</span>
|
|
165
|
+
<input
|
|
166
|
+
type="text"
|
|
167
|
+
class="vk-search-input"
|
|
168
|
+
placeholder="Search articles and FAQs..."
|
|
169
|
+
v-model="searchQuery"
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- Row 3 - Split content -->
|
|
174
|
+
<div v-if="hasResults" class="vk-content-grid">
|
|
175
|
+
<!-- Left: Articles -->
|
|
176
|
+
<div v-if="filteredArticles.length > 0">
|
|
177
|
+
<div class="vk-section-title-row">
|
|
178
|
+
<span class="vk-section-title-icon">📄</span>
|
|
179
|
+
<h2 class="vk-section-title">Popular Articles</h2>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="vk-article-list">
|
|
182
|
+
<button
|
|
183
|
+
v-for="article in filteredArticles"
|
|
184
|
+
:key="article.id"
|
|
185
|
+
type="button"
|
|
186
|
+
class="vk-article-card"
|
|
187
|
+
@click="selectedArticle = article"
|
|
188
|
+
>
|
|
189
|
+
<div class="vk-article-icon" aria-hidden="true">📄</div>
|
|
190
|
+
<div>
|
|
191
|
+
<h3 class="vk-article-title">{{ article.title }}</h3>
|
|
192
|
+
<p v-if="article.content" class="vk-article-preview">
|
|
193
|
+
{{ stripHtml(article.content).substring(0, 150) }}
|
|
194
|
+
</p>
|
|
195
|
+
</div>
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Right: FAQs -->
|
|
201
|
+
<div v-if="filteredFaqs.length > 0">
|
|
202
|
+
<div class="vk-section-title-row">
|
|
203
|
+
<span class="vk-section-title-icon">❔</span>
|
|
204
|
+
<h2 class="vk-section-title">Frequently Asked Questions</h2>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="vk-faq-list">
|
|
207
|
+
<div
|
|
208
|
+
v-for="faq in filteredFaqs"
|
|
209
|
+
:key="faq.id"
|
|
210
|
+
class="vk-faq-item"
|
|
211
|
+
>
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
class="vk-faq-question"
|
|
215
|
+
:aria-expanded="isFaqOpen(faq.id)"
|
|
216
|
+
@click="toggleFaq(faq.id)"
|
|
217
|
+
>
|
|
218
|
+
{{ faq.question }}
|
|
219
|
+
<span :class="cn('vk-faq-chevron', isFaqOpen(faq.id) && 'open')">▼</span>
|
|
220
|
+
</button>
|
|
221
|
+
<div :class="cn('vk-faq-body', isFaqOpen(faq.id) && 'open')">
|
|
222
|
+
<div>
|
|
223
|
+
<div class="vk-faq-answer">{{ faq.answer }}</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<!-- No search results -->
|
|
232
|
+
<div v-if="searchQuery.trim() && !hasResults" class="vk-no-results">
|
|
233
|
+
No results found for “{{ searchQuery }}”
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<!-- Empty state - no articles and no FAQs at all -->
|
|
237
|
+
<div v-if="!hasContent && !error" class="vk-cta-card">
|
|
238
|
+
<div class="vk-cta-icon">💬</div>
|
|
239
|
+
<p class="vk-cta-text">Need help? Our team is here for you.</p>
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
class="vk-button primary pill"
|
|
243
|
+
@click="openDialog"
|
|
244
|
+
>
|
|
245
|
+
Contact Support
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<VicketTicketDialog
|
|
251
|
+
v-model="dialogOpen"
|
|
252
|
+
:templates="templates"
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Template, FormValues } from "
|
|
3
|
-
import { cn, initialFormValues, createTicket } from "
|
|
2
|
+
import type { Template, FormValues } from "vicket";
|
|
3
|
+
import { cn, initialFormValues, createTicket } from "vicket";
|
|
4
4
|
|
|
5
5
|
/* ---------------------------------------------- */
|
|
6
6
|
/* Props & Emits */
|