@alliance-droid/status-feedback-system 3.0.0 → 4.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.
- package/README.md +69 -24
- package/dist/api.d.ts +38 -1
- package/dist/api.js +12 -0
- package/dist/components/DevFeedback.svelte +2 -1
- package/dist/components/board/FeedbackDetail.svelte +205 -171
- package/dist/components/board/FeedbackItem.svelte +50 -46
- package/dist/components/board/FeedbackSidebar.svelte +58 -35
- package/dist/components/board/FeedbackSidebar.svelte.d.ts +1 -1
- package/dist/components/board/FeedbackSubmitForm.svelte +239 -123
- package/dist/components/board/FeedbackSubmitForm.svelte.d.ts +0 -2
- package/dist/components/board/StatusBadgeMenu.svelte +264 -0
- package/dist/components/board/StatusBadgeMenu.svelte.d.ts +11 -0
- package/dist/components/board/state.svelte.d.ts +21 -3
- package/dist/components/board/state.svelte.js +49 -4
- package/dist/components/board/utils.d.ts +5 -2
- package/dist/components/board/utils.js +20 -6
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/types.d.ts +21 -1
- package/dist/types.js +14 -1
- package/package.json +1 -1
- package/dist/components/FeedbackBoard.svelte +0 -110
- package/dist/components/FeedbackBoard.svelte.d.ts +0 -18
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ This is the **client package** — UI components that talk to a [status-feedback
|
|
|
14
14
|
│ │ StatusPage │──┼───────┼──│ /api/status │ │
|
|
15
15
|
│ │ FeedbackButton │ │ │ │ /api/feedback │ │
|
|
16
16
|
│ │ DevFeedback │ │ │ │ /api/incidents │ │
|
|
17
|
-
│ │
|
|
17
|
+
│ │ Feedback board │ │ │ │ /api/board │ │
|
|
18
|
+
│ │ (composable) │ │ │ │ │ │
|
|
18
19
|
│ └───────────────────┘ │ │ └────────────────────┘ │
|
|
19
20
|
│ │ │ │ │
|
|
20
21
|
│ npm package │ │ MSSQL + Azure Blob │
|
|
@@ -152,32 +153,70 @@ Standalone feedback form — embed it wherever you need it.
|
|
|
152
153
|
| `onSuccess` | `() => void` | — | Called after successful submission |
|
|
153
154
|
| `onError` | `(error: string) => void` | — | Called on submission error |
|
|
154
155
|
|
|
155
|
-
###
|
|
156
|
+
### Feedback board (composable)
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
> 📖 **Full integration guide:** [docs/custom-feedback-integration.md](docs/custom-feedback-integration.md) — composing the parts into your own shell, status management via `getAuthToken`, list restyling, and the not-configured fallback.
|
|
159
|
+
|
|
160
|
+
The board is composed from parts around **one shared state** (`createFeedbackBoard`), so it drops into your own layout/shell — app rails, breadcrumb, a right-hand detail panel, whatever you have.
|
|
158
161
|
|
|
159
162
|
```svelte
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
<script lang="ts">
|
|
164
|
+
import {
|
|
165
|
+
createFeedbackBoard,
|
|
166
|
+
FeedbackSidebar,
|
|
167
|
+
FeedbackList,
|
|
168
|
+
FeedbackDetail,
|
|
169
|
+
FeedbackSubmitForm,
|
|
170
|
+
type BoardItem,
|
|
171
|
+
} from '@alliance-droid/status-feedback-system';
|
|
172
|
+
|
|
173
|
+
let { data } = $props();
|
|
174
|
+
const board = createFeedbackBoard({
|
|
175
|
+
apiUrl: 'https://status-admin.example.com',
|
|
176
|
+
apiKey: 'sf_abc123...',
|
|
177
|
+
userEmail: data.user?.email,
|
|
178
|
+
getAuthToken: () => auth.getAccessToken(), // optional — enables status management
|
|
179
|
+
categoryConfig: {
|
|
180
|
+
Bug: { icon: 'fa-solid fa-bug', label: 'Bugs' },
|
|
181
|
+
'Feature Request': { icon: 'fa-solid fa-lightbulb', label: 'Ideas' },
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
let selected = $state<BoardItem | null>(null);
|
|
186
|
+
</script>
|
|
187
|
+
|
|
188
|
+
<YourSidebar><FeedbackSidebar {board} /></YourSidebar>
|
|
189
|
+
<YourMain><FeedbackList {board} onSelect={(i) => (selected = i)} /></YourMain>
|
|
190
|
+
<YourRail>
|
|
191
|
+
{#if selected}<FeedbackDetail {board} item={selected} />{/if}
|
|
192
|
+
</YourRail>
|
|
193
|
+
<!-- Render <FeedbackSubmitForm {board} /> wherever you want submission. -->
|
|
171
194
|
```
|
|
172
195
|
|
|
173
|
-
|
|
174
|
-
|
|
196
|
+
`createFeedbackBoard(options)` returns a reactive `FeedbackBoardState` (items, categories, filters, voting, `loadTransitions`/`setStatus`, …) shared by every sub-component.
|
|
197
|
+
|
|
198
|
+
| Option | Type | Default | Description |
|
|
199
|
+
|--------|------|---------|-------------|
|
|
175
200
|
| `apiUrl` | `string` | required | Admin backend URL |
|
|
176
|
-
| `apiKey` | `string` | required | Project API key |
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
201
|
+
| `apiKey` | `string` | required | Project API key (`sf_…`) |
|
|
202
|
+
| `userEmail` | `string` | — | Authenticated user (required to submit feedback/replies) |
|
|
203
|
+
| `categoryConfig` | `Record<string, { label?, icon?, description? }>` | `{}` | Relabel / add icons to sidebar categories |
|
|
204
|
+
| `getAuthToken` | `() => string \| Promise<string \| null>` | — | Bearer token so privileged users can set status (see below) |
|
|
205
|
+
| `pageSize` | `number` | `50` | Items per page |
|
|
206
|
+
|
|
207
|
+
Exported parts: `createFeedbackBoard`, `FeedbackSidebar`, `FeedbackList`, `FeedbackItem`, `FeedbackDetail`, `FeedbackSubmitForm`.
|
|
208
|
+
|
|
209
|
+
Categories are fixed by the system (**Bug**, **Feature Request**) — `categoryConfig` only relabels or adds icons.
|
|
210
|
+
|
|
211
|
+
#### Setting status (Admin / ProductOwner)
|
|
212
|
+
|
|
213
|
+
When you pass `getAuthToken`, a signed-in **Admin** or **ProductOwner** can change an item's status from the board (via the status badge → menu) — Open → Considering → Accepted, or a decline with a reason — no trip to the admin dashboard.
|
|
214
|
+
|
|
215
|
+
- `getAuthToken` must return the user's **Alliance OIDC access token** whose audience covers **`status-feedback-admin`** (an *id token*, whose `aud` is your app's client id, is rejected). It's called on demand; return `null` for anonymous / non-privileged users and the control simply won't appear.
|
|
216
|
+
- The token travels as `Authorization: Bearer …` to the admin backend, which **verifies it against the IdP** and derives the user's roles. Authorization (per-project access + which transitions a role may make, including reason prompts on declines/reopens) is enforced **server-side** by the same logic as the dashboard — the client never decides permissions.
|
|
217
|
+
- The available transitions are returned by the backend per item, so the menu always reflects exactly what that user may do.
|
|
218
|
+
|
|
219
|
+
> Security note: the project `apiKey` alone never grants status changes — it only identifies the project. Every mutation additionally requires a valid IdP token, so the public board key stays safe to embed.
|
|
181
220
|
|
|
182
221
|
### DevFeedback
|
|
183
222
|
|
|
@@ -253,10 +292,16 @@ import {
|
|
|
253
292
|
```typescript
|
|
254
293
|
// Components
|
|
255
294
|
import {
|
|
256
|
-
StatusPage, StatusBanner, FeedbackForm, FeedbackButton,
|
|
257
|
-
|
|
258
|
-
|
|
295
|
+
StatusPage, StatusBanner, FeedbackForm, FeedbackButton, DevFeedback,
|
|
296
|
+
StatusIndicator, UptimeBar, IncidentTimeline, SystemCard, PasteImageInput,
|
|
297
|
+
} from '@alliance-droid/status-feedback-system';
|
|
298
|
+
|
|
299
|
+
// Feedback board (composable)
|
|
300
|
+
import {
|
|
301
|
+
createFeedbackBoard,
|
|
302
|
+
FeedbackSidebar, FeedbackList, FeedbackItem, FeedbackDetail, FeedbackSubmitForm,
|
|
259
303
|
} from '@alliance-droid/status-feedback-system';
|
|
304
|
+
import type { FeedbackBoardState, FeedbackBoardOptions, CategoryConfig } from '@alliance-droid/status-feedback-system';
|
|
260
305
|
|
|
261
306
|
// API Client
|
|
262
307
|
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
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, FeedbackResponse } from './types.js';
|
|
14
|
+
import type { System, Incident, StatusHistoryEntry, Feedback, FeedbackResponse, PublicStatus } from './types.js';
|
|
15
15
|
export interface StatusClientConfig {
|
|
16
16
|
/** Base URL of the status-feedback-admin app */
|
|
17
17
|
apiUrl: string;
|
|
@@ -41,6 +41,8 @@ export interface BoardItem {
|
|
|
41
41
|
message: string;
|
|
42
42
|
email: string | null;
|
|
43
43
|
status: string;
|
|
44
|
+
/** Coarse public bucket for display (server projection of the internal status). */
|
|
45
|
+
publicStatus: PublicStatus;
|
|
44
46
|
votes: number;
|
|
45
47
|
adminResponse: string | null;
|
|
46
48
|
responseCount: number;
|
|
@@ -51,6 +53,30 @@ export interface BoardData {
|
|
|
51
53
|
total: number;
|
|
52
54
|
categories: BoardCategory[];
|
|
53
55
|
}
|
|
56
|
+
/** A status a privileged user may move an item to, and whether it needs a reason. */
|
|
57
|
+
export interface BoardTransition {
|
|
58
|
+
/** Raw internal status value to submit. */
|
|
59
|
+
status: string;
|
|
60
|
+
/** Human label for the menu. */
|
|
61
|
+
display: string;
|
|
62
|
+
/** When true, the user must supply a reason to make this transition. */
|
|
63
|
+
requiresReason: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** The current status of a board item plus the transitions the caller may make. */
|
|
66
|
+
export interface BoardTransitions {
|
|
67
|
+
current: {
|
|
68
|
+
status: string;
|
|
69
|
+
display: string;
|
|
70
|
+
publicStatus: PublicStatus;
|
|
71
|
+
};
|
|
72
|
+
targets: BoardTransition[];
|
|
73
|
+
}
|
|
74
|
+
/** Result of applying a status change from the board. */
|
|
75
|
+
export interface BoardStatusUpdate {
|
|
76
|
+
id: string;
|
|
77
|
+
status: string;
|
|
78
|
+
publicStatus: PublicStatus;
|
|
79
|
+
}
|
|
54
80
|
export interface StatusClient {
|
|
55
81
|
/** Get current system statuses */
|
|
56
82
|
getStatus(): Promise<StatusData>;
|
|
@@ -80,6 +106,17 @@ export interface StatusClient {
|
|
|
80
106
|
limit?: number;
|
|
81
107
|
offset?: number;
|
|
82
108
|
}): Promise<BoardData>;
|
|
109
|
+
/**
|
|
110
|
+
* Status transitions the token's user may make on a board item. Requires an
|
|
111
|
+
* Alliance id token; the backend returns an empty `targets` list for users
|
|
112
|
+
* without a qualifying role.
|
|
113
|
+
*/
|
|
114
|
+
getBoardTransitions(feedbackId: string, token: string): Promise<BoardTransitions>;
|
|
115
|
+
/** Set a board item's status (privileged). Requires an Alliance id token. */
|
|
116
|
+
updateBoardStatus(feedbackId: string, data: {
|
|
117
|
+
status: string;
|
|
118
|
+
reason?: string;
|
|
119
|
+
}, token: string): Promise<BoardStatusUpdate>;
|
|
83
120
|
/** Vote on a feedback item */
|
|
84
121
|
vote(feedbackId: string, direction?: 'up' | 'down'): Promise<{
|
|
85
122
|
votes: number;
|
package/dist/api.js
CHANGED
|
@@ -87,6 +87,18 @@ export function createStatusClient(config) {
|
|
|
87
87
|
const qs = params.toString();
|
|
88
88
|
return request(`/api/board${qs ? `?${qs}` : ''}`);
|
|
89
89
|
},
|
|
90
|
+
async getBoardTransitions(feedbackId, token) {
|
|
91
|
+
return request(`/api/board/feedback/${feedbackId}/transitions`, {
|
|
92
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
async updateBoardStatus(feedbackId, data, token) {
|
|
96
|
+
return request(`/api/board/feedback/${feedbackId}/status`, {
|
|
97
|
+
method: 'PATCH',
|
|
98
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
99
|
+
body: JSON.stringify(data),
|
|
100
|
+
});
|
|
101
|
+
},
|
|
90
102
|
async vote(feedbackId, direction = 'up') {
|
|
91
103
|
return request('/api/board/vote', {
|
|
92
104
|
method: 'POST',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createStatusClient, type StatusClient } from '../api.js';
|
|
3
|
+
import { FEEDBACK_CATEGORIES } from '../types.js';
|
|
3
4
|
import PasteImageInput from './shared/PasteImageInput.svelte';
|
|
4
5
|
import {
|
|
5
6
|
Card,
|
|
@@ -363,7 +364,7 @@
|
|
|
363
364
|
await getClient().submitFeedback({
|
|
364
365
|
title: formTitle.trim() || undefined,
|
|
365
366
|
message: formMessage.trim(),
|
|
366
|
-
category: formType === 'bug' ?
|
|
367
|
+
category: formType === 'bug' ? FEEDBACK_CATEGORIES[0] : FEEDBACK_CATEGORIES[1],
|
|
367
368
|
email: userEmail,
|
|
368
369
|
pageUrl: window.location.href,
|
|
369
370
|
metadata,
|