@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,249 +0,0 @@
1
- /* ── Types ─────────────────────────────────────── */
2
-
3
- export type TemplateOption = {
4
- id: string;
5
- label: string;
6
- value: string;
7
- };
8
-
9
- export type TemplateQuestion = {
10
- id: string;
11
- label: string;
12
- type: "TEXT" | "TEXTAREA" | "SELECT" | "CHECKBOX" | "DATE" | "FILE";
13
- required: boolean;
14
- order: number;
15
- options?: TemplateOption[];
16
- };
17
-
18
- export type Template = {
19
- id: string;
20
- name: string;
21
- description: string;
22
- questions: TemplateQuestion[];
23
- };
24
-
25
- export type Article = {
26
- id: string;
27
- title: string;
28
- slug: string;
29
- content: string;
30
- };
31
-
32
- export type Faq = {
33
- id: string;
34
- question: string;
35
- answer: string;
36
- };
37
-
38
- export type SupportInitResponse = {
39
- success: boolean;
40
- data?: {
41
- website?: { name?: string };
42
- templates: Template[];
43
- articles?: Article[];
44
- faqs?: Faq[];
45
- };
46
- error?: string;
47
- };
48
-
49
- export type FormValues = {
50
- email: string;
51
- title: string;
52
- answers: Record<string, unknown>;
53
- };
54
-
55
- export type Attachment = {
56
- id: string;
57
- original_filename: string;
58
- url: string;
59
- };
60
-
61
- export type Message = {
62
- id: string;
63
- content: string;
64
- author_type: "reporter" | "user" | "system";
65
- created_at: string;
66
- attachments?: Attachment[];
67
- };
68
-
69
- export type TicketAnswer = {
70
- id: string;
71
- question_label: string;
72
- answer: string;
73
- attachments?: Attachment[];
74
- };
75
-
76
- export type TicketThread = {
77
- id: string;
78
- title: string;
79
- status?: { label: string };
80
- priority?: { label: string };
81
- messages: Message[];
82
- answers?: TicketAnswer[];
83
- };
84
-
85
- /* ── Constants ─────────────────────────────────── */
86
-
87
- const PROXY_BASE = "/api/vicket";
88
-
89
- export const AUTHOR_LABELS: Record<string, string> = {
90
- reporter: "You",
91
- user: "Support",
92
- system: "System",
93
- };
94
-
95
- export const initialFormValues: FormValues = { email: "", title: "", answers: {} };
96
-
97
- /* ── Helpers ───────────────────────────────────── */
98
-
99
- export function cn(...classes: (string | false | null | undefined)[]): string {
100
- return classes.filter(Boolean).join(" ");
101
- }
102
-
103
- export function stripHtml(html: string): string {
104
- return html.replace(/<[^>]*>/g, "");
105
- }
106
-
107
- export function sanitizeHtml(html: string): string {
108
- return html
109
- .replace(/<script[\s\S]*?<\/script>/gi, "")
110
- .replace(/\son\w+="[^"]*"/gi, "")
111
- .replace(/\son\w+='[^']*'/gi, "")
112
- .replace(/href\s*=\s*"javascript:[^"]*"/gi, 'href="#"')
113
- .replace(/href\s*=\s*'javascript:[^']*'/gi, "href='#'");
114
- }
115
-
116
- export function formatDate(iso: string): string {
117
- try {
118
- return new Intl.DateTimeFormat("en", {
119
- month: "short",
120
- day: "numeric",
121
- hour: "numeric",
122
- minute: "2-digit",
123
- }).format(new Date(iso));
124
- } catch {
125
- return iso;
126
- }
127
- }
128
-
129
- export function isFileAnswer(answer: string): boolean {
130
- return answer?.includes("__isFile:true") || answer?.includes("map[__isFile");
131
- }
132
-
133
- export function formatAnswerText(value: string): string {
134
- if (!value) return "";
135
- const trimmed = value.trim();
136
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
137
- const rawItems = trimmed.slice(1, -1).trim();
138
- return rawItems.length > 0 ? rawItems.split(/\s+/).join(", ") : "";
139
- }
140
- return value;
141
- }
142
-
143
- /* ── API (uses Nuxt $fetch) ────────────────────── */
144
-
145
- export async function fetchInit(): Promise<NonNullable<SupportInitResponse["data"]>> {
146
- const payload = await $fetch<SupportInitResponse>(`${PROXY_BASE}/init`, { method: "GET" });
147
- if (!payload?.success || !payload?.data) {
148
- throw new Error(payload?.error || "Failed to load support data.");
149
- }
150
- return payload.data;
151
- }
152
-
153
- export async function createTicket(body: {
154
- email: string;
155
- title: string;
156
- templateId: string;
157
- answers: Record<string, unknown>;
158
- hasFiles: boolean;
159
- fileQuestionIds: string[];
160
- }): Promise<{ emailLimitReached?: boolean; warning?: string }> {
161
- const payload = {
162
- email: body.email,
163
- title: body.title,
164
- templateId: body.templateId,
165
- answers: { ...body.answers },
166
- };
167
-
168
- let response: Response;
169
- if (body.hasFiles) {
170
- const formData = new FormData();
171
- const normalizedAnswers: Record<string, unknown> = {};
172
- for (const [questionId, answer] of Object.entries(payload.answers)) {
173
- if (answer instanceof File) {
174
- formData.append(`files[${questionId}]`, answer);
175
- normalizedAnswers[questionId] = "__isFile:true";
176
- } else {
177
- normalizedAnswers[questionId] = answer;
178
- }
179
- }
180
- formData.append("data", JSON.stringify({ ...payload, answers: normalizedAnswers }));
181
- response = await fetch(`${PROXY_BASE}/tickets`, { method: "POST", body: formData });
182
- } else {
183
- response = await fetch(`${PROXY_BASE}/tickets`, {
184
- method: "POST",
185
- headers: { "Content-Type": "application/json" },
186
- body: JSON.stringify(payload),
187
- });
188
- }
189
-
190
- const responsePayload = (await response.json()) as {
191
- error?: string;
192
- success?: boolean;
193
- data?: { email_limit_reached?: boolean; warning?: string };
194
- };
195
- if (!response.ok || !responsePayload?.success) {
196
- throw new Error(responsePayload?.error || "Failed to create ticket.");
197
- }
198
- return {
199
- emailLimitReached: responsePayload.data?.email_limit_reached ?? false,
200
- warning: responsePayload.data?.warning,
201
- };
202
- }
203
-
204
- export async function fetchThread(token: string): Promise<TicketThread> {
205
- const payload = await $fetch<{
206
- success?: boolean;
207
- error?: string;
208
- error_code?: string;
209
- data?: TicketThread;
210
- }>(`${PROXY_BASE}/thread?token=${encodeURIComponent(token)}`, { method: "GET" });
211
-
212
- if (!payload?.success || !payload?.data) {
213
- if (payload?.error_code === "ticket-link-expired") {
214
- throw new Error("This link has expired. A new secure link has been sent to your email.");
215
- }
216
- throw new Error(payload?.error || "Failed to load ticket.");
217
- }
218
- return payload.data;
219
- }
220
-
221
- export async function sendReply(token: string, content: string, files: File[]): Promise<void> {
222
- const url = `${PROXY_BASE}/messages?token=${encodeURIComponent(token)}`;
223
-
224
- let response: Response;
225
- if (files.length > 0) {
226
- const formData = new FormData();
227
- formData.append("data", JSON.stringify({ content }));
228
- for (const file of files) formData.append("files", file);
229
- response = await fetch(url, { method: "POST", body: formData });
230
- } else {
231
- response = await fetch(url, {
232
- method: "POST",
233
- headers: { "Content-Type": "application/json" },
234
- body: JSON.stringify({ content }),
235
- });
236
- }
237
-
238
- const responsePayload = (await response.json()) as {
239
- success?: boolean;
240
- error?: string;
241
- error_code?: string;
242
- };
243
- if (!response.ok || !responsePayload?.success) {
244
- if (responsePayload?.error_code === "ticket-link-expired") {
245
- throw new Error("This link has expired. A new secure link has been sent to your email.");
246
- }
247
- throw new Error(responsePayload?.error || "Failed to send reply.");
248
- }
249
- }
@@ -1,22 +0,0 @@
1
- import { defineEventHandler } from "h3";
2
-
3
- export default defineEventHandler(async (event) => {
4
- const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
5
- const apiKey = process.env.VICKET_API_KEY || "";
6
-
7
- if (!apiUrl || !apiKey) {
8
- throw createError({
9
- statusCode: 500,
10
- statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
11
- });
12
- }
13
-
14
- const response = await fetch(`${apiUrl}/public/support/init`, {
15
- method: "GET",
16
- headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
17
- });
18
-
19
- const data = await response.json();
20
- event.node.res.statusCode = response.status;
21
- return data;
22
- });
@@ -1,56 +0,0 @@
1
- import { defineEventHandler, getQuery, readBody, readMultipartFormData } from "h3";
2
-
3
- export default defineEventHandler(async (event) => {
4
- const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
5
- const apiKey = process.env.VICKET_API_KEY || "";
6
-
7
- if (!apiUrl || !apiKey) {
8
- throw createError({
9
- statusCode: 500,
10
- statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
11
- });
12
- }
13
-
14
- const query = getQuery(event);
15
- const token = String(query.token || "");
16
- const targetUrl = `${apiUrl}/public/support/ticket/messages?token=${encodeURIComponent(token)}`;
17
-
18
- const contentType = event.node.req.headers["content-type"] || "";
19
- const isMultipart = contentType.includes("multipart/form-data");
20
-
21
- let fetchOptions: RequestInit;
22
-
23
- if (isMultipart) {
24
- const parts = await readMultipartFormData(event);
25
- const formData = new FormData();
26
-
27
- if (parts) {
28
- for (const part of parts) {
29
- if (part.filename) {
30
- const blob = new Blob([part.data], { type: part.type || "application/octet-stream" });
31
- formData.append(part.name || "file", blob, part.filename);
32
- } else {
33
- formData.append(part.name || "field", part.data.toString("utf-8"));
34
- }
35
- }
36
- }
37
-
38
- fetchOptions = {
39
- method: "POST",
40
- headers: { "X-Api-Key": apiKey },
41
- body: formData,
42
- };
43
- } else {
44
- const body = await readBody(event);
45
- fetchOptions = {
46
- method: "POST",
47
- headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
48
- body: body ? JSON.stringify(body) : undefined,
49
- };
50
- }
51
-
52
- const response = await fetch(targetUrl, fetchOptions);
53
- const data = await response.json();
54
- event.node.res.statusCode = response.status;
55
- return data;
56
- });
@@ -1,26 +0,0 @@
1
- import { defineEventHandler, getQuery } from "h3";
2
-
3
- export default defineEventHandler(async (event) => {
4
- const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
5
- const apiKey = process.env.VICKET_API_KEY || "";
6
-
7
- if (!apiUrl || !apiKey) {
8
- throw createError({
9
- statusCode: 500,
10
- statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
11
- });
12
- }
13
-
14
- const query = getQuery(event);
15
- const token = String(query.token || "");
16
- const targetUrl = `${apiUrl}/public/support/ticket?token=${encodeURIComponent(token)}`;
17
-
18
- const response = await fetch(targetUrl, {
19
- method: "GET",
20
- headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
21
- });
22
-
23
- const data = await response.json();
24
- event.node.res.statusCode = response.status;
25
- return data;
26
- });
@@ -1,53 +0,0 @@
1
- import { defineEventHandler, readBody, readMultipartFormData } from "h3";
2
-
3
- export default defineEventHandler(async (event) => {
4
- const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
5
- const apiKey = process.env.VICKET_API_KEY || "";
6
-
7
- if (!apiUrl || !apiKey) {
8
- throw createError({
9
- statusCode: 500,
10
- statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
11
- });
12
- }
13
-
14
- const contentType = event.node.req.headers["content-type"] || "";
15
- const isMultipart = contentType.includes("multipart/form-data");
16
- const targetUrl = `${apiUrl}/public/support/tickets`;
17
-
18
- let fetchOptions: RequestInit;
19
-
20
- if (isMultipart) {
21
- const parts = await readMultipartFormData(event);
22
- const formData = new FormData();
23
-
24
- if (parts) {
25
- for (const part of parts) {
26
- if (part.filename) {
27
- const blob = new Blob([part.data], { type: part.type || "application/octet-stream" });
28
- formData.append(part.name || "file", blob, part.filename);
29
- } else {
30
- formData.append(part.name || "field", part.data.toString("utf-8"));
31
- }
32
- }
33
- }
34
-
35
- fetchOptions = {
36
- method: "POST",
37
- headers: { "X-Api-Key": apiKey },
38
- body: formData,
39
- };
40
- } else {
41
- const body = await readBody(event);
42
- fetchOptions = {
43
- method: "POST",
44
- headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
45
- body: body ? JSON.stringify(body) : undefined,
46
- };
47
- }
48
-
49
- const response = await fetch(targetUrl, fetchOptions);
50
- const data = await response.json();
51
- event.node.res.statusCode = response.status;
52
- return data;
53
- });