@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.
Files changed (71) hide show
  1. package/bin/create-vicket-support.js +429 -389
  2. package/package.json +1 -1
  3. package/templates/next/src/app/api/vicket/[...path]/route.ts +2 -55
  4. package/templates/next/src/app/components/vicket/ReplyForm.tsx +154 -0
  5. package/templates/next/src/app/components/vicket/SupportContent.tsx +298 -0
  6. package/templates/next/src/app/components/vicket/TicketDialog.tsx +3 -3
  7. package/templates/next/src/app/support/page.tsx +27 -353
  8. package/templates/next/src/app/ticket/page.tsx +110 -325
  9. package/templates/next/src/app/vicket.css +1325 -1325
  10. package/templates/nuxt/app/assets/css/vicket.css +1325 -1325
  11. package/templates/nuxt/app/components/VicketReplyForm.vue +154 -0
  12. package/templates/nuxt/app/components/VicketSupportContent.vue +255 -0
  13. package/templates/nuxt/app/components/VicketTicketDialog.vue +2 -2
  14. package/templates/nuxt/app/pages/support.vue +7 -293
  15. package/templates/nuxt/app/pages/ticket.vue +36 -178
  16. package/templates/nuxt/server/api/vicket/[...path].ts +2 -85
  17. package/templates/sveltekit/src/lib/vicket/ReplyForm.svelte +134 -0
  18. package/templates/sveltekit/src/lib/vicket/SupportContent.svelte +263 -0
  19. package/templates/sveltekit/src/lib/vicket/TicketDialog.svelte +457 -459
  20. package/templates/sveltekit/src/lib/vicket.css +1325 -1325
  21. package/templates/sveltekit/src/routes/api/vicket/[...path]/+server.ts +2 -76
  22. package/templates/sveltekit/src/routes/support/+page.server.ts +13 -0
  23. package/templates/sveltekit/src/routes/support/+page.svelte +3 -312
  24. package/templates/sveltekit/src/routes/ticket/+page.server.ts +19 -0
  25. package/templates/sveltekit/src/routes/ticket/+page.svelte +13 -188
  26. package/templates-tailwind/next/src/app/api/vicket/[...path]/route.ts +6 -0
  27. package/templates-tailwind/next/src/app/support/page.tsx +33 -3
  28. package/templates-tailwind/next/src/app/ticket/page.tsx +249 -6
  29. package/templates-tailwind/next/src/components/vicket/reply-form.tsx +113 -0
  30. package/templates-tailwind/next/src/components/vicket/support-content.tsx +265 -0
  31. package/templates-tailwind/next/src/components/vicket/ticket-dialog.tsx +2 -2
  32. package/templates-tailwind/nuxt/app/components/VicketReplyForm.vue +169 -0
  33. package/templates-tailwind/nuxt/app/components/{VicketSupportPage.vue → VicketSupportContent.vue} +275 -317
  34. package/templates-tailwind/nuxt/app/components/VicketTicketDialog.vue +3 -0
  35. package/templates-tailwind/nuxt/app/pages/support.vue +10 -1
  36. package/templates-tailwind/nuxt/app/pages/ticket.vue +298 -1
  37. package/templates-tailwind/nuxt/server/api/vicket/[...path].ts +2 -0
  38. package/templates-tailwind/sveltekit/src/lib/vicket/ReplyForm.svelte +127 -0
  39. package/templates-tailwind/sveltekit/src/lib/vicket/{SupportPage.svelte → SupportContent.svelte} +9 -71
  40. package/templates-tailwind/sveltekit/src/lib/vicket/TicketDialog.svelte +405 -406
  41. package/templates-tailwind/sveltekit/src/routes/api/vicket/[...path]/+server.ts +3 -0
  42. package/templates-tailwind/sveltekit/src/routes/support/+page.server.ts +13 -0
  43. package/templates-tailwind/sveltekit/src/routes/support/+page.svelte +4 -2
  44. package/templates-tailwind/sveltekit/src/routes/ticket/+page.server.ts +19 -0
  45. package/templates-tailwind/sveltekit/src/routes/ticket/+page.svelte +292 -2
  46. package/templates/next/src/app/utils/vicket/api.ts +0 -149
  47. package/templates/next/src/app/utils/vicket/types.ts +0 -85
  48. package/templates/next/src/app/utils/vicket/utils.ts +0 -49
  49. package/templates/nuxt/app/composables/useVicket.ts +0 -274
  50. package/templates/sveltekit/src/lib/vicket/api.ts +0 -162
  51. package/templates/sveltekit/src/lib/vicket/types.ts +0 -87
  52. package/templates/sveltekit/src/lib/vicket/utils.ts +0 -55
  53. package/templates-tailwind/next/src/app/api/vicket/init/route.ts +0 -24
  54. package/templates-tailwind/next/src/app/api/vicket/messages/route.ts +0 -36
  55. package/templates-tailwind/next/src/app/api/vicket/thread/route.ts +0 -27
  56. package/templates-tailwind/next/src/app/api/vicket/tickets/route.ts +0 -37
  57. package/templates-tailwind/next/src/components/vicket/support-page.tsx +0 -359
  58. package/templates-tailwind/next/src/components/vicket/ticket-page.tsx +0 -425
  59. package/templates-tailwind/next/src/lib/vicket.ts +0 -257
  60. package/templates-tailwind/nuxt/app/components/VicketTicketPage.vue +0 -449
  61. package/templates-tailwind/nuxt/app/composables/use-vicket.ts +0 -249
  62. package/templates-tailwind/nuxt/server/api/vicket/init.get.ts +0 -22
  63. package/templates-tailwind/nuxt/server/api/vicket/messages.post.ts +0 -56
  64. package/templates-tailwind/nuxt/server/api/vicket/thread.get.ts +0 -26
  65. package/templates-tailwind/nuxt/server/api/vicket/tickets.post.ts +0 -53
  66. package/templates-tailwind/sveltekit/src/lib/vicket/TicketPage.svelte +0 -465
  67. package/templates-tailwind/sveltekit/src/lib/vicket/index.ts +0 -257
  68. package/templates-tailwind/sveltekit/src/routes/api/vicket/init/+server.ts +0 -22
  69. package/templates-tailwind/sveltekit/src/routes/api/vicket/messages/+server.ts +0 -40
  70. package/templates-tailwind/sveltekit/src/routes/api/vicket/thread/+server.ts +0 -25
  71. package/templates-tailwind/sveltekit/src/routes/api/vicket/tickets/+server.ts +0 -37
