@alliance-droid/status-feedback-system 0.1.1 → 0.2.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.
package/dist/api.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * const { systems, incidents } = await client.getStatus();
13
13
  */
14
- import type { System, Incident, StatusHistoryEntry, Feedback } from './types.js';
14
+ import type { System, Incident, StatusHistoryEntry, Feedback, FeedbackResponse } from './types.js';
15
15
  export interface StatusClientConfig {
16
16
  /** Base URL of the status-feedback-admin app */
17
17
  apiUrl: string;
@@ -30,6 +30,26 @@ export interface IncidentData {
30
30
  export interface UptimeData {
31
31
  history: Record<string, StatusHistoryEntry[]>;
32
32
  }
33
+ export interface BoardCategory {
34
+ name: string;
35
+ count: number;
36
+ }
37
+ export interface BoardItem {
38
+ id: string;
39
+ title: string | null;
40
+ category: string | null;
41
+ message: string;
42
+ status: string;
43
+ votes: number;
44
+ adminResponse: string | null;
45
+ responseCount: number;
46
+ createdAt: number;
47
+ }
48
+ export interface BoardData {
49
+ items: BoardItem[];
50
+ total: number;
51
+ categories: BoardCategory[];
52
+ }
33
53
  export interface StatusClient {
34
54
  /** Get current system statuses */
35
55
  getStatus(): Promise<StatusData>;
@@ -45,11 +65,28 @@ export interface StatusClient {
45
65
  /** Submit user feedback */
46
66
  submitFeedback(data: {
47
67
  message: string;
68
+ title?: string;
48
69
  category?: string;
49
70
  email?: string;
50
71
  pageUrl?: string;
51
72
  metadata?: Record<string, unknown>;
52
73
  }): Promise<Feedback>;
74
+ /** Get public feedback board */
75
+ getBoard(options?: {
76
+ category?: string;
77
+ status?: 'open' | 'resolved' | 'all';
78
+ sort?: 'votes' | 'newest';
79
+ limit?: number;
80
+ offset?: number;
81
+ }): Promise<BoardData>;
82
+ /** Vote on a feedback item */
83
+ vote(feedbackId: string, direction?: 'up' | 'down'): Promise<{
84
+ votes: number;
85
+ }>;
86
+ /** Get responses for a feedback item */
87
+ getResponses(feedbackId: string): Promise<{
88
+ responses: FeedbackResponse[];
89
+ }>;
53
90
  }
54
91
  /**
55
92
  * Create a status API client instance
package/dist/api.js CHANGED
@@ -72,5 +72,29 @@ export function createStatusClient(config) {
72
72
  body: JSON.stringify(data),
73
73
  });
74
74
  },
75
+ async getBoard(options) {
76
+ const params = new URLSearchParams();
77
+ if (options?.category)
78
+ params.set('category', options.category);
79
+ if (options?.status)
80
+ params.set('status', options.status);
81
+ if (options?.sort)
82
+ params.set('sort', options.sort);
83
+ if (options?.limit)
84
+ params.set('limit', String(options.limit));
85
+ if (options?.offset)
86
+ params.set('offset', String(options.offset));
87
+ const qs = params.toString();
88
+ return request(`/api/board${qs ? `?${qs}` : ''}`);
89
+ },
90
+ async vote(feedbackId, direction = 'up') {
91
+ return request('/api/board/vote', {
92
+ method: 'POST',
93
+ body: JSON.stringify({ feedbackId, direction }),
94
+ });
95
+ },
96
+ async getResponses(feedbackId) {
97
+ return request(`/api/board/${feedbackId}/responses`);
98
+ },
75
99
  };
76
100
  }
@@ -0,0 +1,417 @@
1
+ <script lang="ts">
2
+ import { createStatusClient, type BoardItem, type BoardCategory } from '../api.js';
3
+ import {
4
+ Card,
5
+ Button,
6
+ Badge,
7
+ Select,
8
+ Input,
9
+ Textarea,
10
+ Spinner,
11
+ Divider,
12
+ IconButton,
13
+ } from '@alliance-droid/svelte-component-library';
14
+
15
+ interface Props {
16
+ apiUrl: string;
17
+ apiKey: string;
18
+ /** Category labels and icons — order determines sidebar order */
19
+ categoryConfig?: Record<string, { label?: string; icon?: string; description?: string }>;
20
+ /** Title shown at the top */
21
+ title?: string;
22
+ /** Show the submit form inline */
23
+ showSubmit?: boolean;
24
+ /** Available categories for the submit form */
25
+ submitCategories?: string[];
26
+ }
27
+
28
+ let {
29
+ apiUrl,
30
+ apiKey,
31
+ categoryConfig = {},
32
+ title = 'Feedback Board',
33
+ showSubmit = true,
34
+ submitCategories = [],
35
+ }: Props = $props();
36
+
37
+ let client = $derived(createStatusClient({ apiUrl, apiKey }));
38
+ let items = $state<BoardItem[]>([]);
39
+ let categories = $state<BoardCategory[]>([]);
40
+ let total = $state(0);
41
+ let loading = $state(true);
42
+ let error = $state('');
43
+
44
+ let activeCategory = $state<string | null>(null);
45
+ let statusFilter = $state<string>('open');
46
+ let sortBy = $state<string>('votes');
47
+
48
+ let expandedId = $state<string | null>(null);
49
+ let responses = $state<Record<string, { message: string; author: string | null; createdAt: number }[]>>({});
50
+ let loadingResponses = $state<Record<string, boolean>>({});
51
+
52
+ let showForm = $state(false);
53
+ let submitTitle = $state('');
54
+ let submitMessage = $state('');
55
+ let submitCategory = $state('');
56
+ let submitEmail = $state('');
57
+ let submitting = $state(false);
58
+ let submitSuccess = $state(false);
59
+
60
+ let votedItems = $state<Record<string, 'up' | 'down'>>({});
61
+
62
+ async function load() {
63
+ loading = true;
64
+ error = '';
65
+ try {
66
+ const data = await client.getBoard({
67
+ category: activeCategory || undefined,
68
+ status: statusFilter as 'open' | 'resolved' | 'all',
69
+ sort: sortBy as 'votes' | 'newest',
70
+ });
71
+ items = data.items;
72
+ total = data.total;
73
+ categories = data.categories;
74
+ } catch (e) {
75
+ error = e instanceof Error ? e.message : 'Failed to load';
76
+ } finally {
77
+ loading = false;
78
+ }
79
+ }
80
+
81
+ $effect(() => {
82
+ void activeCategory;
83
+ void statusFilter;
84
+ void sortBy;
85
+ load();
86
+ });
87
+
88
+ async function handleVote(itemId: string, direction: 'up' | 'down') {
89
+ const prev = votedItems[itemId];
90
+ try {
91
+ const result = await client.vote(itemId, direction);
92
+ const item = items.find((i) => i.id === itemId);
93
+ if (item) item.votes = result.votes;
94
+ if (prev === direction) {
95
+ delete votedItems[itemId];
96
+ votedItems = { ...votedItems };
97
+ } else {
98
+ votedItems = { ...votedItems, [itemId]: direction };
99
+ }
100
+ } catch {
101
+ // silently fail
102
+ }
103
+ }
104
+
105
+ async function toggleExpand(itemId: string) {
106
+ if (expandedId === itemId) {
107
+ expandedId = null;
108
+ return;
109
+ }
110
+ expandedId = itemId;
111
+ if (!responses[itemId]) {
112
+ loadingResponses = { ...loadingResponses, [itemId]: true };
113
+ try {
114
+ const data = await client.getResponses(itemId);
115
+ responses = { ...responses, [itemId]: data.responses };
116
+ } catch {
117
+ responses = { ...responses, [itemId]: [] };
118
+ } finally {
119
+ loadingResponses = { ...loadingResponses, [itemId]: false };
120
+ }
121
+ }
122
+ }
123
+
124
+ async function handleSubmit() {
125
+ if (!submitMessage.trim()) return;
126
+ submitting = true;
127
+ try {
128
+ await client.submitFeedback({
129
+ title: submitTitle.trim() || undefined,
130
+ message: submitMessage.trim(),
131
+ category: submitCategory || undefined,
132
+ email: submitEmail.trim() || undefined,
133
+ pageUrl: window.location.href,
134
+ });
135
+ submitSuccess = true;
136
+ submitTitle = '';
137
+ submitMessage = '';
138
+ submitCategory = '';
139
+ submitEmail = '';
140
+ setTimeout(() => {
141
+ submitSuccess = false;
142
+ showForm = false;
143
+ }, 2000);
144
+ await load();
145
+ } catch {
146
+ // ignore
147
+ } finally {
148
+ submitting = false;
149
+ }
150
+ }
151
+
152
+ function getCategoryIcon(name: string): string {
153
+ return categoryConfig[name]?.icon || 'fa-solid fa-circle';
154
+ }
155
+
156
+ function getCategoryLabel(name: string): string {
157
+ return categoryConfig[name]?.label || name;
158
+ }
159
+
160
+ function getCategoryDescription(name: string): string {
161
+ return categoryConfig[name]?.description || '';
162
+ }
163
+
164
+ function timeAgo(timestamp: number): string {
165
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
166
+ if (seconds < 60) return 'just now';
167
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
168
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
169
+ const days = Math.floor(seconds / 86400);
170
+ if (days < 30) return `${days}d ago`;
171
+ return new Date(timestamp).toLocaleDateString();
172
+ }
173
+
174
+ function getStatusBadgeVariant(status: string): 'success' | 'neutral' | 'info' {
175
+ if (status === 'new' || status === 'reviewed') return 'success';
176
+ if (status === 'resolved') return 'neutral';
177
+ return 'info';
178
+ }
179
+
180
+ const statusOptions = [
181
+ { value: 'open', label: 'Open' },
182
+ { value: 'resolved', label: 'Resolved' },
183
+ { value: 'all', label: 'All' },
184
+ ];
185
+
186
+ const sortOptions = [
187
+ { value: 'votes', label: 'Most Votes' },
188
+ { value: 'newest', label: 'Newest' },
189
+ ];
190
+
191
+ let submitCategoryOptions = $derived(
192
+ submitCategories.map((c) => ({ value: c, label: getCategoryLabel(c) }))
193
+ );
194
+ </script>
195
+
196
+ <div class="flex gap-6 min-h-[400px]">
197
+ <!-- Sidebar -->
198
+ <aside class="w-56 flex-shrink-0 flex flex-col gap-1">
199
+ <button
200
+ class="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left w-full transition-colors
201
+ {activeCategory === null ? 'bg-surface-secondary text-foreground font-semibold' : 'text-foreground-secondary hover:bg-surface-hover hover:text-foreground'}"
202
+ onclick={() => (activeCategory = null)}
203
+ >
204
+ <i class="fa-solid fa-layer-group"></i>
205
+ <span>All</span>
206
+ <span class="ml-auto bg-surface-hover px-2 py-0.5 rounded-full text-xs">{total}</span>
207
+ </button>
208
+
209
+ {#each categories as cat}
210
+ <button
211
+ class="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left w-full transition-colors
212
+ {activeCategory === cat.name ? 'bg-surface-secondary text-foreground font-semibold' : 'text-foreground-secondary hover:bg-surface-hover hover:text-foreground'}"
213
+ onclick={() => (activeCategory = activeCategory === cat.name ? null : cat.name)}
214
+ >
215
+ <i class={getCategoryIcon(cat.name)}></i>
216
+ <span>{getCategoryLabel(cat.name)}</span>
217
+ <span class="ml-auto bg-surface-hover px-2 py-0.5 rounded-full text-xs">{cat.count}</span>
218
+ </button>
219
+ {/each}
220
+
221
+ <Divider class="my-2" />
222
+
223
+ <div class="px-3 space-y-3">
224
+ <Select
225
+ label="Status"
226
+ options={statusOptions}
227
+ bind:value={statusFilter}
228
+ />
229
+ <Select
230
+ label="Sort By"
231
+ options={sortOptions}
232
+ bind:value={sortBy}
233
+ />
234
+ </div>
235
+ </aside>
236
+
237
+ <!-- Main -->
238
+ <main class="flex-1 min-w-0">
239
+ <div class="flex items-start justify-between mb-4">
240
+ <div>
241
+ <h2 class="text-xl font-bold flex items-center gap-2">
242
+ {#if activeCategory}
243
+ <i class={getCategoryIcon(activeCategory)}></i>
244
+ {getCategoryLabel(activeCategory)}
245
+ {:else}
246
+ {title}
247
+ {/if}
248
+ </h2>
249
+ {#if activeCategory && getCategoryDescription(activeCategory)}
250
+ <p class="text-sm text-foreground-secondary mt-1">{getCategoryDescription(activeCategory)}</p>
251
+ {/if}
252
+ </div>
253
+ {#if showSubmit}
254
+ <Button variant="primary" onclick={() => (showForm = !showForm)}>
255
+ <i class="fa-solid fa-plus mr-1.5"></i> Submit
256
+ </Button>
257
+ {/if}
258
+ </div>
259
+
260
+ <!-- Submit form -->
261
+ {#if showForm}
262
+ <Card variant="outlined" padding="md" class="mb-4">
263
+ {#if submitSuccess}
264
+ <div class="text-center py-2 text-green-400 font-semibold">
265
+ <i class="fa-solid fa-check-circle mr-1"></i> Submitted! It will appear once reviewed.
266
+ </div>
267
+ {:else}
268
+ <div class="space-y-3">
269
+ <Input
270
+ bind:value={submitTitle}
271
+ placeholder="Title (optional)"
272
+ />
273
+ <Textarea
274
+ bind:value={submitMessage}
275
+ placeholder="Describe your feedback..."
276
+ rows={3}
277
+ />
278
+ <div class="flex gap-2 items-center flex-wrap">
279
+ {#if submitCategoryOptions.length > 0}
280
+ <div class="min-w-[140px]">
281
+ <Select
282
+ options={[{ value: '', label: 'Category...' }, ...submitCategoryOptions]}
283
+ bind:value={submitCategory}
284
+ />
285
+ </div>
286
+ {/if}
287
+ <div class="flex-1">
288
+ <Input
289
+ type="email"
290
+ bind:value={submitEmail}
291
+ placeholder="Email (optional)"
292
+ />
293
+ </div>
294
+ <Button
295
+ variant="primary"
296
+ disabled={submitting || !submitMessage.trim()}
297
+ loading={submitting}
298
+ onclick={handleSubmit}
299
+ >
300
+ Submit
301
+ </Button>
302
+ </div>
303
+ </div>
304
+ {/if}
305
+ </Card>
306
+ {/if}
307
+
308
+ <!-- Items -->
309
+ {#if loading}
310
+ <div class="text-center py-12 text-foreground-tertiary">
311
+ <Spinner />
312
+ <p class="mt-2">Loading...</p>
313
+ </div>
314
+ {:else if error}
315
+ <div class="text-center py-12 text-foreground-tertiary">
316
+ <i class="fa-solid fa-exclamation-triangle text-2xl"></i>
317
+ <p class="mt-2">{error}</p>
318
+ </div>
319
+ {:else if items.length === 0}
320
+ <div class="text-center py-12 text-foreground-tertiary">
321
+ <i class="fa-solid fa-inbox text-3xl"></i>
322
+ <p class="mt-2">No feedback yet</p>
323
+ <p class="text-sm">Be the first to submit!</p>
324
+ </div>
325
+ {:else}
326
+ <div class="flex flex-col gap-2">
327
+ {#each items as item (item.id)}
328
+ <Card variant="outlined" padding="none" hover>
329
+ <div class="flex gap-3 p-4">
330
+ <!-- Vote column -->
331
+ <div class="flex flex-col items-center gap-0.5 min-w-[2.5rem]">
332
+ <IconButton
333
+ size="sm"
334
+ variant={votedItems[item.id] === 'up' ? 'primary' : 'neutral'}
335
+ onclick={() => handleVote(item.id, 'up')}
336
+ aria-label="Upvote"
337
+ >
338
+ <i class="fa-solid fa-chevron-up text-xs"></i>
339
+ </IconButton>
340
+ <span class="text-sm font-bold">{item.votes}</span>
341
+ <IconButton
342
+ size="sm"
343
+ variant={votedItems[item.id] === 'down' ? 'primary' : 'neutral'}
344
+ onclick={() => handleVote(item.id, 'down')}
345
+ aria-label="Downvote"
346
+ >
347
+ <i class="fa-solid fa-chevron-down text-xs"></i>
348
+ </IconButton>
349
+ </div>
350
+
351
+ <!-- Content -->
352
+ <div class="flex-1 min-w-0">
353
+ <div class="flex items-center gap-2 flex-wrap">
354
+ {#if item.category}
355
+ <i class="{getCategoryIcon(item.category)} text-sm text-foreground-secondary"></i>
356
+ {/if}
357
+ <h3 class="text-[0.9375rem] font-semibold flex-1">
358
+ {item.title || item.message.slice(0, 80)}
359
+ </h3>
360
+ <Badge variant={getStatusBadgeVariant(item.status)} size="sm">
361
+ {item.status === 'new' ? 'Open' : item.status.charAt(0).toUpperCase() + item.status.slice(1)}
362
+ </Badge>
363
+ </div>
364
+
365
+ {#if item.title}
366
+ <p class="text-sm text-foreground-secondary mt-1.5 leading-relaxed">{item.message}</p>
367
+ {/if}
368
+
369
+ <div class="flex items-center gap-3 mt-2 text-[0.8125rem] text-foreground-tertiary">
370
+ <span>{timeAgo(item.createdAt)}</span>
371
+ {#if item.responseCount > 0}
372
+ <button
373
+ class="flex items-center gap-1 hover:text-foreground transition-colors bg-transparent border-none cursor-pointer text-[0.8125rem] text-foreground-tertiary p-0"
374
+ onclick={() => toggleExpand(item.id)}
375
+ >
376
+ <i class="fa-solid fa-comment"></i>
377
+ {item.responseCount} {item.responseCount === 1 ? 'response' : 'responses'}
378
+ </button>
379
+ {/if}
380
+ {#if item.adminResponse}
381
+ <span class="text-xs text-primary">
382
+ <i class="fa-solid fa-shield"></i> Official response
383
+ </span>
384
+ {/if}
385
+ </div>
386
+
387
+ <!-- Expanded responses -->
388
+ {#if expandedId === item.id}
389
+ <div class="mt-3 pt-3 border-t border-border space-y-2">
390
+ {#if loadingResponses[item.id]}
391
+ <p class="text-sm text-foreground-tertiary"><Spinner /> Loading...</p>
392
+ {:else if responses[item.id]?.length}
393
+ {#each responses[item.id] as resp}
394
+ <div class="p-3 rounded-lg bg-surface">
395
+ <div class="flex items-center justify-between mb-1">
396
+ <span class="text-[0.8125rem] font-semibold text-primary">
397
+ <i class="fa-solid fa-shield mr-1"></i>
398
+ {resp.author || 'Team'}
399
+ </span>
400
+ <span class="text-xs text-foreground-tertiary">{timeAgo(resp.createdAt)}</span>
401
+ </div>
402
+ <p class="text-sm text-foreground-secondary leading-relaxed m-0">{resp.message}</p>
403
+ </div>
404
+ {/each}
405
+ {:else}
406
+ <p class="text-sm text-foreground-tertiary">No responses yet.</p>
407
+ {/if}
408
+ </div>
409
+ {/if}
410
+ </div>
411
+ </div>
412
+ </Card>
413
+ {/each}
414
+ </div>
415
+ {/if}
416
+ </main>
417
+ </div>
@@ -0,0 +1,19 @@
1
+ interface Props {
2
+ apiUrl: string;
3
+ apiKey: string;
4
+ /** Category labels and icons — order determines sidebar order */
5
+ categoryConfig?: Record<string, {
6
+ label?: string;
7
+ icon?: string;
8
+ description?: string;
9
+ }>;
10
+ /** Title shown at the top */
11
+ title?: string;
12
+ /** Show the submit form inline */
13
+ showSubmit?: boolean;
14
+ /** Available categories for the submit form */
15
+ submitCategories?: string[];
16
+ }
17
+ declare const FeedbackBoard: import("svelte").Component<Props, {}, "">;
18
+ type FeedbackBoard = ReturnType<typeof FeedbackBoard>;
19
+ export default FeedbackBoard;
package/dist/index.d.ts CHANGED
@@ -7,12 +7,13 @@
7
7
  * Usage:
8
8
  * import { StatusPage, FeedbackButton, createStatusClient } from '@alliance-droid/status-feedback-system';
9
9
  */
10
- export type { Project, System, Incident, IncidentUpdate, Feedback, StatusHistoryEntry, SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus, ApiResponse, } from './types.js';
10
+ export type { Project, System, Incident, IncidentUpdate, Feedback, FeedbackResponse, FeedbackVisibility, StatusHistoryEntry, SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus, ApiResponse, } from './types.js';
11
11
  export { createStatusClient } from './api.js';
12
- export type { StatusClient, StatusClientConfig, StatusData, IncidentData, UptimeData } from './api.js';
12
+ export type { StatusClient, StatusClientConfig, StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory } from './api.js';
13
13
  export { default as StatusPage } from './components/StatusPage.svelte';
14
14
  export { default as StatusBanner } from './components/StatusBanner.svelte';
15
15
  export { default as FeedbackForm } from './components/FeedbackForm.svelte';
16
+ export { default as FeedbackBoard } from './components/FeedbackBoard.svelte';
16
17
  export { default as FeedbackButton } from './components/FeedbackButton.svelte';
17
18
  export { default as StatusIndicator } from './components/shared/StatusIndicator.svelte';
18
19
  export { default as UptimeBar } from './components/shared/UptimeBar.svelte';
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export { createStatusClient } from './api.js';
13
13
  export { default as StatusPage } from './components/StatusPage.svelte';
14
14
  export { default as StatusBanner } from './components/StatusBanner.svelte';
15
15
  export { default as FeedbackForm } from './components/FeedbackForm.svelte';
16
+ export { default as FeedbackBoard } from './components/FeedbackBoard.svelte';
16
17
  export { default as FeedbackButton } from './components/FeedbackButton.svelte';
17
18
  // ── Shared Components ──
18
19
  export { default as StatusIndicator } from './components/shared/StatusIndicator.svelte';
package/dist/types.d.ts CHANGED
@@ -42,9 +42,18 @@ export interface IncidentUpdate {
42
42
  message: string;
43
43
  createdAt: number;
44
44
  }
45
+ export type FeedbackVisibility = 'public' | 'private';
46
+ export interface FeedbackResponse {
47
+ id: string;
48
+ feedbackId: string;
49
+ message: string;
50
+ author: string | null;
51
+ createdAt: number;
52
+ }
45
53
  export interface Feedback {
46
54
  id: string;
47
55
  projectId: string;
56
+ title: string | null;
48
57
  category: string | null;
49
58
  message: string;
50
59
  email: string | null;
@@ -52,6 +61,10 @@ export interface Feedback {
52
61
  pageUrl: string | null;
53
62
  metadata: Record<string, unknown> | null;
54
63
  status: FeedbackStatus;
64
+ visibility: FeedbackVisibility;
65
+ votes: number;
66
+ adminResponse: string | null;
67
+ responses?: FeedbackResponse[];
55
68
  createdAt: number;
56
69
  }
57
70
  export interface StatusHistoryEntry {
package/package.json CHANGED
@@ -1,60 +1,62 @@
1
1
  {
2
- "name": "@alliance-droid/status-feedback-system",
3
- "version": "0.1.1",
4
- "type": "module",
5
- "description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
6
- "svelte": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "svelte": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "dist",
16
- "!dist/**/*.test.*"
17
- ],
18
- "scripts": {
19
- "dev": "vite dev",
20
- "build": "vite build && npm run package",
21
- "package": "svelte-kit sync && svelte-package -o dist",
22
- "preview": "vite preview",
23
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
24
- "test": "vitest run",
25
- "test:watch": "vitest",
26
- "prepublishOnly": "npm run package"
27
- },
28
- "peerDependencies": {
29
- "svelte": "^5.0.0"
30
- },
31
- "dependencies": {
32
- "@alliance-droid/svelte-component-library": "^0.1.27"
33
- },
34
- "devDependencies": {
35
- "@sveltejs/adapter-auto": "^7.0.0",
36
- "@sveltejs/kit": "^2.50.0",
37
- "@sveltejs/package": "^2.3.0",
38
- "@sveltejs/vite-plugin-svelte": "^6.0.0",
39
- "@tailwindcss/vite": "^4.1.0",
40
- "publint": "^0.3.0",
41
- "svelte": "^5.48.0",
42
- "svelte-check": "^4.3.0",
43
- "tailwindcss": "^4.1.0",
44
- "typescript": "^5.9.0",
45
- "vite": "^7.3.0",
46
- "vitest": "^4.0.0"
47
- },
48
- "keywords": [
49
- "svelte",
50
- "sveltekit",
51
- "status-page",
52
- "feedback",
53
- "uptime"
54
- ],
55
- "author": "Alliance Technical Group",
56
- "license": "UNLICENSED",
57
- "publishConfig": {
58
- "access": "restricted"
59
- }
2
+ "name": "@alliance-droid/status-feedback-system",
3
+ "version": "0.2.1",
4
+ "type": "module",
5
+ "description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
6
+ "svelte": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "svelte": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "!dist/**/*.test.*"
17
+ ],
18
+ "scripts": {
19
+ "dev": "vite dev",
20
+ "build": "vite build && npm run package",
21
+ "package": "svelte-kit sync && svelte-package -o dist",
22
+ "preview": "vite preview",
23
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "prepublishOnly": "npm run package"
27
+ },
28
+ "peerDependencies": {
29
+ "@alliance-droid/svelte-component-library": ">=0.1.0",
30
+ "svelte": "^5.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@alliance-droid/svelte-component-library": "^0.1.88",
34
+ "@sveltejs/adapter-auto": "^7.0.0",
35
+ "@sveltejs/kit": "^2.50.0",
36
+ "@sveltejs/package": "^2.3.0",
37
+ "@sveltejs/vite-plugin-svelte": "^6.0.0",
38
+ "@tailwindcss/vite": "^4.1.0",
39
+ "publint": "^0.3.0",
40
+ "svelte": "^5.48.0",
41
+ "svelte-check": "^4.3.0",
42
+ "tailwindcss": "^4.1.0",
43
+ "typescript": "^5.9.0",
44
+ "vite": "^7.3.0",
45
+ "vitest": "^4.0.0"
46
+ },
47
+ "keywords": [
48
+ "svelte",
49
+ "sveltekit",
50
+ "status-page",
51
+ "feedback",
52
+ "uptime"
53
+ ],
54
+ "author": "Alliance Technical Group",
55
+ "license": "UNLICENSED",
56
+ "publishConfig": {
57
+ "access": "restricted"
58
+ },
59
+ "dependencies": {
60
+ "@alliance-droid/svelte-component-library": ">=0.1.0"
61
+ }
60
62
  }