managarten/packages/feedback/src/feedback.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

117 lines
3.2 KiB
TypeScript

/**
* Core feedback types — Single source of truth for the @mana/feedback hub.
*
* Mana-analytics' Postgres enums (`feedback.feedback_category`,
* `feedback.feedback_status`) MUST mirror these literal unions exactly.
* If you add or rename a value here, also write a SQL migration under
* services/mana-analytics/drizzle/.
*/
export type FeedbackCategory =
| 'bug'
| 'feature'
| 'improvement'
| 'question'
| 'praise'
| 'onboarding-wish'
| 'other';
export type FeedbackStatus =
| 'submitted'
| 'under_review'
| 'planned'
| 'in_progress'
| 'completed'
| 'declined';
/**
* Whitelisted reaction emojis — must mirror REACTION_WEIGHTS in
* services/mana-analytics/src/services/feedback.ts. Server rejects
* any emoji not in this list.
*/
export const REACTION_EMOJIS = ['👍', '❤️', '🚀', '🤔', '🎉'] as const;
export type ReactionEmoji = (typeof REACTION_EMOJIS)[number];
export const REACTION_LABELS: Record<ReactionEmoji, string> = {
'👍': 'Will ich auch',
'❤️': 'Liebe ich',
'🚀': 'Ship it',
'🤔': 'Macht mich nachdenklich',
'🎉': 'Feier',
};
/**
* Anonymized feedback item as it appears in the public community feed.
* Never contains userId or other identifying fields — only the
* persistent display_name pseudonym ("Wachsame Eule #4528").
*/
export interface PublicFeedbackItem {
id: string;
appId: string;
title: string | null;
feedbackText: string;
category: FeedbackCategory;
status: FeedbackStatus;
moduleContext: string | null;
parentId: string | null;
displayName: string;
reactions: Partial<Record<string, number>>;
score: number;
adminResponse: string | null;
createdAt: string;
updatedAt: string;
/** Auth-only: which emojis the requesting user has reacted with. */
myReactions?: string[];
}
/**
* Authenticated, full feedback record (own submissions / admin views).
* Includes the user-private fields the public feed redacts.
*/
export interface Feedback {
id: string;
userId: string;
appId: string;
title?: string;
feedbackText: string;
category: FeedbackCategory;
status: FeedbackStatus;
isPublic: boolean;
adminResponse?: string;
voteCount: number;
displayHash?: string;
displayName?: string;
moduleContext?: string;
parentId?: string;
reactions?: Partial<Record<string, number>>;
score?: number;
deviceInfo?: Record<string, unknown>;
createdAt: string;
updatedAt: string;
publishedAt?: string;
completedAt?: string;
// Legacy / derived for older UI surfaces:
userHasVoted?: boolean;
}
export const FEEDBACK_CATEGORY_LABELS: Record<FeedbackCategory, string> = {
bug: 'Bug',
feature: 'Feature',
improvement: 'Verbesserung',
question: 'Frage',
praise: 'Lob',
'onboarding-wish': 'Was ich mir wünsche',
other: 'Sonstiges',
};
export const FEEDBACK_STATUS_CONFIG: Record<
FeedbackStatus,
{ label: string; color: string; icon: string }
> = {
submitted: { label: 'Eingereicht', color: '#999', icon: 'clock' },
under_review: { label: 'Wird geprüft', color: '#3498DB', icon: 'eye' },
planned: { label: 'Geplant', color: '#9B59B6', icon: 'calendar' },
in_progress: { label: 'In Arbeit', color: '#F39C12', icon: 'loader' },
completed: { label: 'Umgesetzt', color: '#27AE60', icon: 'check-circle' },
declined: { label: 'Abgelehnt', color: '#E74C3C', icon: 'x-circle' },
};