@vicket/create-support 1.1.1

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 (55) hide show
  1. package/README.md +52 -0
  2. package/bin/create-vicket-support.js +389 -0
  3. package/package.json +18 -0
  4. package/templates/next/src/app/api/vicket/[...path]/route.ts +59 -0
  5. package/templates/next/src/app/components/vicket/TicketDialog.tsx +514 -0
  6. package/templates/next/src/app/support/page.tsx +358 -0
  7. package/templates/next/src/app/ticket/page.tsx +483 -0
  8. package/templates/next/src/app/utils/vicket/api.ts +149 -0
  9. package/templates/next/src/app/utils/vicket/types.ts +85 -0
  10. package/templates/next/src/app/utils/vicket/utils.ts +49 -0
  11. package/templates/next/src/app/vicket.css +1325 -0
  12. package/templates/nuxt/app/assets/css/vicket.css +1325 -0
  13. package/templates/nuxt/app/components/VicketTicketDialog.vue +499 -0
  14. package/templates/nuxt/app/composables/useVicket.ts +274 -0
  15. package/templates/nuxt/app/pages/support.vue +303 -0
  16. package/templates/nuxt/app/pages/ticket.vue +434 -0
  17. package/templates/nuxt/server/api/vicket/[...path].ts +85 -0
  18. package/templates/sveltekit/src/lib/vicket/TicketDialog.svelte +459 -0
  19. package/templates/sveltekit/src/lib/vicket/api.ts +162 -0
  20. package/templates/sveltekit/src/lib/vicket/types.ts +87 -0
  21. package/templates/sveltekit/src/lib/vicket/utils.ts +55 -0
  22. package/templates/sveltekit/src/lib/vicket.css +1325 -0
  23. package/templates/sveltekit/src/routes/api/vicket/[...path]/+server.ts +77 -0
  24. package/templates/sveltekit/src/routes/support/+page.svelte +316 -0
  25. package/templates/sveltekit/src/routes/ticket/+page.svelte +418 -0
  26. package/templates-tailwind/next/src/app/api/vicket/init/route.ts +24 -0
  27. package/templates-tailwind/next/src/app/api/vicket/messages/route.ts +36 -0
  28. package/templates-tailwind/next/src/app/api/vicket/thread/route.ts +27 -0
  29. package/templates-tailwind/next/src/app/api/vicket/tickets/route.ts +37 -0
  30. package/templates-tailwind/next/src/app/support/page.tsx +5 -0
  31. package/templates-tailwind/next/src/app/ticket/page.tsx +10 -0
  32. package/templates-tailwind/next/src/components/vicket/support-page.tsx +359 -0
  33. package/templates-tailwind/next/src/components/vicket/ticket-dialog.tsx +306 -0
  34. package/templates-tailwind/next/src/components/vicket/ticket-page.tsx +425 -0
  35. package/templates-tailwind/next/src/lib/vicket.ts +257 -0
  36. package/templates-tailwind/nuxt/app/components/VicketSupportPage.vue +317 -0
  37. package/templates-tailwind/nuxt/app/components/VicketTicketDialog.vue +444 -0
  38. package/templates-tailwind/nuxt/app/components/VicketTicketPage.vue +449 -0
  39. package/templates-tailwind/nuxt/app/composables/use-vicket.ts +249 -0
  40. package/templates-tailwind/nuxt/app/pages/support.vue +3 -0
  41. package/templates-tailwind/nuxt/app/pages/ticket.vue +3 -0
  42. package/templates-tailwind/nuxt/server/api/vicket/init.get.ts +22 -0
  43. package/templates-tailwind/nuxt/server/api/vicket/messages.post.ts +56 -0
  44. package/templates-tailwind/nuxt/server/api/vicket/thread.get.ts +26 -0
  45. package/templates-tailwind/nuxt/server/api/vicket/tickets.post.ts +53 -0
  46. package/templates-tailwind/sveltekit/src/lib/vicket/SupportPage.svelte +395 -0
  47. package/templates-tailwind/sveltekit/src/lib/vicket/TicketDialog.svelte +406 -0
  48. package/templates-tailwind/sveltekit/src/lib/vicket/TicketPage.svelte +465 -0
  49. package/templates-tailwind/sveltekit/src/lib/vicket/index.ts +257 -0
  50. package/templates-tailwind/sveltekit/src/routes/api/vicket/init/+server.ts +22 -0
  51. package/templates-tailwind/sveltekit/src/routes/api/vicket/messages/+server.ts +40 -0
  52. package/templates-tailwind/sveltekit/src/routes/api/vicket/thread/+server.ts +25 -0
  53. package/templates-tailwind/sveltekit/src/routes/api/vicket/tickets/+server.ts +37 -0
  54. package/templates-tailwind/sveltekit/src/routes/support/+page.svelte +5 -0
  55. package/templates-tailwind/sveltekit/src/routes/ticket/+page.svelte +5 -0
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @vicket/create-support
2
+
3
+ Scaffold white-label support pages into a client app:
4
+
5
+ - `/support`: create a ticket
6
+ - `/ticket?token=...`: read and reply to a ticket
7
+
8
+ No shadcn requirement and no WYSIWYG dependency.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ npx @vicket/create-support --framework next
14
+ ```
15
+
16
+ Without `--framework`, the CLI opens a guided selection menu.
17
+ Without `--styling`, the CLI defaults to `css` (or asks in interactive mode).
18
+
19
+ Supported frameworks:
20
+
21
+ - `next`
22
+ - `nuxt`
23
+ - `sveltekit` (or `svelte`)
24
+
25
+ Options:
26
+
27
+ - `--framework` / `-f`
28
+ - `--styling` / `-s` (`css` or `tailwind`)
29
+ - `--tailwind` (shortcut for `--styling tailwind`)
30
+ - `--dir` / `-d` output directory (default: current directory)
31
+ - `--yes` / `-y` overwrite files without prompt
32
+ - `--help` / `-h` show usage
33
+
34
+ Tailwind mode:
35
+
36
+ - Generates templates with Tailwind utility classes directly in `class`/`className`
37
+ - Generates a minimal `vicket.css` (Tailwind import + small state helpers)
38
+ - Requires Tailwind CSS to already be configured in the target app
39
+
40
+ Server-side proxy (Next.js, Nuxt, SvelteKit):
41
+
42
+ - API keys are kept server-side and never exposed to the browser
43
+ - Generated code includes an API route handler that proxies requests to the Vicket API
44
+ - Client components call the local proxy (`/api/vicket/...`) instead of the external API directly
45
+
46
+ ## Required env vars in generated app
47
+
48
+ - `next`: `VICKET_API_URL`, `VICKET_API_KEY` (server-side only, never exposed to client)
49
+ - `nuxt`: `VICKET_API_URL`, `VICKET_API_KEY` (server-side only, never exposed to client)
50
+ - `sveltekit`: `VICKET_API_URL`, `VICKET_API_KEY` (server-side only, never exposed to client)
51
+
52
+ Example URL value: `https://api.vicket.app/api/v1`
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { stdin } = require("node:process");
6
+
7
+ const PKG_VERSION = require(
8
+ path.resolve(__dirname, "..", "package.json"),
9
+ ).version;
10
+
11
+ const FRAMEWORK_ALIASES = {
12
+ next: "next",
13
+ nextjs: "next",
14
+ nuxt: "nuxt",
15
+ svelte: "sveltekit",
16
+ sveltekit: "sveltekit",
17
+ };
18
+
19
+ const FRAMEWORK_CHOICES = [
20
+ { value: "next", label: "Next.js (App Router)" },
21
+ { value: "nuxt", label: "Nuxt 4 (Vue)" },
22
+ { value: "sveltekit", label: "SvelteKit" },
23
+ ];
24
+
25
+ const DEFAULT_FRAMEWORK = "next";
26
+ const STYLING_ALIASES = {
27
+ css: "css",
28
+ tailwind: "tailwind",
29
+ tw: "tailwind",
30
+ };
31
+ const STYLING_CHOICES = [
32
+ { value: "css", label: "CSS file (default)" },
33
+ { value: "tailwind", label: "Tailwind CSS (inline utilities)" },
34
+ ];
35
+ const DEFAULT_STYLING = "css";
36
+ let clack = null;
37
+
38
+ async function loadClack() {
39
+ if (clack) return clack;
40
+ const prompts = await import("@clack/prompts");
41
+ clack = prompts;
42
+ return prompts;
43
+ }
44
+
45
+ function printHelp() {
46
+ console.log(`
47
+ Usage:
48
+ create-vicket-support [options]
49
+
50
+ Options:
51
+ -f, --framework <name> Framework: next | nuxt | sveltekit
52
+ -s, --styling <name> Styling: css | tailwind (default: css)
53
+ --tailwind Shortcut for --styling tailwind
54
+ -d, --dir <path> Output directory (default: current directory)
55
+ -y, --yes Overwrite existing files without prompt
56
+ -v, --version Show version
57
+ -h, --help Show help
58
+
59
+ Examples:
60
+ npx @vicket/create-support --framework next
61
+ npx @vicket/create-support --framework next --styling tailwind
62
+ npx @vicket/create-support --framework nuxt --dir ./my-app
63
+ npx @vicket/create-support -f sveltekit -d . -y --tailwind
64
+ `);
65
+ }
66
+
67
+ function parseArgs(argv) {
68
+ const options = {
69
+ framework: "",
70
+ styling: "",
71
+ dir: process.cwd(),
72
+ yes: false,
73
+ help: false,
74
+ version: false,
75
+ };
76
+
77
+ for (let i = 0; i < argv.length; i += 1) {
78
+ const arg = argv[i];
79
+ if (arg === "--version" || arg === "-v") {
80
+ options.version = true;
81
+ continue;
82
+ }
83
+ if (arg === "--help" || arg === "-h") {
84
+ options.help = true;
85
+ continue;
86
+ }
87
+ if (arg === "--framework" || arg === "-f") {
88
+ options.framework = argv[i + 1] || "";
89
+ i += 1;
90
+ continue;
91
+ }
92
+ if (arg.startsWith("--framework=")) {
93
+ options.framework = arg.split("=")[1] || "";
94
+ continue;
95
+ }
96
+ if (arg === "--styling" || arg === "-s") {
97
+ options.styling = argv[i + 1] || options.styling;
98
+ i += 1;
99
+ continue;
100
+ }
101
+ if (arg.startsWith("--styling=")) {
102
+ options.styling = arg.split("=")[1] || options.styling;
103
+ continue;
104
+ }
105
+ if (arg === "--tailwind") {
106
+ options.styling = "tailwind";
107
+ continue;
108
+ }
109
+ if (arg === "--dir" || arg === "-d") {
110
+ options.dir = argv[i + 1] || options.dir;
111
+ i += 1;
112
+ continue;
113
+ }
114
+ if (arg.startsWith("--dir=")) {
115
+ options.dir = arg.split("=")[1] || options.dir;
116
+ continue;
117
+ }
118
+ if (arg === "--yes" || arg === "-y") {
119
+ options.yes = true;
120
+ continue;
121
+ }
122
+ }
123
+
124
+ return options;
125
+ }
126
+
127
+ function normalizeFramework(input) {
128
+ return FRAMEWORK_ALIASES[(input || "").trim().toLowerCase()] || "";
129
+ }
130
+
131
+ function normalizeStyling(input) {
132
+ return STYLING_ALIASES[(input || "").trim().toLowerCase()] || "";
133
+ }
134
+
135
+ async function askFrameworkSelection() {
136
+ if (!stdin.isTTY) {
137
+ return "";
138
+ }
139
+
140
+ const p = await loadClack();
141
+ p.intro("Create Vicket Support");
142
+ const value = await p.select({
143
+ message: "Select a framework",
144
+ initialValue: DEFAULT_FRAMEWORK,
145
+ options: FRAMEWORK_CHOICES.map((choice) => ({
146
+ value: choice.value,
147
+ label: choice.label,
148
+ })),
149
+ });
150
+
151
+ if (p.isCancel(value)) {
152
+ p.cancel("Operation cancelled.");
153
+ return "";
154
+ }
155
+
156
+ return value;
157
+ }
158
+
159
+ async function askStylingSelection() {
160
+ if (!stdin.isTTY) {
161
+ return DEFAULT_STYLING;
162
+ }
163
+
164
+ const p = await loadClack();
165
+ const value = await p.select({
166
+ message: "Select a styling mode",
167
+ initialValue: DEFAULT_STYLING,
168
+ options: STYLING_CHOICES.map((choice) => ({
169
+ value: choice.value,
170
+ label: choice.label,
171
+ })),
172
+ });
173
+
174
+ if (p.isCancel(value)) {
175
+ p.cancel("Operation cancelled.");
176
+ return "";
177
+ }
178
+
179
+ return value;
180
+ }
181
+
182
+ function collectFiles(baseDir, currentDir = baseDir, acc = []) {
183
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
184
+ for (const entry of entries) {
185
+ const absolute = path.join(currentDir, entry.name);
186
+ if (entry.isDirectory()) {
187
+ collectFiles(baseDir, absolute, acc);
188
+ continue;
189
+ }
190
+
191
+ const relative = path.relative(baseDir, absolute);
192
+ acc.push(relative);
193
+ }
194
+ return acc;
195
+ }
196
+
197
+ function ensureDirectory(filePath) {
198
+ const dir = path.dirname(filePath);
199
+ fs.mkdirSync(dir, { recursive: true });
200
+ }
201
+
202
+ /**
203
+ * Detect whether a Next.js project uses the src/ directory layout.
204
+ * Returns true if src/app exists, false if app/ exists at root,
205
+ * or falls back to checking for a src/ directory.
206
+ */
207
+ function detectNextSrcLayout(targetRoot) {
208
+ if (fs.existsSync(path.join(targetRoot, "src", "app"))) {
209
+ return true;
210
+ }
211
+ if (fs.existsSync(path.join(targetRoot, "app"))) {
212
+ return false;
213
+ }
214
+ if (fs.existsSync(path.join(targetRoot, "src"))) {
215
+ return true;
216
+ }
217
+ return false;
218
+ }
219
+
220
+ async function confirmOverwrite(existingFiles) {
221
+ if (!stdin.isTTY) {
222
+ return false;
223
+ }
224
+
225
+ const p = await loadClack();
226
+ p.log.warn("The following files already exist:");
227
+ for (const file of existingFiles) {
228
+ p.log.message(`- ${file}`);
229
+ }
230
+
231
+ const value = await p.confirm({
232
+ message: "Overwrite these files?",
233
+ initialValue: false,
234
+ });
235
+
236
+ if (p.isCancel(value)) {
237
+ p.cancel("Operation cancelled.");
238
+ return false;
239
+ }
240
+
241
+ return Boolean(value);
242
+ }
243
+
244
+ async function printPostInstall(framework, styling) {
245
+ const envLinesByFramework = {
246
+ next: [
247
+ "- VICKET_API_URL (https://api.vicket.app/api/v1) — server-side only",
248
+ "- VICKET_API_KEY (website API key) — server-side only, NEVER expose to client",
249
+ ],
250
+ nuxt: [
251
+ "- VICKET_API_URL (https://api.vicket.app/api/v1) — server-side only",
252
+ "- VICKET_API_KEY (website API key) — server-side only, NEVER expose to client",
253
+ ],
254
+ sveltekit: [
255
+ "- VICKET_API_URL (https://api.vicket.app/api/v1) — server-side only",
256
+ "- VICKET_API_KEY (website API key) — server-side only, NEVER expose to client",
257
+ ],
258
+ };
259
+ const envLines = envLinesByFramework[framework] || envLinesByFramework.next;
260
+
261
+ if (stdin.isTTY) {
262
+ const p = await loadClack();
263
+ p.outro("Scaffold complete.");
264
+ p.log.info("Set these environment variables in the target app:");
265
+ for (const line of envLines) {
266
+ p.log.message(line);
267
+ }
268
+
269
+ if (framework === "nuxt") {
270
+ p.log.info(
271
+ "Nuxt note: if you prefer global styles, add ~/assets/css/vicket.css to nuxt.config.ts > css. Files are in the app/ directory (Nuxt 4 convention).",
272
+ );
273
+ }
274
+ if (styling === "tailwind") {
275
+ p.log.info(
276
+ "Tailwind mode: ensure Tailwind CSS is configured in the target app.",
277
+ );
278
+ }
279
+ return;
280
+ }
281
+
282
+ console.log("\nScaffold complete.");
283
+ console.log("Set these environment variables in the target app:");
284
+ for (const line of envLines) {
285
+ console.log(line);
286
+ }
287
+ if (styling === "tailwind") {
288
+ console.log(
289
+ "Tailwind mode: ensure Tailwind CSS is configured in the target app.",
290
+ );
291
+ }
292
+ }
293
+
294
+ async function main() {
295
+ const options = parseArgs(process.argv.slice(2));
296
+ if (options.version) {
297
+ console.log(`@vicket/create-support v${PKG_VERSION}`);
298
+ process.exit(0);
299
+ }
300
+ if (options.help) {
301
+ printHelp();
302
+ process.exit(0);
303
+ }
304
+
305
+ let framework = normalizeFramework(options.framework);
306
+ const requestedStyling = (options.styling || "").trim();
307
+ if (requestedStyling && !normalizeStyling(requestedStyling)) {
308
+ console.error("Unsupported styling. Use --styling css|tailwind");
309
+ printHelp();
310
+ process.exit(1);
311
+ }
312
+
313
+ let styling = normalizeStyling(options.styling) || DEFAULT_STYLING;
314
+
315
+ if (!framework) {
316
+ framework = await askFrameworkSelection();
317
+ }
318
+
319
+ if (!framework) {
320
+ console.error(
321
+ "Missing or unsupported framework. Use --framework next|nuxt|sveltekit",
322
+ );
323
+ printHelp();
324
+ process.exit(1);
325
+ }
326
+
327
+ if (
328
+ !normalizeStyling(options.styling) &&
329
+ stdin.isTTY &&
330
+ !process.argv.includes("--tailwind")
331
+ ) {
332
+ const selectedStyling = await askStylingSelection();
333
+ if (!selectedStyling) {
334
+ console.log("Aborted. No files were written.");
335
+ process.exit(0);
336
+ }
337
+ styling = selectedStyling;
338
+ }
339
+
340
+ const packageRoot = path.resolve(__dirname, "..");
341
+ const templateDir =
342
+ styling === "tailwind" ? "templates-tailwind" : "templates";
343
+ const templateRoot = path.join(packageRoot, templateDir, framework);
344
+
345
+ if (!fs.existsSync(templateRoot)) {
346
+ console.error(`Template not found: ${templateDir}/${framework}`);
347
+ process.exit(1);
348
+ }
349
+
350
+ const targetRoot = path.resolve(options.dir);
351
+ const files = collectFiles(templateRoot);
352
+
353
+ const stripSrc =
354
+ framework === "next" && !detectNextSrcLayout(targetRoot);
355
+ const srcPrefix = "src" + path.sep;
356
+
357
+ function toDestRelative(relativePath) {
358
+ if (stripSrc && relativePath.startsWith(srcPrefix)) {
359
+ return relativePath.slice(srcPrefix.length);
360
+ }
361
+ return relativePath;
362
+ }
363
+
364
+ const existing = files
365
+ .map((f) => toDestRelative(f))
366
+ .filter((destPath) => fs.existsSync(path.join(targetRoot, destPath)));
367
+
368
+ if (existing.length > 0 && !options.yes) {
369
+ const shouldOverwrite = await confirmOverwrite(existing);
370
+ if (!shouldOverwrite) {
371
+ console.log("Aborted. No files were written.");
372
+ process.exit(0);
373
+ }
374
+ }
375
+
376
+ for (const relativePath of files) {
377
+ const from = path.join(templateRoot, relativePath);
378
+ const to = path.join(targetRoot, toDestRelative(relativePath));
379
+ ensureDirectory(to);
380
+ fs.copyFileSync(from, to);
381
+ }
382
+
383
+ await printPostInstall(framework, styling);
384
+ }
385
+
386
+ main().catch((error) => {
387
+ console.error(error);
388
+ process.exit(1);
389
+ });
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@vicket/create-support",
3
+ "version": "1.1.1",
4
+ "bin": {
5
+ "create-support": "./bin/create-vicket-support.js"
6
+ },
7
+ "files": [
8
+ "bin",
9
+ "templates",
10
+ "templates-tailwind",
11
+ "README.md"
12
+ ],
13
+ "type": "commonjs",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@clack/prompts": "^0.10.1"
17
+ }
18
+ }
@@ -0,0 +1,59 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+
3
+ const VICKET_API_URL = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
4
+ const VICKET_API_KEY = process.env.VICKET_API_KEY || "";
5
+
6
+ async function proxy(
7
+ req: NextRequest,
8
+ { params }: { params: Promise<{ path: string[] }> },
9
+ ) {
10
+ if (!VICKET_API_URL || !VICKET_API_KEY) {
11
+ return NextResponse.json(
12
+ { error: "Missing VICKET_API_URL or VICKET_API_KEY server environment variables." },
13
+ { status: 500 },
14
+ );
15
+ }
16
+
17
+ const { path } = await params;
18
+ const subpath = path.join("/");
19
+ const url = new URL(`${VICKET_API_URL}/public/support/${subpath}`);
20
+
21
+ // Forward query params
22
+ req.nextUrl.searchParams.forEach((value, key) => {
23
+ url.searchParams.set(key, value);
24
+ });
25
+
26
+ const headers: HeadersInit = { "X-Api-Key": VICKET_API_KEY };
27
+ const contentType = req.headers.get("content-type") || "";
28
+
29
+ let body: BodyInit | null = null;
30
+
31
+ if (req.method !== "GET" && req.method !== "HEAD") {
32
+ if (contentType.includes("multipart/form-data")) {
33
+ // Stream FormData as-is; let fetch set the boundary
34
+ body = await req.blob();
35
+ headers["Content-Type"] = contentType;
36
+ } else {
37
+ body = await req.text();
38
+ headers["Content-Type"] = contentType || "application/json";
39
+ }
40
+ }
41
+
42
+ const upstream = await fetch(url.toString(), {
43
+ method: req.method,
44
+ headers,
45
+ body,
46
+ });
47
+
48
+ const responseBody = await upstream.arrayBuffer();
49
+
50
+ return new NextResponse(responseBody, {
51
+ status: upstream.status,
52
+ headers: {
53
+ "Content-Type": upstream.headers.get("Content-Type") || "application/json",
54
+ },
55
+ });
56
+ }
57
+
58
+ export const GET = proxy;
59
+ export const POST = proxy;