@@ -1,358 +1,32 @@
1
- "use client";
2
-
3
- import { useEffect, useMemo, useState } from "react";
4
- import "../vicket.css";
5
- import type { Article, Faq, Template } from "../utils/vicket/types";
6
- import { cn, stripHtml, sanitizeHtml } from "../utils/vicket/utils";
7
- import { fetchSupportInit } from "../utils/vicket/api";
8
- import TicketDialog from "../components/vicket/TicketDialog";
9
-
10
- /* ---------------------------------------------- */
11
- /* Skeleton loader */
12
- /* ---------------------------------------------- */
13
- function HomeSkeleton() {
14
- return (
15
- <div className="vk-shell">
16
- <div className="vk-page vk-animate-in">
17
- <div className="vk-hero-row">
18
- <div>
19
- <div className="vk-skeleton" aria-hidden="true">
20
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
21
- </div>
22
- <div className="vk-skeleton" aria-hidden="true">
23
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
24
- </div>
25
- </div>
26
- <div className="vk-skeleton" aria-hidden="true">
27
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
28
- </div>
29
- </div>
30
- <div className="vk-search-wrap">
31
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
32
- </div>
33
- <div className="vk-content-grid">
34
- <div>
35
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
36
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
37
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
38
- </div>
39
- <div>
40
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
41
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
42
- <div className="vk-skeleton" aria-hidden="true">&nbsp;</div>
43
- </div>
44
- </div>
45
- </div>
46
- </div>
47
- );
48
- }
49
-
50
- /* ---------------------------------------------- */
51
- /* Alert component */
52
- /* ---------------------------------------------- */
53
- function Alert({
54
- type,
55
- message,
56
- onDismiss,
57
- }: {
58
- type: "error" | "success";
59
- message: string;
60
- onDismiss?: () => void;
61
- }) {
62
- return (
63
- <div
64
- className={cn("vk-alert vk-slide-up", type === "error" ? "error" : "success")}
65
- role="alert"
66
- >
67
- <span>{type === "error" ? "\u26A0" : "\u2713"}</span>
68
- <span>{message}</span>
69
- {onDismiss && (
70
- <button
71
- type="button"
72
- onClick={onDismiss}
73
- className="vk-alert-dismiss"
74
- aria-label="Dismiss"
75
- >
76
- &#10005;
77
- </button>
78
- )}
79
- </div>
80
- );
81
- }
82
-
83
- /* ---------------------------------------------- */
84
- /* FAQ Accordion item */
85
- /* ---------------------------------------------- */
86
- function FaqItem({ question, answer }: { question: string; answer: string }) {
87
- const [open, setOpen] = useState(false);
88
-
89
- return (
90
- <div className="vk-faq-item">
91
- <button
92
- type="button"
93
- onClick={() => setOpen(!open)}
94
- className="vk-faq-question"
95
- aria-expanded={open}
96
- >
97
- {question}
98
- <span className={cn("vk-faq-chevron", open && "open")}>&#9660;</span>
99
- </button>
100
- <div className={cn("vk-faq-body", open && "open")}>
101
- <div>
102
- <div className="vk-faq-answer">{answer}</div>
103
- </div>
104
- </div>
105
- </div>
106
- );
107
- }
108
-
109
- /* ---------------------------------------------- */
110
- /* Main page */
111
- /* ---------------------------------------------- */
112
- export default function SupportPage() {
113
- const [templates, setTemplates] = useState<Template[]>([]);
114
- const [articles, setArticles] = useState<Article[]>([]);
115
- const [faqs, setFaqs] = useState<Faq[]>([]);
116
- const [websiteName, setWebsiteName] = useState("Support");
117
- const [isLoading, setIsLoading] = useState(true);
118
- const [error, setError] = useState("");
119
- const [searchQuery, setSearchQuery] = useState("");
120
- const [dialogOpen, setDialogOpen] = useState(false);
121
- const [selectedArticle, setSelectedArticle] = useState<Article | null>(null);
122
-
123
- useEffect(() => {
124
- let isMounted = true;
125
- const load = async () => {
126
- setIsLoading(true);
127
- setError("");
128
- try {
129
- const data = await fetchSupportInit();
130
- if (!isMounted) return;
131
- setTemplates(data.templates || []);
132
- setArticles(data.articles || []);
133
- setFaqs(data.faqs || []);
134
- setWebsiteName(data.website?.name || "Support");
135
- } catch (loadError) {
136
- if (!isMounted) return;
137
- setError(loadError instanceof Error ? loadError.message : "Unexpected error.");
138
- } finally {
139
- if (isMounted) setIsLoading(false);
140
- }
141
- };
142
-
143
- load();
144
- return () => {
145
- isMounted = false;
146
- };
147
- }, []);
148
-
149
- /* Filtered articles & FAQs by search query */
150
- const filteredArticles = useMemo(() => {
151
- if (!searchQuery.trim()) return articles;
152
- const q = searchQuery.toLowerCase();
153
- return articles.filter(
154
- (a) =>
155
- a.title.toLowerCase().includes(q) ||
156
- stripHtml(a.content).toLowerCase().includes(q),
157
- );
158
- }, [articles, searchQuery]);
159
-
160
- const filteredFaqs = useMemo(() => {
161
- if (!searchQuery.trim()) return faqs;
162
- const q = searchQuery.toLowerCase();
163
- return faqs.filter(
164
- (f) =>
165
- f.question.toLowerCase().includes(q) ||
166
- f.answer.toLowerCase().includes(q),
167
- );
168
- }, [faqs, searchQuery]);
169
-
170
- const hasContent = articles.length > 0 || faqs.length > 0;
171
- const hasResults = filteredArticles.length > 0 || filteredFaqs.length > 0;
172
-
173
- /* Loading state */
174
- if (isLoading) {
175
- return <HomeSkeleton />;
176
- }
177
-
178
- /* Article viewer */
179
- if (selectedArticle) {
180
- return (
181
- <div className="vk-shell">
182
- <div className="vk-page vk-animate-in">
183
- {/* Hero stays visible */}
184
- <div className="vk-hero-row">
185
- <div>
186
- <h1 className="vk-hero-title">{websiteName}</h1>
187
- <p className="vk-hero-subtitle">How can we help you today?</p>
188
- </div>
189
- {templates.length > 0 && (
190
- <button
191
- type="button"
192
- className="vk-button primary pill"
193
- onClick={() => setDialogOpen(true)}
194
- >
195
- &#128172; Contact Support
196
- </button>
197
- )}
198
- </div>
199
-
200
- {/* Article content */}
201
- <div className="vk-article-viewer">
202
- <button
203
- type="button"
204
- onClick={() => setSelectedArticle(null)}
205
- className="vk-back-button"
206
- >
207
- &#8592; Back to articles
208
- </button>
209
-
210
- <div className="vk-article-viewer-card">
211
- <h2 className="vk-article-viewer-title">{selectedArticle.title}</h2>
212
- <div
213
- className="vk-article-viewer-content vk-message-html"
214
- dangerouslySetInnerHTML={{
215
- __html: sanitizeHtml(selectedArticle.content),
216
- }}
217
- />
218
- </div>
219
- </div>
220
- </div>
221
-
222
- <TicketDialog
223
- open={dialogOpen}
224
- onClose={() => setDialogOpen(false)}
225
- templates={templates}
226
- />
227
- </div>
228
- );
1
+ import { createServerClient } from "vicket/server";
2
+ import type { Template, Article, Faq } from "vicket";
3
+ import SupportContent from "../components/vicket/SupportContent";
4
+
5
+ export default async function SupportPage() {
6
+ let templates: Template[] = [];
7
+ let articles: Article[] = [];
8
+ let faqs: Faq[] = [];
9
+ let websiteName = "Support";
10
+ let initError = "";
11
+
12
+ try {
13
+ const vicket = createServerClient();
14
+ const data = await vicket.fetchInit();
15
+ templates = data.templates || [];
16
+ articles = data.articles || [];
17
+ faqs = data.faqs || [];
18
+ websiteName = data.website?.name || "Support";
19
+ } catch (e) {
20
+ initError = e instanceof Error ? e.message : "Failed to load support data.";
229
21
  }
230
22
 
231
- /* Home view */
232
23
  return (
233
- <div className="vk-shell">
234
- <div className="vk-page vk-animate-in">
235
- {/* Row 1 - Hero */}
236
- <div className="vk-hero-row">
237
- <div>
238
- <h1 className="vk-hero-title">{websiteName}</h1>
239
- <p className="vk-hero-subtitle">How can we help you today?</p>
240
- </div>
241
- {templates.length > 0 && (
242
- <div className="vk-hero-cta">
243
- <span className="vk-hero-cta-hint">
244
- Can&apos;t find what you&apos;re looking for?
245
- </span>
246
- <button
247
- type="button"
248
- className="vk-button primary pill"
249
- onClick={() => setDialogOpen(true)}
250
- >
251
- &#128172; Contact Support
252
- </button>
253
- </div>
254
- )}
255
- </div>
256
-
257
- {/* Alerts */}
258
- {error && (
259
- <Alert type="error" message={error} onDismiss={() => setError("")} />
260
- )}
261
-
262
- {/* Row 2 - Search bar */}
263
- {hasContent && (
264
- <div className="vk-search-wrap">
265
- <span className="vk-search-icon">&#128269;</span>
266
- <input
267
- type="text"
268
- className="vk-search-input"
269
- placeholder="Search articles and FAQs..."
270
- value={searchQuery}
271
- onChange={(e) => setSearchQuery(e.target.value)}
272
- />
273
- </div>
274
- )}
275
-
276
- {/* Row 3 - Split content */}
277
- {hasResults && (
278
- <div className="vk-content-grid">
279
- {/* Left: Articles */}
280
- {filteredArticles.length > 0 && (
281
- <div>
282
- <div className="vk-section-title-row">
283
- <span className="vk-section-title-icon">&#128196;</span>
284
- <h2 className="vk-section-title">Popular Articles</h2>
285
- </div>
286
- <div className="vk-article-list">
287
- {filteredArticles.map((article) => (
288
- <button
289
- key={article.id}
290
- type="button"
291
- className="vk-article-card"
292
- onClick={() => setSelectedArticle(article)}
293
- >
294
- <div className="vk-article-icon" aria-hidden="true">
295
- &#128196;
296
- </div>
297
- <div>
298
- <h3 className="vk-article-title">{article.title}</h3>
299
- {article.content && (
300
- <p className="vk-article-preview">
301
- {stripHtml(article.content).substring(0, 150)}
302
- </p>
303
- )}
304
- </div>
305
- </button>
306
- ))}
307
- </div>
308
- </div>
309
- )}
310
-
311
- {/* Right: FAQs */}
312
- {filteredFaqs.length > 0 && (
313
- <div>
314
- <div className="vk-section-title-row">
315
- <span className="vk-section-title-icon">&#10068;</span>
316
- <h2 className="vk-section-title">Frequently Asked Questions</h2>
317
- </div>
318
- <div className="vk-faq-list">
319
- {filteredFaqs.map((faq) => (
320
- <FaqItem key={faq.id} question={faq.question} answer={faq.answer} />
321
- ))}
322
- </div>
323
- </div>
324
- )}
325
- </div>
326
- )}
327
-
328
- {/* No search results */}
329
- {searchQuery.trim() && !hasResults && (
330
- <div className="vk-no-results">
331
- No results found for &ldquo;{searchQuery}&rdquo;
332
- </div>
333
- )}
334
-
335
- {/* Empty state - no articles and no FAQs at all */}
336
- {!hasContent && !error && (
337
- <div className="vk-cta-card">
338
- <div className="vk-cta-icon">&#128172;</div>
339
- <p className="vk-cta-text">Need help? Our team is here for you.</p>
340
- <button
341
- type="button"
342
- className="vk-button primary pill"
343
- onClick={() => setDialogOpen(true)}
344
- >
345
- Contact Support
346
- </button>
347
- </div>
348
- )}
349
- </div>
350
-
351
- <TicketDialog
352
- open={dialogOpen}
353
- onClose={() => setDialogOpen(false)}
354
- templates={templates}
355
- />
356
- </div>
24
+ <SupportContent
25
+ templates={templates}
26
+ articles={articles}
27
+ faqs={faqs}
28
+ websiteName={websiteName}
29
+ initialError={initError}
30
+ />
357
31
  );
358
32
  }