Füge Archivierungs-Funktion für Kommentare hinzu und fixe initiales Laden
- Archivierungs-Funktionalität: Kuratoren können Kommentare archivieren - archived-Flag in CuratorCommentRecipient hinzugefügt - API-Route für Archivieren: /api/curator-comments/[id]/archive - Kommentare werden beim initialen Laden automatisch abgerufen - Archivierte Kommentare werden nicht mehr in der Liste angezeigt - Archivieren-Button in der UI hinzugefügt - Migration für archived-Feld - Übersetzungen für Archivierung (DE/EN)
This commit is contained in:
66
app/api/curator-comments/[id]/archive/route.ts
Normal file
66
app/api/curator-comments/[id]/archive/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { requireStaffAuth } from '@/lib/auth';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
// Require curator authentication
|
||||
const { error, context } = await requireStaffAuth(request);
|
||||
if (error || !context) {
|
||||
return error!;
|
||||
}
|
||||
|
||||
// Only curators can archive comments
|
||||
if (context.role !== 'curator') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Only curators can archive comments' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = await params;
|
||||
const commentId = Number(id);
|
||||
const curatorId = context.curator.id;
|
||||
|
||||
// Verify that this comment belongs to this curator
|
||||
const recipient = await prisma.curatorCommentRecipient.findUnique({
|
||||
where: {
|
||||
commentId_curatorId: {
|
||||
commentId: commentId,
|
||||
curatorId: curatorId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Comment not found or access denied' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Update archived flag
|
||||
await prisma.curatorCommentRecipient.update({
|
||||
where: {
|
||||
id: recipient.id
|
||||
},
|
||||
data: {
|
||||
archived: true
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error archiving comment:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal Server Error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const curatorId = context.curator.id;
|
||||
|
||||
// Get all comments for this curator, ordered by creation date (newest first)
|
||||
// Get all non-archived comments for this curator, ordered by creation date (newest first)
|
||||
const comments = await prisma.curatorCommentRecipient.findMany({
|
||||
where: {
|
||||
curatorId: curatorId
|
||||
curatorId: curatorId,
|
||||
archived: false
|
||||
},
|
||||
include: {
|
||||
comment: {
|
||||
|
||||
@@ -133,16 +133,11 @@ export default function CuratorPageClient() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (showComments && isAuthenticated) {
|
||||
fetchComments();
|
||||
}
|
||||
}, [showComments, isAuthenticated]);
|
||||
|
||||
const bootstrapCuratorData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await Promise.all([fetchCuratorInfo(), fetchSongs(), fetchGenres(), fetchSpecials()]);
|
||||
await Promise.all([fetchCuratorInfo(), fetchSongs(), fetchGenres(), fetchSpecials(), fetchComments()]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -184,6 +179,24 @@ export default function CuratorPageClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const archiveComment = async (commentId: number) => {
|
||||
try {
|
||||
const res = await fetch(`/api/curator-comments/${commentId}/archive`, {
|
||||
method: 'POST',
|
||||
headers: getCuratorAuthHeaders(),
|
||||
});
|
||||
if (res.ok) {
|
||||
// Remove comment from local state (archived comments are not shown)
|
||||
setComments(comments.filter(c => c.id !== commentId));
|
||||
} else {
|
||||
setMessage(t('archiveCommentError'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error archiving comment:', error);
|
||||
setMessage(t('archiveCommentError'));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCuratorInfo = async () => {
|
||||
const res = await fetch('/api/curator/me', {
|
||||
headers: getCuratorAuthHeaders(),
|
||||
@@ -708,9 +721,6 @@ export default function CuratorPageClient() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowComments(!showComments);
|
||||
if (!showComments && comments.length === 0) {
|
||||
fetchComments();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
padding: '0.4rem 0.8rem',
|
||||
@@ -790,9 +800,31 @@ export default function CuratorPageClient() {
|
||||
<div style={{ marginBottom: '0.5rem', fontSize: '0.85rem', color: '#6b7280' }}>
|
||||
{comment.puzzle.song.title} - {comment.puzzle.song.artist}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.9rem', whiteSpace: 'pre-wrap' }}>
|
||||
<div style={{ fontSize: '0.9rem', whiteSpace: 'pre-wrap', marginBottom: '0.5rem' }}>
|
||||
{comment.message}
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.5rem', marginTop: '0.5rem' }}>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm(t('archiveCommentConfirm'))) {
|
||||
archiveComment(comment.id);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
padding: '0.4rem 0.8rem',
|
||||
borderRadius: '0.25rem',
|
||||
border: '1px solid #d1d5db',
|
||||
background: '#fff',
|
||||
color: '#6b7280',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.85rem',
|
||||
}}
|
||||
title={t('archiveComment')}
|
||||
>
|
||||
{t('archiveComment')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -248,7 +248,10 @@
|
||||
"loadCommentsError": "Fehler beim Laden der Kommentare.",
|
||||
"commentFromPuzzle": "Kommentar zu Puzzle",
|
||||
"commentGenre": "Genre",
|
||||
"unreadComment": "Ungelesen"
|
||||
"unreadComment": "Ungelesen",
|
||||
"archiveComment": "Archivieren",
|
||||
"archiveCommentConfirm": "Möchtest du diesen Kommentar wirklich archivieren?",
|
||||
"archiveCommentError": "Fehler beim Archivieren des Kommentars."
|
||||
},
|
||||
"About": {
|
||||
"title": "Über Hördle & Impressum",
|
||||
|
||||
@@ -248,7 +248,10 @@
|
||||
"loadCommentsError": "Error loading comments.",
|
||||
"commentFromPuzzle": "Comment from puzzle",
|
||||
"commentGenre": "Genre",
|
||||
"unreadComment": "Unread"
|
||||
"unreadComment": "Unread",
|
||||
"archiveComment": "Archive",
|
||||
"archiveCommentConfirm": "Do you really want to archive this comment?",
|
||||
"archiveCommentError": "Error archiving comment."
|
||||
},
|
||||
"About": {
|
||||
"title": "About Hördle & Imprint",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "CuratorCommentRecipient" ADD COLUMN "archived" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
@@ -176,6 +176,7 @@ model CuratorCommentRecipient {
|
||||
curatorId Int
|
||||
curator Curator @relation(fields: [curatorId], references: [id], onDelete: Cascade)
|
||||
readAt DateTime?
|
||||
archived Boolean @default(false)
|
||||
|
||||
@@unique([commentId, curatorId])
|
||||
@@index([curatorId])
|
||||
|
||||
Reference in New Issue
Block a user