diff --git a/next.config.ts b/next.config.ts index 695141f..ca7989d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -13,6 +13,16 @@ const nextConfig: NextConfig = { }, async headers() { return [ + { + // Allow clipboard access when embedded in iframes (e.g., on f3nation.com) + source: "/:path*", + headers: [ + { + key: "Permissions-Policy", + value: "clipboard-write=(self \"https://f3nation.com\" \"https://www.f3nation.com\")", + }, + ], + }, { source: "/callback/:path*", headers: [ diff --git a/src/app/admin/AdminPanel.tsx b/src/app/admin/AdminPanel.tsx index 7c3ab7e..e79acd8 100644 --- a/src/app/admin/AdminPanel.tsx +++ b/src/app/admin/AdminPanel.tsx @@ -82,6 +82,7 @@ import { import { getOAuthConfig } from "@/lib/auth"; import { TiptapEditor } from "@/components/shared/TiptapEditor"; import { searchEntriesByName } from "@/app/submit/actions"; +import { RichTextDisplay } from "@/components/shared/RichTextDisplay"; interface UserInfo { sub: string; @@ -137,6 +138,8 @@ export default function AdminPanel() { const [isRejectDialogOpen, setIsRejectDialogOpen] = useState(false); const [rejectionReason, setRejectionReason] = useState(""); const [submissionToReject, setSubmissionToReject] = useState(null); + const [adminNotes, setAdminNotes] = useState(""); + const [resolvedMentions, setResolvedMentions] = useState>({}); const [lexiconEntriesForDisplay, setLexiconEntriesForDisplay] = useState< AnyEntry[] @@ -469,6 +472,29 @@ export default function AdminPanel() { setEditedSubmissionData(JSON.parse(JSON.stringify(submission.data))); // Deep clone setIsEditingSubmission(false); setOriginalEntryForEditView(null); + + // Load mentioned entries for proper display + const mentionedEntryIds = submission.submissionType === "new" + ? (submission.data as NewEntrySuggestionData).mentionedEntries || [] + : (submission.data as EditEntrySuggestionData).changes.mentionedEntries || []; + + if (mentionedEntryIds.length > 0) { + const resolved: Record = {}; + for (const entryId of mentionedEntryIds) { + try { + const entry = await fetchEntryById(entryId); + if (entry) { + resolved[entryId] = entry; + } + } catch (error) { + console.error(`Error loading mentioned entry ${entryId}:`, error); + } + } + setResolvedMentions(resolved); + } else { + setResolvedMentions({}); + } + if (submission.submissionType === "edit") { setIsLoadingOriginalEntry(true); try { @@ -490,6 +516,9 @@ export default function AdminPanel() { }; const handleStartEditingSubmission = () => { + if (viewingSubmission) { + setAdminNotes(viewingSubmission.adminNotes || ""); + } setIsEditingSubmission(true); }; @@ -498,6 +527,7 @@ export default function AdminPanel() { setEditedSubmissionData( JSON.parse(JSON.stringify(viewingSubmission.data)), ); + setAdminNotes(viewingSubmission.adminNotes || ""); } setIsEditingSubmission(false); }; @@ -507,6 +537,7 @@ export default function AdminPanel() { setViewingSubmission({ ...viewingSubmission, data: editedSubmissionData, + adminNotes: adminNotes, }); } setIsEditingSubmission(false); @@ -1066,7 +1097,7 @@ export default function AdminPanel() { {isEditingSubmission - ? "Make any necessary changes before approving" + ? "Edit any field and optionally add a message for the user" : "Reviewing"}{" "} {viewingSubmission?.submissionType === "new" ? "new entry suggestion" @@ -1284,13 +1315,10 @@ export default function AdminPanel() { Description -
@@ -1396,131 +1424,123 @@ export default function AdminPanel() { <> {isEditingSubmission && editedSubmissionData ? (
- {(editedSubmissionData as EditEntrySuggestionData) - .changes.name !== undefined && ( -
- - - setEditedSubmissionData({ - ...editedSubmissionData, - changes: { - ...( - editedSubmissionData as EditEntrySuggestionData - ).changes, - name: e.target.value, - }, - } as EditEntrySuggestionData) - } - /> -
- )} - {(editedSubmissionData as EditEntrySuggestionData) - .changes.description !== undefined && ( -
- - - setEditedSubmissionData({ - ...editedSubmissionData, - changes: { - ...( - editedSubmissionData as EditEntrySuggestionData - ).changes, - description: html, - }, - } as EditEntrySuggestionData) - } - onMentionsChange={(mentions) => { - setEditedSubmissionData({ - ...editedSubmissionData, - changes: { - ...( - editedSubmissionData as EditEntrySuggestionData - ).changes, - mentionedEntries: mentions.map((m) => m.id), - }, - } as EditEntrySuggestionData); - }} - searchEntries={searchEntriesByName} - placeholder="Edit description..." - /> -
- )} - {(editedSubmissionData as EditEntrySuggestionData) - .changes.aliases !== undefined && ( +

+ You can edit any field below, not just the user's suggested changes. Fields marked with * were suggested by the user. +

+ + {/* Name - always shown */} +
+ + + setEditedSubmissionData({ + ...editedSubmissionData, + changes: { + ...(editedSubmissionData as EditEntrySuggestionData).changes, + name: e.target.value, + }, + } as EditEntrySuggestionData) + } + /> +
+ + {/* Description - always shown */} +
+ + + setEditedSubmissionData({ + ...editedSubmissionData, + changes: { + ...(editedSubmissionData as EditEntrySuggestionData).changes, + description: html, + }, + } as EditEntrySuggestionData) + } + onMentionsChange={(mentions) => { + setEditedSubmissionData({ + ...editedSubmissionData, + changes: { + ...(editedSubmissionData as EditEntrySuggestionData).changes, + mentionedEntries: mentions.map((m) => m.id), + }, + } as EditEntrySuggestionData); + }} + searchEntries={searchEntriesByName} + placeholder="Edit description..." + /> +
+ + {/* Aliases - always shown */} +
+ + a.name).join(", ") + } + onChange={(e) => + setEditedSubmissionData({ + ...editedSubmissionData, + changes: { + ...(editedSubmissionData as EditEntrySuggestionData).changes, + aliases: e.target.value + .split(",") + .map((a) => a.trim()) + .filter(Boolean), + }, + } as EditEntrySuggestionData) + } + /> +
+ + {/* Tags - for exicon only, always shown */} + {originalEntryForEditView.type === "exicon" && (
-
- )} - {originalEntryForEditView.type === "exicon" && - (editedSubmissionData as EditEntrySuggestionData) - .changes.tags !== undefined && ( -
- -
- {tags.map((tag) => ( +
+ {tags.map((tag) => { + const currentTags = (editedSubmissionData as EditEntrySuggestionData).changes.tags !== undefined + ? (editedSubmissionData as EditEntrySuggestionData).changes.tags || [] + : (originalEntryForEditView as ExiconEntry).tags.map(t => t.name); + + return (
{ - const currentTags = - ( - editedSubmissionData as EditEntrySuggestionData - ).changes.tags || []; const newTags = checked ? [...currentTags, tag.name] - : currentTags.filter( - (t) => t !== tag.name, - ); + : currentTags.filter((t) => t !== tag.name); setEditedSubmissionData({ ...editedSubmissionData, changes: { - ...( - editedSubmissionData as EditEntrySuggestionData - ).changes, + ...(editedSubmissionData as EditEntrySuggestionData).changes, tags: newTags, }, } as EditEntrySuggestionData); @@ -1533,39 +1553,57 @@ export default function AdminPanel() { {tag.name}
- ))} -
-
- )} - {originalEntryForEditView.type === "exicon" && - (editedSubmissionData as EditEntrySuggestionData) - .changes.videoLink !== undefined && ( -
- - - setEditedSubmissionData({ - ...editedSubmissionData, - changes: { - ...( - editedSubmissionData as EditEntrySuggestionData - ).changes, - videoLink: e.target.value, - }, - } as EditEntrySuggestionData) - } - placeholder="https://youtube.com/watch?v=..." - /> + ); + })}
- )} +
+ )} + + {/* Video Link - for exicon only, always shown */} + {originalEntryForEditView.type === "exicon" && ( +
+ + + setEditedSubmissionData({ + ...editedSubmissionData, + changes: { + ...(editedSubmissionData as EditEntrySuggestionData).changes, + videoLink: e.target.value, + }, + } as EditEntrySuggestionData) + } + placeholder="https://youtube.com/watch?v=..." + /> +
+ )} + + {/* Admin Notes */} + +
+ +

+ Add a message to communicate with the user about any additional changes you made or feedback on their submission. +

+