feat(web): wire settings feedback submission
This commit is contained in:
parent
5dd0a18221
commit
d38b9745a4
@ -17,7 +17,7 @@
|
||||
| TODO | Priority | Phase | File(s) | Summary |
|
||||
|------|----------|-------|---------|---------|
|
||||
| **TODO-001** | medium | 0 | `web/src/app/providers.tsx` | Kill switch maintenance banner — create `<MaintenanceBanner />`, set React state when `checkKillSwitch()` returns `disabled=true`, disable timer creation buttons |
|
||||
| **TODO-002** | medium | 0 | `web/src/app/(app)/settings/page.tsx` (create) | Wire feedback button into settings page (or floating FAB) using `feedbackClient` from `web/src/lib/feedback.ts` |
|
||||
| ✅ **TODO-002** | medium | 0 | `web/src/app/settings/page.tsx` | ~~Wire feedback button into settings page using `feedbackClient`~~ — settings feedback form submits through `@bytelyst/feedback-client`; verification blocked locally by missing `GITEA_NPM_TOKEN` for private `@bytelyst/*` install (df670d9) |
|
||||
| **TODO-003** | medium | 0 | `web/src/components/CreateTimerModal.tsx`, `web/src/components/AlarmOverlay.tsx` | Apply `@bytelyst/accessibility` focus trap + screen reader announcements. Ensure `--cm-*` color tokens meet WCAG AA contrast |
|
||||
| ✅ **TODO-004** | medium | A.1 | `backend/src/modules/routines/routes.ts` | ~~Clone template~~ — templates now cloned via `crypto.randomUUID()`, original stays reusable (0e7c1ae) |
|
||||
| ✅ **TODO-005** | high | A.4 | `backend/src/lib/ai-context.ts` | ~~Wire LLM~~ — dual-path: extraction-service → Ollama `/api/generate` (5s timeout, feature-gated) (229ce4f) |
|
||||
@ -64,7 +64,7 @@
|
||||
| `@bytelyst/design-tokens` | ✅ Used | `--cm-*` CSS props in globals.css |
|
||||
| `@bytelyst/ui` | ✅ Used | Imported in providers.tsx |
|
||||
| `@bytelyst/kill-switch-client` | ❌ **MISSING** | Every other product has this — critical for prod safety |
|
||||
| `@bytelyst/feedback-client` | ❌ **MISSING** | No in-app feedback mechanism |
|
||||
| `@bytelyst/feedback-client` | ✅ Used | Settings feedback form submits via shared SDK |
|
||||
| `@bytelyst/event-store` | ❌ **MISSING** | Domain events not persisted — needed for webhooks + audit |
|
||||
| `@bytelyst/webhook-dispatch` | ❌ **MISSING** | Backend uses hand-rolled dispatcher instead of shared package |
|
||||
| `@bytelyst/accessibility` | ❌ **MISSING** | Shared accessibility helpers not consumed |
|
||||
@ -124,7 +124,7 @@ These proxy to `chronomind-backend` (port 4011) via `chronomind-client.ts`.
|
||||
|
||||
- [x] `web/src/lib/feedback.ts` — integrate `@bytelyst/feedback-client` (commit: f3e14e2)
|
||||
- `createFeedbackClient({ baseUrl, productId, getAccessToken })`
|
||||
- [ ] Add feedback button to settings page (or floating FAB) (commit: )
|
||||
- [x] Add feedback button to settings page (or floating FAB) (commit: df670d9; status: implemented, verification blocked locally because `pnpm install` requires `GITEA_NPM_TOKEN` for private `@bytelyst/*` packages)
|
||||
- TODO-002: Wire feedback button into settings page using `feedbackClient` from `web/src/lib/feedback.ts`
|
||||
|
||||
### 0.3 — Accessibility Package (Web)
|
||||
@ -642,7 +642,7 @@ Systematic audit against `learning_ai_common_plat/packages/` (58 packages) revea
|
||||
| Gap | Severity | Fixed In |
|
||||
|-----|----------|----------|
|
||||
| No `@bytelyst/kill-switch-client` | **Critical** | Phase 0.1 |
|
||||
| No `@bytelyst/feedback-client` | Medium | Phase 0.2 |
|
||||
| No settings feedback entry point using `@bytelyst/feedback-client` | Medium | Phase 0.2 — complete (df670d9) |
|
||||
| No `@bytelyst/accessibility` helpers | Medium | Phase 0.3 |
|
||||
| No feature flags for new features | **Critical** | Phase 0.4 |
|
||||
| No telemetry events for new features | Medium | Phase 0.5 |
|
||||
|
||||
@ -38,6 +38,7 @@ export default function SettingsPage() {
|
||||
const [feedbackText, setFeedbackText] = useState('');
|
||||
const [feedbackSubmitting, setFeedbackSubmitting] = useState(false);
|
||||
const [feedbackSuccess, setFeedbackSuccess] = useState(false);
|
||||
const [feedbackError, setFeedbackError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@ -106,6 +107,38 @@ export default function SettingsPage() {
|
||||
.forEach((t) => removeTimer(t.id));
|
||||
};
|
||||
|
||||
const handleFeedbackSubmit = async () => {
|
||||
const body = feedbackText.trim();
|
||||
if (!body) return;
|
||||
|
||||
setFeedbackSubmitting(true);
|
||||
setFeedbackError('');
|
||||
try {
|
||||
await feedbackClient.submitWithScreenshot({
|
||||
type: feedbackType,
|
||||
title: body.slice(0, 100),
|
||||
body,
|
||||
screen: 'settings',
|
||||
appVersion: '0.1.0',
|
||||
platform: 'web',
|
||||
deviceContext: {
|
||||
osVersion: navigator.userAgent,
|
||||
appVersion: '0.1.0',
|
||||
deviceModel: navigator.platform || 'web',
|
||||
screenResolution: `${window.screen.width}x${window.screen.height}`,
|
||||
locale: navigator.language,
|
||||
},
|
||||
});
|
||||
setFeedbackSuccess(true);
|
||||
setFeedbackText('');
|
||||
setTimeout(() => setFeedbackSuccess(false), 4000);
|
||||
} catch {
|
||||
setFeedbackError('Feedback could not be sent right now. Please try again in a moment.');
|
||||
} finally {
|
||||
setFeedbackSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: 'var(--cm-bg-canvas)' }}>
|
||||
<div className="max-w-2xl mx-auto px-4 py-8">
|
||||
@ -526,8 +559,12 @@ export default function SettingsPage() {
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
aria-label="Feedback message"
|
||||
value={feedbackText}
|
||||
onChange={(e) => setFeedbackText(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setFeedbackText(e.target.value);
|
||||
setFeedbackError('');
|
||||
}}
|
||||
placeholder="Tell us what you think..."
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 rounded-lg text-sm outline-none resize-none"
|
||||
@ -537,21 +574,15 @@ export default function SettingsPage() {
|
||||
border: '1px solid var(--cm-border)',
|
||||
}}
|
||||
/>
|
||||
{feedbackError && (
|
||||
<p className="text-xs" style={{ color: 'var(--cm-danger)' }} role="alert">
|
||||
{feedbackError}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!feedbackText.trim()) return;
|
||||
setFeedbackSubmitting(true);
|
||||
try {
|
||||
await feedbackClient.submitWithScreenshot({ type: feedbackType, title: feedbackText.trim().slice(0, 100), body: feedbackText.trim(), platform: 'web' });
|
||||
setFeedbackSuccess(true);
|
||||
setFeedbackText('');
|
||||
setTimeout(() => setFeedbackSuccess(false), 4000);
|
||||
} catch {
|
||||
// fail silently — feedback is best-effort
|
||||
}
|
||||
setFeedbackSubmitting(false);
|
||||
}}
|
||||
onClick={handleFeedbackSubmit}
|
||||
disabled={feedbackSubmitting || !feedbackText.trim()}
|
||||
aria-label="Send feedback"
|
||||
className="w-full px-4 py-2 rounded-lg text-sm font-medium cursor-pointer disabled:opacity-40"
|
||||
style={{ backgroundColor: 'var(--cm-accent)', color: 'var(--cm-white)' }}
|
||||
>
|
||||
|
||||
@ -11,7 +11,7 @@ import { createFeedbackClient } from '@bytelyst/feedback-client';
|
||||
import { getAuthClient, getBaseUrl } from './auth-api';
|
||||
|
||||
const feedbackClient = createFeedbackClient({
|
||||
baseUrl: `${getBaseUrl()}/api`,
|
||||
baseUrl: getBaseUrl(),
|
||||
getAuthToken: () => getAuthClient().getAccessToken(),
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user