managarten/packages/feedback/src/createPublicFeedbackService.ts
Till JS c9b122076a feat(feedback): public feed types + ReactionBar + service split
@mana/feedback wird zur Pflege-SSOT für Public-Community-Hub.

- PublicFeedbackItem-Typ: anonymisiertes Item, das nur display_name +
  reactions + status führt — kein userId, displayHash, deviceInfo.
- ReactionEmoji ('👍' '❤️' '🚀' '🤔' '🎉') + REACTION_LABELS mit DE-Labels.
- CreateFeedbackInput erweitert um moduleContext + parentId. Reactions
  + score auf Feedback-Type optional gemacht.
- Service-Split:
  createFeedbackService    — auth-required Submit/React/Manage,
                            getPublicFeed (auth-enriched mit myReactions)
  createPublicFeedbackService — anonymous, SSR-only, getFeed/getItem.
  toggleReaction(emoji) statt vote/unvote (legacy-Shims bleiben für
  back-compat zu vote → '👍'-Toggle).
- ReactionBar.svelte: Slack-Style emoji-row mit Active-Highlighting für
  myReactions, ReadOnly-Mode für Public-SSR. Auto-disabled-Tooltip.
- index.ts re-exportiert die neuen Typen + ReactionBar; FeedbackVote
  rausgeschmissen (durch FeedbackReactions im Server-Schema ersetzt).

FeedbackCard + FeedbackPage minimal angepasst, damit svelte-check
clean bleibt — die Legacy-Komponenten bleiben funktional, werden aber
in Phase 3 zu @mana/feedback's neuen Modul-Views ausgemistet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 00:01:06 +02:00

48 lines
1.9 KiB
TypeScript

/**
* Public, anonymous feedback service — for SSR / unauthenticated reads
* of the community feed (e.g. /community route, embeddable widget).
*
* No auth, no submit, no react. If you need write access, instantiate
* `createFeedbackService()` instead and pass a getAuthToken callback.
*/
import type { PublicFeedListResponse, PublicItemResponse, FeedbackQueryParams } from './api';
import type { PublicFeedbackItem } from './feedback';
import type { PublicFeedbackServiceConfig } from './types';
export function createPublicFeedbackService(config: PublicFeedbackServiceConfig) {
const { apiUrl, appId, publicEndpoint = '/api/v1/public/feedback' } = config;
const baseUrl = apiUrl.replace(/\/$/, '');
async function fetchPublic<T>(endpoint: string): Promise<T> {
const res = await fetch(`${baseUrl}${endpoint}`);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
return res.json();
}
function feedQueryString(query?: FeedbackQueryParams): string {
const params = new URLSearchParams();
const effectiveAppId = query?.appId ?? appId;
if (effectiveAppId) params.set('appId', effectiveAppId);
if (query?.moduleContext) params.set('moduleContext', query.moduleContext);
if (query?.status) params.set('status', query.status);
if (query?.category) params.set('category', query.category);
if (query?.limit) params.set('limit', String(query.limit));
if (query?.offset) params.set('offset', String(query.offset));
return params.toString();
}
async function getFeed(query?: FeedbackQueryParams): Promise<PublicFeedbackItem[]> {
const qs = feedQueryString(query);
const res = await fetchPublic<PublicFeedListResponse>(`${publicEndpoint}/feed?${qs}`);
return res.items;
}
async function getItem(id: string): Promise<PublicItemResponse> {
return fetchPublic<PublicItemResponse>(`${publicEndpoint}/${id}`);
}
return { getFeed, getItem };
}
export type PublicFeedbackService = ReturnType<typeof createPublicFeedbackService>;