@alliance-droid/status-feedback-system 1.3.11 → 1.3.13
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/components/DevFeedback.svelte +16 -13
- package/dist/components/DevFeedback.svelte.d.ts +1 -1
- package/dist/components/FeedbackBoard.svelte +4 -1
- package/dist/components/FeedbackBoard.svelte.d.ts +2 -0
- package/dist/components/board/FeedbackDetail.svelte +16 -16
- package/dist/components/board/FeedbackSubmitForm.svelte +15 -15
- package/dist/components/board/FeedbackSubmitForm.svelte.d.ts +0 -2
- package/dist/components/board/state.svelte.d.ts +4 -5
- package/dist/components/board/state.svelte.js +10 -3
- package/package.json +1 -1
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
showOnboarding?: boolean;
|
|
32
32
|
/** URL to the feedback board page. If omitted, "View Open Issues" is hidden from the context menu. */
|
|
33
33
|
boardUrl?: string;
|
|
34
|
-
/**
|
|
34
|
+
/** Authenticated user email. Required for submitting feedback. */
|
|
35
35
|
userEmail?: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -115,7 +115,8 @@
|
|
|
115
115
|
let formType = $state<'bug' | 'feature'>('bug');
|
|
116
116
|
let formTitle = $state('');
|
|
117
117
|
let formMessage = $state('');
|
|
118
|
-
|
|
118
|
+
|
|
119
|
+
let isAuthenticated = $derived(!!userEmail);
|
|
119
120
|
let submitting = $state(false);
|
|
120
121
|
let submitResult = $state<'success' | 'error' | null>(null);
|
|
121
122
|
|
|
@@ -233,7 +234,6 @@
|
|
|
233
234
|
formType = type;
|
|
234
235
|
formTitle = '';
|
|
235
236
|
formMessage = '';
|
|
236
|
-
formEmail = userEmail ?? '';
|
|
237
237
|
submitResult = null;
|
|
238
238
|
menuOpen = false;
|
|
239
239
|
formOpen = true;
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
async function handleSubmit() {
|
|
252
|
-
if (!formMessage.trim()) return;
|
|
252
|
+
if (!formMessage.trim() || !isAuthenticated) return;
|
|
253
253
|
submitting = true;
|
|
254
254
|
submitResult = null;
|
|
255
255
|
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
title: formTitle.trim() || undefined,
|
|
287
287
|
message: formMessage.trim(),
|
|
288
288
|
category: formType === 'bug' ? 'bug' : 'feature',
|
|
289
|
-
email:
|
|
289
|
+
email: userEmail,
|
|
290
290
|
pageUrl: window.location.href,
|
|
291
291
|
metadata,
|
|
292
292
|
});
|
|
@@ -372,7 +372,17 @@
|
|
|
372
372
|
{formType === 'bug' ? "We'll look into this." : "Thanks for the suggestion!"}
|
|
373
373
|
</p>
|
|
374
374
|
</div>
|
|
375
|
+
{:else if !isAuthenticated}
|
|
376
|
+
<div class="text-center py-6 text-foreground-tertiary">
|
|
377
|
+
<i class="fa-solid fa-lock text-2xl" aria-hidden="true"></i>
|
|
378
|
+
<p class="mt-2 text-sm">Sign in to submit feedback</p>
|
|
379
|
+
</div>
|
|
375
380
|
{:else}
|
|
381
|
+
<div class="flex items-center gap-1.5 text-xs text-foreground-tertiary">
|
|
382
|
+
<i class="fa-solid fa-user" aria-hidden="true"></i>
|
|
383
|
+
<span>Submitting as <span class="font-medium text-foreground-secondary">{userEmail}</span></span>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
376
386
|
<Input
|
|
377
387
|
bind:value={formTitle}
|
|
378
388
|
placeholder={formType === 'bug' ? 'Brief summary of the bug' : 'Feature title'}
|
|
@@ -388,13 +398,6 @@
|
|
|
388
398
|
rows={4}
|
|
389
399
|
/>
|
|
390
400
|
|
|
391
|
-
<Input
|
|
392
|
-
type="email"
|
|
393
|
-
bind:value={formEmail}
|
|
394
|
-
placeholder="your@email.com (optional)"
|
|
395
|
-
label="Email"
|
|
396
|
-
/>
|
|
397
|
-
|
|
398
401
|
<!-- Auto-captured context (bugs only) -->
|
|
399
402
|
{#if formType === 'bug'}
|
|
400
403
|
<div class="space-y-2">
|
|
@@ -470,7 +473,7 @@
|
|
|
470
473
|
</div>
|
|
471
474
|
|
|
472
475
|
<!-- Footer -->
|
|
473
|
-
{#if submitResult !== 'success'}
|
|
476
|
+
{#if submitResult !== 'success' && isAuthenticated}
|
|
474
477
|
<div class="flex justify-end gap-2 px-5 py-4 border-t border-border shrink-0">
|
|
475
478
|
<Button variant="neutral" style="outline" onclick={() => (formOpen = false)}>
|
|
476
479
|
Cancel
|
|
@@ -9,7 +9,7 @@ interface Props {
|
|
|
9
9
|
showOnboarding?: boolean;
|
|
10
10
|
/** URL to the feedback board page. If omitted, "View Open Issues" is hidden from the context menu. */
|
|
11
11
|
boardUrl?: string;
|
|
12
|
-
/**
|
|
12
|
+
/** Authenticated user email. Required for submitting feedback. */
|
|
13
13
|
userEmail?: string;
|
|
14
14
|
}
|
|
15
15
|
declare const DevFeedback: import("svelte").Component<Props, {}, "">;
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
interface Props {
|
|
17
17
|
apiUrl: string;
|
|
18
18
|
apiKey: string;
|
|
19
|
+
/** Authenticated user email. Required for submitting feedback and replies. */
|
|
20
|
+
userEmail?: string;
|
|
19
21
|
/** Category labels and icons — order determines sidebar order */
|
|
20
22
|
categoryConfig?: Record<string, CategoryConfig>;
|
|
21
23
|
/** Title shown at the top */
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
let {
|
|
30
32
|
apiUrl,
|
|
31
33
|
apiKey,
|
|
34
|
+
userEmail,
|
|
32
35
|
categoryConfig = {},
|
|
33
36
|
title = 'Feedback Board',
|
|
34
37
|
showSubmit = true,
|
|
@@ -36,7 +39,7 @@
|
|
|
36
39
|
}: Props = $props();
|
|
37
40
|
|
|
38
41
|
let board: FeedbackBoardState = $derived(
|
|
39
|
-
createFeedbackBoard({ apiUrl, apiKey, categoryConfig })
|
|
42
|
+
createFeedbackBoard({ apiUrl, apiKey, userEmail, categoryConfig })
|
|
40
43
|
);
|
|
41
44
|
|
|
42
45
|
let showForm = $state(false);
|
|
@@ -2,6 +2,8 @@ import { type CategoryConfig } from './board/index.js';
|
|
|
2
2
|
interface Props {
|
|
3
3
|
apiUrl: string;
|
|
4
4
|
apiKey: string;
|
|
5
|
+
/** Authenticated user email. Required for submitting feedback and replies. */
|
|
6
|
+
userEmail?: string;
|
|
5
7
|
/** Category labels and icons — order determines sidebar order */
|
|
6
8
|
categoryConfig?: Record<string, CategoryConfig>;
|
|
7
9
|
/** Title shown at the top */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
|
-
import { Badge, Spinner, Button, Textarea
|
|
3
|
+
import { Badge, Spinner, Button, Textarea } from '@alliance-droid/svelte-component-library';
|
|
4
4
|
import type { BoardItem } from '../../api.js';
|
|
5
5
|
import type { FeedbackResponse } from '../../types.js';
|
|
6
6
|
import type { FeedbackBoardState } from './state.svelte.js';
|
|
@@ -18,29 +18,26 @@
|
|
|
18
18
|
|
|
19
19
|
// Response form state
|
|
20
20
|
let replyMessage = $state('');
|
|
21
|
-
let replyAuthor = $state('');
|
|
22
21
|
let submittingReply = $state(false);
|
|
23
22
|
let replyError = $state('');
|
|
24
23
|
let replySuccess = $state(false);
|
|
25
24
|
|
|
25
|
+
let isAuthenticated = $derived(!!board.userEmail);
|
|
26
|
+
|
|
26
27
|
onMount(async () => {
|
|
27
28
|
responses = await board.getResponses(item.id);
|
|
28
29
|
loadingResponses = false;
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
async function handleSubmitReply() {
|
|
32
|
-
if (!replyMessage.trim()) return;
|
|
33
|
+
if (!replyMessage.trim() || !isAuthenticated) return;
|
|
33
34
|
submittingReply = true;
|
|
34
35
|
replyError = '';
|
|
35
36
|
try {
|
|
36
|
-
await board.submitResponse(item.id,
|
|
37
|
-
message: replyMessage.trim(),
|
|
38
|
-
author: replyAuthor.trim() || undefined,
|
|
39
|
-
});
|
|
37
|
+
await board.submitResponse(item.id, replyMessage.trim());
|
|
40
38
|
// Refresh responses
|
|
41
39
|
responses = await board.getResponses(item.id);
|
|
42
40
|
replyMessage = '';
|
|
43
|
-
replyAuthor = '';
|
|
44
41
|
replySuccess = true;
|
|
45
42
|
setTimeout(() => { replySuccess = false; }, 2000);
|
|
46
43
|
} catch (err) {
|
|
@@ -134,24 +131,27 @@
|
|
|
134
131
|
<h4 class="text-sm font-semibold text-foreground">
|
|
135
132
|
<i class="fa-solid fa-reply mr-1" aria-hidden="true"></i> Add a Response
|
|
136
133
|
</h4>
|
|
137
|
-
{#if
|
|
134
|
+
{#if !isAuthenticated}
|
|
135
|
+
<p class="text-sm text-foreground-tertiary">
|
|
136
|
+
<i class="fa-solid fa-lock mr-1" aria-hidden="true"></i>
|
|
137
|
+
Sign in to respond
|
|
138
|
+
</p>
|
|
139
|
+
{:else if replySuccess}
|
|
138
140
|
<p class="text-sm text-green-400 font-semibold">
|
|
139
141
|
<i class="fa-solid fa-check-circle mr-1" aria-hidden="true"></i> Response submitted!
|
|
140
142
|
</p>
|
|
141
143
|
{:else}
|
|
144
|
+
<div class="flex items-center gap-1.5 text-xs text-foreground-tertiary">
|
|
145
|
+
<i class="fa-solid fa-user" aria-hidden="true"></i>
|
|
146
|
+
<span>Replying as <span class="font-medium text-foreground-secondary">{board.userEmail}</span></span>
|
|
147
|
+
</div>
|
|
142
148
|
<Textarea
|
|
143
149
|
value={replyMessage}
|
|
144
150
|
oninput={(e) => (replyMessage = (e.currentTarget as HTMLTextAreaElement).value)}
|
|
145
151
|
placeholder="Share your thoughts..."
|
|
146
152
|
rows={3}
|
|
147
153
|
/>
|
|
148
|
-
<div class="flex gap-2 items-center">
|
|
149
|
-
<div class="flex-1">
|
|
150
|
-
<Input
|
|
151
|
-
bind:value={replyAuthor}
|
|
152
|
-
placeholder="Your name (optional)"
|
|
153
|
-
/>
|
|
154
|
-
</div>
|
|
154
|
+
<div class="flex gap-2 items-center justify-end">
|
|
155
155
|
<Button
|
|
156
156
|
variant="primary"
|
|
157
157
|
size="sm"
|
|
@@ -6,26 +6,25 @@
|
|
|
6
6
|
board: FeedbackBoardState;
|
|
7
7
|
/** Available category options for the dropdown */
|
|
8
8
|
categories?: string[];
|
|
9
|
-
/** Pre-fill the email field (e.g. from the logged-in user's session) */
|
|
10
|
-
userEmail?: string;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
let { board, categories = []
|
|
11
|
+
let { board, categories = [] }: Props = $props();
|
|
14
12
|
|
|
15
13
|
let title = $state('');
|
|
16
14
|
let message = $state('');
|
|
17
15
|
let category = $state('');
|
|
18
|
-
let email = $derived(userEmail ?? '');
|
|
19
16
|
let submitting = $state(false);
|
|
20
17
|
let success = $state(false);
|
|
21
18
|
let error = $state('');
|
|
22
19
|
|
|
20
|
+
let isAuthenticated = $derived(!!board.userEmail);
|
|
21
|
+
|
|
23
22
|
let categoryOptions = $derived(
|
|
24
23
|
categories.map((c) => ({ value: c, label: board.getCategoryLabel(c) }))
|
|
25
24
|
);
|
|
26
25
|
|
|
27
26
|
async function handleSubmit() {
|
|
28
|
-
if (!message.trim()) return;
|
|
27
|
+
if (!message.trim() || !isAuthenticated) return;
|
|
29
28
|
submitting = true;
|
|
30
29
|
error = '';
|
|
31
30
|
try {
|
|
@@ -33,13 +32,11 @@
|
|
|
33
32
|
title: title.trim() || undefined,
|
|
34
33
|
message: message.trim(),
|
|
35
34
|
category: category || undefined,
|
|
36
|
-
email: email.trim() || undefined,
|
|
37
35
|
});
|
|
38
36
|
success = true;
|
|
39
37
|
title = '';
|
|
40
38
|
message = '';
|
|
41
39
|
category = '';
|
|
42
|
-
email = '';
|
|
43
40
|
setTimeout(() => { success = false; }, 2000);
|
|
44
41
|
} catch (err) {
|
|
45
42
|
error = err instanceof Error ? err.message : 'Failed to submit';
|
|
@@ -50,12 +47,21 @@
|
|
|
50
47
|
</script>
|
|
51
48
|
|
|
52
49
|
<Card variant="outlined" padding="md">
|
|
53
|
-
{#if
|
|
50
|
+
{#if !isAuthenticated}
|
|
51
|
+
<div class="text-center py-3 text-foreground-tertiary">
|
|
52
|
+
<i class="fa-solid fa-lock mr-1" aria-hidden="true"></i>
|
|
53
|
+
<span class="text-sm">Sign in to submit feedback</span>
|
|
54
|
+
</div>
|
|
55
|
+
{:else if success}
|
|
54
56
|
<div class="text-center py-2 text-green-400 font-semibold">
|
|
55
57
|
<i class="fa-solid fa-check-circle mr-1"></i> Submitted!
|
|
56
58
|
</div>
|
|
57
59
|
{:else}
|
|
58
60
|
<div class="space-y-3">
|
|
61
|
+
<div class="flex items-center gap-1.5 text-xs text-foreground-tertiary">
|
|
62
|
+
<i class="fa-solid fa-user" aria-hidden="true"></i>
|
|
63
|
+
<span>Submitting as <span class="font-medium text-foreground-secondary">{board.userEmail}</span></span>
|
|
64
|
+
</div>
|
|
59
65
|
<Input
|
|
60
66
|
bind:value={title}
|
|
61
67
|
placeholder="Title (optional)"
|
|
@@ -81,13 +87,7 @@
|
|
|
81
87
|
/>
|
|
82
88
|
</div>
|
|
83
89
|
{/if}
|
|
84
|
-
<div class="flex-1">
|
|
85
|
-
<Input
|
|
86
|
-
type="email"
|
|
87
|
-
bind:value={email}
|
|
88
|
-
placeholder="Email (optional)"
|
|
89
|
-
/>
|
|
90
|
-
</div>
|
|
90
|
+
<div class="flex-1"></div>
|
|
91
91
|
<Button
|
|
92
92
|
variant="primary"
|
|
93
93
|
disabled={submitting || !message.trim()}
|
|
@@ -3,8 +3,6 @@ interface Props {
|
|
|
3
3
|
board: FeedbackBoardState;
|
|
4
4
|
/** Available category options for the dropdown */
|
|
5
5
|
categories?: string[];
|
|
6
|
-
/** Pre-fill the email field (e.g. from the logged-in user's session) */
|
|
7
|
-
userEmail?: string;
|
|
8
6
|
}
|
|
9
7
|
declare const FeedbackSubmitForm: import("svelte").Component<Props, {}, "">;
|
|
10
8
|
type FeedbackSubmitForm = ReturnType<typeof FeedbackSubmitForm>;
|
|
@@ -17,12 +17,15 @@ export interface FeedbackBoardOptions {
|
|
|
17
17
|
apiUrl: string;
|
|
18
18
|
apiKey: string;
|
|
19
19
|
categoryConfig?: Record<string, CategoryConfig>;
|
|
20
|
+
/** Authenticated user email. Required for submitting feedback and replies. */
|
|
21
|
+
userEmail?: string;
|
|
20
22
|
/** Items per page. Default: 50 */
|
|
21
23
|
pageSize?: number;
|
|
22
24
|
}
|
|
23
25
|
export interface FeedbackBoardState {
|
|
24
26
|
readonly client: StatusClient;
|
|
25
27
|
readonly categoryConfig: Record<string, CategoryConfig>;
|
|
28
|
+
readonly userEmail: string | null;
|
|
26
29
|
readonly items: BoardItem[];
|
|
27
30
|
readonly categories: BoardCategory[];
|
|
28
31
|
readonly total: number;
|
|
@@ -43,12 +46,8 @@ export interface FeedbackBoardState {
|
|
|
43
46
|
title?: string;
|
|
44
47
|
message: string;
|
|
45
48
|
category?: string;
|
|
46
|
-
email?: string;
|
|
47
|
-
}): Promise<void>;
|
|
48
|
-
submitResponse(itemId: string, data: {
|
|
49
|
-
message: string;
|
|
50
|
-
author?: string;
|
|
51
49
|
}): Promise<void>;
|
|
50
|
+
submitResponse(itemId: string, message: string): Promise<void>;
|
|
52
51
|
getCategoryIcon(name: string): string;
|
|
53
52
|
getCategoryLabel(name: string): string;
|
|
54
53
|
getCategoryDescription(name: string): string;
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createStatusClient } from '../../api.js';
|
|
10
10
|
export function createFeedbackBoard(options) {
|
|
11
|
-
const { apiUrl, apiKey, categoryConfig = {}, pageSize = 50 } = options;
|
|
11
|
+
const { apiUrl, apiKey, categoryConfig = {}, userEmail, pageSize = 50 } = options;
|
|
12
12
|
const client = createStatusClient({ apiUrl, apiKey });
|
|
13
|
+
const resolvedEmail = userEmail?.trim() || null;
|
|
13
14
|
// Reactive state
|
|
14
15
|
let items = $state([]);
|
|
15
16
|
let categories = $state([]);
|
|
@@ -125,14 +126,19 @@ export function createFeedbackBoard(options) {
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
async function submitFeedback(data) {
|
|
129
|
+
if (!resolvedEmail)
|
|
130
|
+
throw new Error('Authentication required to submit feedback');
|
|
128
131
|
await client.submitFeedback({
|
|
129
132
|
...data,
|
|
133
|
+
email: resolvedEmail,
|
|
130
134
|
pageUrl: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
131
135
|
});
|
|
132
136
|
await load();
|
|
133
137
|
}
|
|
134
|
-
async function submitResponse(itemId,
|
|
135
|
-
|
|
138
|
+
async function submitResponse(itemId, message) {
|
|
139
|
+
if (!resolvedEmail)
|
|
140
|
+
throw new Error('Authentication required to submit a response');
|
|
141
|
+
await client.submitResponse(itemId, { message, author: resolvedEmail });
|
|
136
142
|
// Invalidate cache so next getResponses fetches fresh data
|
|
137
143
|
delete responseCache[itemId];
|
|
138
144
|
// Update response count on the item
|
|
@@ -143,6 +149,7 @@ export function createFeedbackBoard(options) {
|
|
|
143
149
|
return {
|
|
144
150
|
get client() { return client; },
|
|
145
151
|
get categoryConfig() { return categoryConfig; },
|
|
152
|
+
get userEmail() { return resolvedEmail; },
|
|
146
153
|
get items() { return items; },
|
|
147
154
|
get categories() { return categories; },
|
|
148
155
|
get total() { return total; },
|
package/package.json
CHANGED