@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,257 +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 | boolean | undefined | null)[]): 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 client ────────────────────────────────── */
144
-
145
- export async function fetchInit(): Promise<NonNullable<SupportInitResponse["data"]>> {
146
- const response = await fetch(`${PROXY_BASE}/init`, {
147
- method: "GET",
148
- cache: "no-store",
149
- headers: { "Content-Type": "application/json" },
150
- });
151
- const payload = (await response.json()) as SupportInitResponse;
152
- if (!response.ok || !payload?.success || !payload?.data) {
153
- throw new Error(payload?.error || "Failed to load support data.");
154
- }
155
- return payload.data;
156
- }
157
-
158
- export async function createTicket(body: {
159
- email: string;
160
- title: string;
161
- templateId: string;
162
- answers: Record<string, unknown>;
163
- hasFiles: boolean;
164
- fileQuestionIds: string[];
165
- }): Promise<{ emailLimitReached?: boolean; warning?: string }> {
166
- const payload = {
167
- email: body.email,
168
- title: body.title,
169
- templateId: body.templateId,
170
- answers: { ...body.answers },
171
- };
172
-
173
- let response: Response;
174
- if (body.hasFiles) {
175
- const formData = new FormData();
176
- const normalizedAnswers: Record<string, unknown> = {};
177
- for (const [questionId, answer] of Object.entries(payload.answers)) {
178
- if (answer instanceof File) {
179
- formData.append(`files[${questionId}]`, answer);
180
- normalizedAnswers[questionId] = "__isFile:true";
181
- } else {
182
- normalizedAnswers[questionId] = answer;
183
- }
184
- }
185
- formData.append("data", JSON.stringify({ ...payload, answers: normalizedAnswers }));
186
- response = await fetch(`${PROXY_BASE}/tickets`, { method: "POST", body: formData });
187
- } else {
188
- response = await fetch(`${PROXY_BASE}/tickets`, {
189
- method: "POST",
190
- headers: { "Content-Type": "application/json" },
191
- body: JSON.stringify(payload),
192
- });
193
- }
194
-
195
- const responsePayload = (await response.json()) as {
196
- error?: string;
197
- success?: boolean;
198
- data?: { email_limit_reached?: boolean; warning?: string };
199
- };
200
- if (!response.ok || !responsePayload?.success) {
201
- throw new Error(responsePayload?.error || "Failed to create ticket.");
202
- }
203
- return {
204
- emailLimitReached: responsePayload.data?.email_limit_reached ?? false,
205
- warning: responsePayload.data?.warning,
206
- };
207
- }
208
-
209
- export async function fetchThread(token: string): Promise<TicketThread> {
210
- const response = await fetch(
211
- `${PROXY_BASE}/thread?token=${encodeURIComponent(token)}`,
212
- { method: "GET", cache: "no-store", headers: { "Content-Type": "application/json" } },
213
- );
214
- const payload = (await response.json()) as {
215
- success?: boolean;
216
- error?: string;
217
- error_code?: string;
218
- data?: TicketThread;
219
- };
220
- if (!response.ok || !payload?.success || !payload?.data) {
221
- if (payload?.error_code === "ticket-link-expired") {
222
- throw new Error("This link has expired. A new secure link has been sent to your email.");
223
- }
224
- throw new Error(payload?.error || "Failed to load ticket.");
225
- }
226
- return payload.data;
227
- }
228
-
229
- export async function sendReply(token: string, content: string, files: File[]): Promise<void> {
230
- const url = `${PROXY_BASE}/messages?token=${encodeURIComponent(token)}`;
231
-
232
- let response: Response;
233
- if (files.length > 0) {
234
- const formData = new FormData();
235
- formData.append("data", JSON.stringify({ content }));
236
- for (const file of files) formData.append("files", file);
237
- response = await fetch(url, { method: "POST", body: formData });
238
- } else {
239
- response = await fetch(url, {
240
- method: "POST",
241
- headers: { "Content-Type": "application/json" },
242
- body: JSON.stringify({ content }),
243
- });
244
- }
245
-
246
- const payload = (await response.json()) as {
247
- success?: boolean;
248
- error?: string;
249
- error_code?: string;
250
- };
251
- if (!response.ok || !payload?.success) {
252
- if (payload?.error_code === "ticket-link-expired") {
253
- throw new Error("This link has expired. A new secure link has been sent to your email.");
254
- }
255
- throw new Error(payload?.error || "Failed to send reply.");
256
- }
257
- }
@@ -1,22 +0,0 @@
1
- import { VICKET_API_URL, VICKET_API_KEY } from "$env/static/private";
2
- import { json } from "@sveltejs/kit";
3
- import type { RequestHandler } from "./$types";
4
-
5
- const baseUrl = (VICKET_API_URL || "").replace(/\/+$/, "");
6
-
7
- export const GET: RequestHandler = async () => {
8
- if (!baseUrl || !VICKET_API_KEY) {
9
- return json({ success: false, error: "Server misconfigured: missing VICKET_API_URL or VICKET_API_KEY." }, { status: 500 });
10
- }
11
-
12
- const response = await fetch(`${baseUrl}/public/support/init`, {
13
- method: "GET",
14
- headers: { "X-Api-Key": VICKET_API_KEY, "Content-Type": "application/json" },
15
- });
16
-
17
- const body = await response.text();
18
- return new Response(body, {
19
- status: response.status,
20
- headers: { "Content-Type": response.headers.get("Content-Type") || "application/json" },
21
- });
22
- };
@@ -1,40 +0,0 @@
1
- import { VICKET_API_URL, VICKET_API_KEY } from "$env/static/private";
2
- import { json } from "@sveltejs/kit";
3
- import type { RequestHandler } from "./$types";
4
-
5
- const baseUrl = (VICKET_API_URL || "").replace(/\/+$/, "");
6
-
7
- export const POST: RequestHandler = async ({ url, request }) => {
8
- if (!baseUrl || !VICKET_API_KEY) {
9
- return json({ success: false, error: "Server misconfigured: missing VICKET_API_URL or VICKET_API_KEY." }, { status: 500 });
10
- }
11
-
12
- const token = url.searchParams.get("token") || "";
13
- const target = `${baseUrl}/public/support/ticket/messages?token=${encodeURIComponent(token)}`;
14
-
15
- const contentType = request.headers.get("Content-Type") || "";
16
- const isFormData = contentType.includes("multipart/form-data");
17
-
18
- let response: Response;
19
- if (isFormData) {
20
- const formData = await request.formData();
21
- response = await fetch(target, {
22
- method: "POST",
23
- headers: { "X-Api-Key": VICKET_API_KEY },
24
- body: formData,
25
- });
26
- } else {
27
- const body = await request.text();
28
- response = await fetch(target, {
29
- method: "POST",
30
- headers: { "X-Api-Key": VICKET_API_KEY, "Content-Type": "application/json" },
31
- body,
32
- });
33
- }
34
-
35
- const responseBody = await response.text();
36
- return new Response(responseBody, {
37
- status: response.status,
38
- headers: { "Content-Type": response.headers.get("Content-Type") || "application/json" },
39
- });
40
- };
@@ -1,25 +0,0 @@
1
- import { VICKET_API_URL, VICKET_API_KEY } from "$env/static/private";
2
- import { json } from "@sveltejs/kit";
3
- import type { RequestHandler } from "./$types";
4
-
5
- const baseUrl = (VICKET_API_URL || "").replace(/\/+$/, "");
6
-
7
- export const GET: RequestHandler = async ({ url }) => {
8
- if (!baseUrl || !VICKET_API_KEY) {
9
- return json({ success: false, error: "Server misconfigured: missing VICKET_API_URL or VICKET_API_KEY." }, { status: 500 });
10
- }
11
-
12
- const token = url.searchParams.get("token") || "";
13
- const target = `${baseUrl}/public/support/ticket?token=${encodeURIComponent(token)}`;
14
-
15
- const response = await fetch(target, {
16
- method: "GET",
17
- headers: { "X-Api-Key": VICKET_API_KEY, "Content-Type": "application/json" },
18
- });
19
-
20
- const body = await response.text();
21
- return new Response(body, {
22
- status: response.status,
23
- headers: { "Content-Type": response.headers.get("Content-Type") || "application/json" },
24
- });
25
- };
@@ -1,37 +0,0 @@
1
- import { VICKET_API_URL, VICKET_API_KEY } from "$env/static/private";
2
- import { json } from "@sveltejs/kit";
3
- import type { RequestHandler } from "./$types";
4
-
5
- const baseUrl = (VICKET_API_URL || "").replace(/\/+$/, "");
6
-
7
- export const POST: RequestHandler = async ({ request }) => {
8
- if (!baseUrl || !VICKET_API_KEY) {
9
- return json({ success: false, error: "Server misconfigured: missing VICKET_API_URL or VICKET_API_KEY." }, { status: 500 });
10
- }
11
-
12
- const contentType = request.headers.get("Content-Type") || "";
13
- const isFormData = contentType.includes("multipart/form-data");
14
-
15
- let response: Response;
16
- if (isFormData) {
17
- const formData = await request.formData();
18
- response = await fetch(`${baseUrl}/public/support/tickets`, {
19
- method: "POST",
20
- headers: { "X-Api-Key": VICKET_API_KEY },
21
- body: formData,
22
- });
23
- } else {
24
- const body = await request.text();
25
- response = await fetch(`${baseUrl}/public/support/tickets`, {
26
- method: "POST",
27
- headers: { "X-Api-Key": VICKET_API_KEY, "Content-Type": "application/json" },
28
- body,
29
- });
30
- }
31
-
32
- const responseBody = await response.text();
33
- return new Response(responseBody, {
34
- status: response.status,
35
- headers: { "Content-Type": response.headers.get("Content-Type") || "application/json" },
36
- });
37
- };