mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 12:06:42 +02:00
feat(community): Phase 3.B — loop closure (notifications + my-wishes page)
Schließt den Loop zwischen Submit und Ship. User kriegt jetzt:
- Toast beim nächsten App-Start, wenn ein eigener oder unterstützter
Wisch ›planned/in_progress/completed/declined‹ wurde
- /profile/my-wishes als persönliche Roadmap mit drei Tabs:
Eigene · Unterstützt · Inbox
Server (mana-analytics):
- Neue Tabelle feedback_notifications mit ON DELETE CASCADE auf
user_feedback. Migration 0004 lokal + prod eingespielt.
- adminUpdate enqueued bei jeder Status-Transition Author-
Notifications. AdminResponse-Edits feuern eine eigene
'admin_response'-Notify. tryGrantShipBonus hängt zusätzlich
Reactioner-Notifications dran (›Dein Like ist gelandet, +25 Mana‹).
- Endpoints:
GET /api/v1/feedback/me/notifications?unread_only=true&limit=N
POST /api/v1/feedback/me/notifications/:id/read
POST /api/v1/feedback/me/notifications/read-all
GET /api/v1/feedback/me/reacted (für die My-Wishes-Page)
Package (@mana/feedback):
- FeedbackNotification + NotificationKind types exportiert
- service.getNotifications/markNotificationRead/markAllNotificationsRead
- service.getMyReactedItems
Web:
- lib/notifications/feedback-toaster.svelte.ts: Boot-Pull + 60s-Poll,
rendert unread-notifications via toast-store, markiert sofort read.
In (app)/+layout.svelte's authReady-Hook gestartet/gestoppt.
- /profile/my-wishes: Tab-View über getMyFeedback + getMyReactedItems
+ getNotifications. Tabs zeigen Counter-Badges, unread-Badge in der
Inbox-Sektion. ›Alle als gelesen markieren‹-Action vorhanden.
Pre-launch saubere Lösung — kein Polling-Spam (60s), Mark-Read direkt
nach Toast-Display, fail-soft an mehreren Stellen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c94b67395a
commit
3a18a5e50d
11 changed files with 933 additions and 7 deletions
|
|
@ -18,7 +18,7 @@ import type {
|
|||
ReactInput,
|
||||
} from './api';
|
||||
import type { FeedbackServiceConfig } from './types';
|
||||
import type { PublicFeedbackItem, ReactionEmoji } from './feedback';
|
||||
import type { FeedbackNotification, PublicFeedbackItem, ReactionEmoji } from './feedback';
|
||||
|
||||
export function createFeedbackService(config: FeedbackServiceConfig) {
|
||||
const {
|
||||
|
|
@ -95,6 +95,40 @@ export function createFeedbackService(config: FeedbackServiceConfig) {
|
|||
return fetchWithAuth<FeedbackListResponse>(`${feedbackEndpoint}/me`);
|
||||
}
|
||||
|
||||
async function getMyReactedItems(): Promise<PublicFeedbackItem[]> {
|
||||
const res = await fetchWithAuth<{ items: PublicFeedbackItem[] }>(
|
||||
`${feedbackEndpoint}/me/reacted`
|
||||
);
|
||||
return res.items;
|
||||
}
|
||||
|
||||
async function getNotifications(opts?: {
|
||||
unreadOnly?: boolean;
|
||||
limit?: number;
|
||||
}): Promise<FeedbackNotification[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (opts?.unreadOnly) params.set('unread_only', 'true');
|
||||
if (opts?.limit) params.set('limit', String(opts.limit));
|
||||
const qs = params.toString();
|
||||
const res = await fetchWithAuth<{ items: FeedbackNotification[] }>(
|
||||
`${feedbackEndpoint}/me/notifications${qs ? `?${qs}` : ''}`
|
||||
);
|
||||
return res.items;
|
||||
}
|
||||
|
||||
async function markNotificationRead(id: string): Promise<{ ok: true }> {
|
||||
return fetchWithAuth<{ ok: true }>(`${feedbackEndpoint}/me/notifications/${id}/read`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
async function markAllNotificationsRead(): Promise<{ ok: true; count: number }> {
|
||||
return fetchWithAuth<{ ok: true; count: number }>(
|
||||
`${feedbackEndpoint}/me/notifications/read-all`,
|
||||
{ method: 'POST' }
|
||||
);
|
||||
}
|
||||
|
||||
async function getReplies(feedbackId: string): Promise<PublicFeedbackItem[]> {
|
||||
return fetchWithAuth<PublicFeedbackItem[]>(`${feedbackEndpoint}/${feedbackId}/replies`);
|
||||
}
|
||||
|
|
@ -153,6 +187,10 @@ export function createFeedbackService(config: FeedbackServiceConfig) {
|
|||
getPublicFeedAnonymous,
|
||||
getPublicItemAnonymous,
|
||||
getMyFeedback,
|
||||
getMyReactedItems,
|
||||
getNotifications,
|
||||
markNotificationRead,
|
||||
markAllNotificationsRead,
|
||||
getReplies,
|
||||
toggleReaction,
|
||||
deleteFeedback,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue