@alliance-droid/status-feedback-system 2.3.2 → 3.0.0

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.
@@ -1,155 +1,155 @@
1
- <script lang="ts">
2
- import { untrack } from 'svelte';
3
- import { createStatusClient, type StatusClient } from '../api.js';
4
-
5
- interface Props {
6
- apiKey: string;
7
- apiUrl: string;
8
- categories?: string[];
9
- userEmail?: string;
10
- onSuccess?: () => void;
11
- onError?: (error: string) => void;
12
- }
13
-
14
- let {
15
- apiKey,
16
- apiUrl,
17
- categories = ['Bug', 'Feature Request', 'General'],
18
- userEmail,
19
- onSuccess,
20
- onError,
21
- }: Props = $props();
22
-
23
- let client: StatusClient | null = $state(null);
24
- let lastApiUrl = '';
25
- let lastApiKey = '';
26
-
27
- function getClient(): StatusClient {
28
- if (!client || lastApiUrl !== apiUrl || lastApiKey !== apiKey) {
29
- client = createStatusClient({ apiUrl, apiKey });
30
- lastApiUrl = apiUrl;
31
- lastApiKey = apiKey;
32
- }
33
- return client;
34
- }
35
-
36
- let category = $state(untrack(() => categories[0] || ''));
37
- let message = $state('');
38
- let email = $state(untrack(() => userEmail ?? ''));
39
- let submitting = $state(false);
40
- let submitted = $state(false);
41
- let errorMsg = $state('');
42
-
43
- async function handleSubmit(e: Event) {
44
- e.preventDefault();
45
- if (!message.trim()) return;
46
-
47
- submitting = true;
48
- errorMsg = '';
49
-
50
- try {
51
- await getClient().submitFeedback({
52
- category,
53
- message: message.trim(),
54
- email: email.trim() || undefined,
55
- pageUrl: typeof window !== 'undefined' ? window.location.href : undefined,
56
- });
57
-
58
- submitted = true;
59
- onSuccess?.();
60
- } catch (err) {
61
- errorMsg = err instanceof Error ? err.message : 'Something went wrong';
62
- onError?.(errorMsg);
63
- } finally {
64
- submitting = false;
65
- }
66
- }
67
-
68
- function reset() {
69
- message = '';
70
- submitted = false;
71
- errorMsg = '';
72
- }
73
- </script>
74
-
75
- {#if submitted}
76
- <div class="rounded-lg border border-green-500/30 bg-green-500/10 p-6 text-center">
77
- <i class="fa-solid fa-circle-check mb-2 text-2xl text-green-500"></i>
78
- <h3 class="font-semibold text-foreground">Thanks for your feedback!</h3>
79
- <p class="mt-1 text-sm text-foreground-secondary">We'll review it shortly.</p>
80
- <button
81
- onclick={reset}
82
- class="mt-4 rounded-md bg-surface-hover px-4 py-2 text-sm text-foreground-secondary transition-colors hover:bg-border"
83
- >
84
- Send more feedback
85
- </button>
86
- </div>
87
- {:else}
88
- <form onsubmit={handleSubmit} class="space-y-4">
89
- {#if categories.length > 1}
90
- <div>
91
- <label for="sf-category" class="mb-1 block text-sm font-semibold text-foreground">
92
- Category
93
- </label>
94
- <select
95
- id="sf-category"
96
- bind:value={category}
97
- class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground"
98
- >
99
- {#each categories as cat (cat)}
100
- <option value={cat}>{cat}</option>
101
- {/each}
102
- </select>
103
- </div>
104
- {/if}
105
-
106
- <div>
107
- <label for="sf-message" class="mb-1 block text-sm font-semibold text-foreground">
108
- Message <span class="text-red-500">*</span>
109
- </label>
110
- <textarea
111
- id="sf-message"
112
- bind:value={message}
113
- placeholder="Tell us what's on your mind..."
114
- rows="4"
115
- maxlength="5000"
116
- required
117
- class="w-full resize-y rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground placeholder:text-foreground-tertiary"
118
- ></textarea>
119
- </div>
120
-
121
- <div>
122
- <label for="sf-email" class="mb-1 block text-sm font-semibold text-foreground">
123
- Email <span class="text-foreground-tertiary">(optional)</span>
124
- </label>
125
- <input
126
- id="sf-email"
127
- type="email"
128
- bind:value={email}
129
- placeholder="your@email.com"
130
- class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground placeholder:text-foreground-tertiary"
131
- />
132
- </div>
133
-
134
- {#if errorMsg}
135
- <div class="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-500">
136
- <i class="fa-solid fa-circle-exclamation mr-1"></i>
137
- {errorMsg}
138
- </div>
139
- {/if}
140
-
141
- <button
142
- type="submit"
143
- disabled={submitting || !message.trim()}
144
- class="w-full rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
145
- >
146
- {#if submitting}
147
- <i class="fa-solid fa-spinner fa-spin mr-1"></i>
148
- Submitting...
149
- {:else}
150
- <i class="fa-solid fa-paper-plane mr-1"></i>
151
- Submit Feedback
152
- {/if}
153
- </button>
154
- </form>
155
- {/if}
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import { createStatusClient, type StatusClient } from '../api.js';
4
+
5
+ interface Props {
6
+ apiKey: string;
7
+ apiUrl: string;
8
+ categories?: string[];
9
+ userEmail?: string;
10
+ onSuccess?: () => void;
11
+ onError?: (error: string) => void;
12
+ }
13
+
14
+ let {
15
+ apiKey,
16
+ apiUrl,
17
+ categories = ['Bug', 'Feature Request', 'General'],
18
+ userEmail,
19
+ onSuccess,
20
+ onError,
21
+ }: Props = $props();
22
+
23
+ let client: StatusClient | null = $state(null);
24
+ let lastApiUrl = '';
25
+ let lastApiKey = '';
26
+
27
+ function getClient(): StatusClient {
28
+ if (!client || lastApiUrl !== apiUrl || lastApiKey !== apiKey) {
29
+ client = createStatusClient({ apiUrl, apiKey });
30
+ lastApiUrl = apiUrl;
31
+ lastApiKey = apiKey;
32
+ }
33
+ return client;
34
+ }
35
+
36
+ let category = $state(untrack(() => categories[0] || ''));
37
+ let message = $state('');
38
+ let email = $state(untrack(() => userEmail ?? ''));
39
+ let submitting = $state(false);
40
+ let submitted = $state(false);
41
+ let errorMsg = $state('');
42
+
43
+ async function handleSubmit(e: Event) {
44
+ e.preventDefault();
45
+ if (!message.trim()) return;
46
+
47
+ submitting = true;
48
+ errorMsg = '';
49
+
50
+ try {
51
+ await getClient().submitFeedback({
52
+ category,
53
+ message: message.trim(),
54
+ email: email.trim() || undefined,
55
+ pageUrl: typeof window !== 'undefined' ? window.location.href : undefined,
56
+ });
57
+
58
+ submitted = true;
59
+ onSuccess?.();
60
+ } catch (err) {
61
+ errorMsg = err instanceof Error ? err.message : 'Something went wrong';
62
+ onError?.(errorMsg);
63
+ } finally {
64
+ submitting = false;
65
+ }
66
+ }
67
+
68
+ function reset() {
69
+ message = '';
70
+ submitted = false;
71
+ errorMsg = '';
72
+ }
73
+ </script>
74
+
75
+ {#if submitted}
76
+ <div class="rounded-lg border border-green-500/30 bg-green-500/10 p-6 text-center">
77
+ <i class="fa-solid fa-circle-check mb-2 text-2xl text-green-500"></i>
78
+ <h3 class="font-semibold text-foreground">Thanks for your feedback!</h3>
79
+ <p class="mt-1 text-sm text-foreground-secondary">We'll review it shortly.</p>
80
+ <button
81
+ onclick={reset}
82
+ class="mt-4 rounded-md bg-surface-hover px-4 py-2 text-sm text-foreground-secondary transition-colors hover:bg-border"
83
+ >
84
+ Send more feedback
85
+ </button>
86
+ </div>
87
+ {:else}
88
+ <form onsubmit={handleSubmit} class="space-y-4">
89
+ {#if categories.length > 1}
90
+ <div>
91
+ <label for="sf-category" class="mb-1 block text-sm font-semibold text-foreground">
92
+ Category
93
+ </label>
94
+ <select
95
+ id="sf-category"
96
+ bind:value={category}
97
+ class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground"
98
+ >
99
+ {#each categories as cat (cat)}
100
+ <option value={cat}>{cat}</option>
101
+ {/each}
102
+ </select>
103
+ </div>
104
+ {/if}
105
+
106
+ <div>
107
+ <label for="sf-message" class="mb-1 block text-sm font-semibold text-foreground">
108
+ Message <span class="text-red-500">*</span>
109
+ </label>
110
+ <textarea
111
+ id="sf-message"
112
+ bind:value={message}
113
+ placeholder="Tell us what's on your mind..."
114
+ rows="4"
115
+ maxlength="5000"
116
+ required
117
+ class="w-full resize-y rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground placeholder:text-foreground-tertiary"
118
+ ></textarea>
119
+ </div>
120
+
121
+ <div>
122
+ <label for="sf-email" class="mb-1 block text-sm font-semibold text-foreground">
123
+ Email <span class="text-foreground-tertiary">(optional)</span>
124
+ </label>
125
+ <input
126
+ id="sf-email"
127
+ type="email"
128
+ bind:value={email}
129
+ placeholder="your@email.com"
130
+ class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground placeholder:text-foreground-tertiary"
131
+ />
132
+ </div>
133
+
134
+ {#if errorMsg}
135
+ <div class="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-500">
136
+ <i class="fa-solid fa-circle-exclamation mr-1"></i>
137
+ {errorMsg}
138
+ </div>
139
+ {/if}
140
+
141
+ <button
142
+ type="submit"
143
+ disabled={submitting || !message.trim()}
144
+ class="w-full rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
145
+ >
146
+ {#if submitting}
147
+ <i class="fa-solid fa-spinner fa-spin mr-1"></i>
148
+ Submitting...
149
+ {:else}
150
+ <i class="fa-solid fa-paper-plane mr-1"></i>
151
+ Submit Feedback
152
+ {/if}
153
+ </button>
154
+ </form>
155
+ {/if}
@@ -1,105 +1,105 @@
1
- <script lang="ts">
2
- import { onMount } from 'svelte';
3
- import type { System, SystemStatus } from '../types.js';
4
- import { createStatusClient, type StatusClient } from '../api.js';
5
- import { deriveOverallStatus } from '../utils.js';
6
- import StatusIndicator from './shared/StatusIndicator.svelte';
7
-
8
- interface Props {
9
- /** Systems data (pass-through mode) */
10
- systems?: System[];
11
- /** API URL for self-fetching mode */
12
- apiUrl?: string;
13
- /** API key for self-fetching mode */
14
- apiKey?: string;
15
- /** Link to full status page */
16
- statusUrl?: string;
17
- /** Auto-refresh interval in seconds. 0 = disabled. Default: 60 */
18
- refreshInterval?: number;
19
- }
20
-
21
- let { systems: systemsProp, apiUrl, apiKey, statusUrl, refreshInterval = 60 }: Props = $props();
22
-
23
- let systems = $derived(systemsProp ?? []);
24
- let loading = $derived(!systemsProp && !!apiUrl && !!apiKey);
25
- let fetchFailed = $state(false);
26
-
27
- let usesApi = $derived(!systemsProp && !!apiUrl && !!apiKey);
28
-
29
- let cachedClient: StatusClient | null = null;
30
- let cachedApiUrl = '';
31
- let cachedApiKey = '';
32
-
33
- function getClient(): StatusClient {
34
- if (!cachedClient || cachedApiUrl !== apiUrl || cachedApiKey !== apiKey) {
35
- cachedClient = createStatusClient({ apiUrl: apiUrl!, apiKey: apiKey! });
36
- cachedApiUrl = apiUrl!;
37
- cachedApiKey = apiKey!;
38
- }
39
- return cachedClient;
40
- }
41
-
42
- async function fetchData() {
43
- if (!usesApi) return;
44
- try {
45
- const client = getClient();
46
- const data = await client.getStatus();
47
- systems = data.systems;
48
- fetchFailed = false;
49
- } catch {
50
- fetchFailed = true;
51
- } finally {
52
- loading = false;
53
- }
54
- }
55
-
56
- onMount(() => {
57
- fetchData();
58
-
59
- let timer: ReturnType<typeof setInterval> | null = null;
60
- if (usesApi && refreshInterval > 0) {
61
- timer = setInterval(fetchData, refreshInterval * 1000);
62
- }
63
-
64
- return () => {
65
- if (timer) clearInterval(timer);
66
- };
67
- });
68
-
69
- let overallStatus = $derived(deriveOverallStatus(systems));
70
-
71
- const messages: Record<SystemStatus, string> = {
72
- operational: 'All systems operational',
73
- degraded: 'Some systems degraded',
74
- outage: 'System outage detected',
75
- maintenance: 'Scheduled maintenance',
76
- };
77
- </script>
78
-
79
- {#if loading}
80
- <div class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-tertiary">
81
- <div class="h-2 w-2 animate-pulse rounded-full bg-foreground-tertiary"></div>
82
- <span>Loading...</span>
83
- </div>
84
- {:else if fetchFailed}
85
- <button
86
- onclick={() => { loading = true; fetchData(); }}
87
- class="inline-flex items-center gap-2 rounded-full border border-red-500/30 px-3 py-1.5 text-xs text-red-400 transition-colors hover:bg-red-500/10"
88
- >
89
- <i class="fa-solid fa-circle-exclamation"></i>
90
- <span>Status unavailable</span>
91
- </button>
92
- {:else if statusUrl}
93
- <a
94
- href={statusUrl}
95
- class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-secondary transition-colors hover:bg-surface-hover"
96
- >
97
- <StatusIndicator status={overallStatus} size="sm" />
98
- <span>{messages[overallStatus]}</span>
99
- </a>
100
- {:else}
101
- <div class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-secondary">
102
- <StatusIndicator status={overallStatus} size="sm" />
103
- <span>{messages[overallStatus]}</span>
104
- </div>
105
- {/if}
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import type { System, SystemStatus } from '../types.js';
4
+ import { createStatusClient, type StatusClient } from '../api.js';
5
+ import { deriveOverallStatus } from '../utils.js';
6
+ import StatusIndicator from './shared/StatusIndicator.svelte';
7
+
8
+ interface Props {
9
+ /** Systems data (pass-through mode) */
10
+ systems?: System[];
11
+ /** API URL for self-fetching mode */
12
+ apiUrl?: string;
13
+ /** API key for self-fetching mode */
14
+ apiKey?: string;
15
+ /** Link to full status page */
16
+ statusUrl?: string;
17
+ /** Auto-refresh interval in seconds. 0 = disabled. Default: 60 */
18
+ refreshInterval?: number;
19
+ }
20
+
21
+ let { systems: systemsProp, apiUrl, apiKey, statusUrl, refreshInterval = 60 }: Props = $props();
22
+
23
+ let systems = $derived(systemsProp ?? []);
24
+ let loading = $derived(!systemsProp && !!apiUrl && !!apiKey);
25
+ let fetchFailed = $state(false);
26
+
27
+ let usesApi = $derived(!systemsProp && !!apiUrl && !!apiKey);
28
+
29
+ let cachedClient: StatusClient | null = null;
30
+ let cachedApiUrl = '';
31
+ let cachedApiKey = '';
32
+
33
+ function getClient(): StatusClient {
34
+ if (!cachedClient || cachedApiUrl !== apiUrl || cachedApiKey !== apiKey) {
35
+ cachedClient = createStatusClient({ apiUrl: apiUrl!, apiKey: apiKey! });
36
+ cachedApiUrl = apiUrl!;
37
+ cachedApiKey = apiKey!;
38
+ }
39
+ return cachedClient;
40
+ }
41
+
42
+ async function fetchData() {
43
+ if (!usesApi) return;
44
+ try {
45
+ const client = getClient();
46
+ const data = await client.getStatus();
47
+ systems = data.systems;
48
+ fetchFailed = false;
49
+ } catch {
50
+ fetchFailed = true;
51
+ } finally {
52
+ loading = false;
53
+ }
54
+ }
55
+
56
+ onMount(() => {
57
+ fetchData();
58
+
59
+ let timer: ReturnType<typeof setInterval> | null = null;
60
+ if (usesApi && refreshInterval > 0) {
61
+ timer = setInterval(fetchData, refreshInterval * 1000);
62
+ }
63
+
64
+ return () => {
65
+ if (timer) clearInterval(timer);
66
+ };
67
+ });
68
+
69
+ let overallStatus = $derived(deriveOverallStatus(systems));
70
+
71
+ const messages: Record<SystemStatus, string> = {
72
+ operational: 'All systems operational',
73
+ degraded: 'Some systems degraded',
74
+ outage: 'System outage detected',
75
+ maintenance: 'Scheduled maintenance',
76
+ };
77
+ </script>
78
+
79
+ {#if loading}
80
+ <div class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-tertiary">
81
+ <div class="h-2 w-2 animate-pulse rounded-full bg-foreground-tertiary"></div>
82
+ <span>Loading...</span>
83
+ </div>
84
+ {:else if fetchFailed}
85
+ <button
86
+ onclick={() => { loading = true; fetchData(); }}
87
+ class="inline-flex items-center gap-2 rounded-full border border-red-500/30 px-3 py-1.5 text-xs text-red-400 transition-colors hover:bg-red-500/10"
88
+ >
89
+ <i class="fa-solid fa-circle-exclamation"></i>
90
+ <span>Status unavailable</span>
91
+ </button>
92
+ {:else if statusUrl}
93
+ <a
94
+ href={statusUrl}
95
+ class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-secondary transition-colors hover:bg-surface-hover"
96
+ >
97
+ <StatusIndicator status={overallStatus} size="sm" />
98
+ <span>{messages[overallStatus]}</span>
99
+ </a>
100
+ {:else}
101
+ <div class="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1.5 text-xs text-foreground-secondary">
102
+ <StatusIndicator status={overallStatus} size="sm" />
103
+ <span>{messages[overallStatus]}</span>
104
+ </div>
105
+ {/if}