Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 83 additions & 6 deletions src/app/admin/AdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ export default function AdminPanel() {
const [isLoadingOriginalEntry, setIsLoadingOriginalEntry] = useState(false);
const [isSubmissionDetailOpen, setIsSubmissionDetailOpen] = useState(false);

const [isRejectDialogOpen, setIsRejectDialogOpen] = useState(false);
const [rejectionReason, setRejectionReason] = useState("");
const [submissionToReject, setSubmissionToReject] = useState<number | null>(null);

const [lexiconEntriesForDisplay, setLexiconEntriesForDisplay] = useState<
AnyEntry[]
>([]);
Expand Down Expand Up @@ -540,26 +544,48 @@ export default function AdminPanel() {
}
};

const handleRejectSubmission = async (submissionId: number) => {
const submission = userSubmissions.find((s) => s.id === submissionId);
const handleRejectSubmission = (submissionId: number) => {
setSubmissionToReject(submissionId);
setRejectionReason("");
setIsRejectDialogOpen(true);
};

const handleConfirmReject = async () => {
if (!submissionToReject) return;

if (!rejectionReason.trim()) {
toast({
title: "Rejection Reason Required",
description: "Please provide a reason for rejecting this submission.",
variant: "destructive",
});
return;
}

const submission = userSubmissions.find((s) => s.id === submissionToReject);
if (submission) {
try {
await updateSubmissionStatusInDatabase(
submissionId,
submissionToReject,
"rejected",
submission,
rejectionReason.trim(),
);
toast({
title: "Submission Rejected",
description: `Submission ID "${submissionId}" has been rejected.`,
description: `Submission ID "${submissionToReject}" has been rejected.`,
});
await refetchAllData();
if (viewingSubmission?.id === submissionId)
if (viewingSubmission?.id === submissionToReject) {
setIsSubmissionDetailOpen(false);
}
setIsRejectDialogOpen(false);
setSubmissionToReject(null);
setRejectionReason("");
} catch (error) {
toast({
title: "Reject Failed",
description: `Could not reject submission ID "${submissionId}".`,
description: `Could not reject submission ID "${submissionToReject}".`,
variant: "destructive",
});
}
Expand Down Expand Up @@ -1736,6 +1762,57 @@ export default function AdminPanel() {
</DialogFooter>
</DialogContent>
</Dialog>

<Dialog open={isRejectDialogOpen} onOpenChange={setIsRejectDialogOpen}>
<DialogContent className="sm:max-w-[525px]">
<DialogHeader>
<DialogTitle>Reject Submission</DialogTitle>
<DialogDescription>
Please provide a reason for rejecting this submission. This will be
sent to the submitter via email.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="rejection-reason">
Reason for Rejection <span className="text-destructive">*</span>
</Label>
<Textarea
id="rejection-reason"
value={rejectionReason}
onChange={(e) => setRejectionReason(e.target.value)}
placeholder="Please explain why this submission is being rejected. This helps the submitter understand and potentially improve future submissions."
rows={5}
className="resize-none"
/>
<p className="text-xs text-muted-foreground">
Be constructive and specific. The submitter will receive this
feedback via email.
</p>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => {
setIsRejectDialogOpen(false);
setSubmissionToReject(null);
setRejectionReason("");
}}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleConfirmReject}
disabled={!rejectionReason.trim()}
>
<XCircle className="h-4 w-4 mr-2" />
Confirm Rejection
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</PageContainer>
);
}
34 changes: 22 additions & 12 deletions src/app/admin/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ if (process.env.SENDGRID_API_KEY) {
async function sendStatusUpdateNotification(
submission: UserSubmissionBase<any>,
status: "approved" | "rejected",
rejectionReason?: string,
) {
if (!process.env.SENDGRID_API_KEY) {
return;
Expand Down Expand Up @@ -72,7 +73,22 @@ async function sendStatusUpdateNotification(
<p>Hi ${submission.submitterName},</p>
<p>Thank you for your ${isEdit ? "edit suggestion" : "entry suggestion"} for "<strong>${entryName}</strong>".</p>
<p>After careful review, we've decided not to implement this suggestion at this time.</p>
${
rejectionReason
? `
<div style="background-color: #f3f4f6; border-left: 4px solid #ef4444; padding: 12px 16px; margin: 16px 0;">
<p style="margin: 0; font-weight: bold; color: #1f2937;">Feedback from our team:</p>
<p style="margin: 8px 0 0 0; color: #374151;">${rejectionReason.replace(/\n/g, "<br>")}</p>
</div>
`
: ""
}
<p>We appreciate your contribution and encourage you to keep sharing ideas with the F3 community.</p>
<hr style="margin: 24px 0; border: none; border-top: 1px solid #e5e7eb;">
<p style="font-size: 14px; color: #6b7280;">
<strong>Have questions or want to discuss this further?</strong><br>
Feel free to reach out to us at <a href="mailto:${process.env.FROM_EMAIL || "support.codex@f3nation.com"}" style="color: #3b82f6;">${process.env.FROM_EMAIL || "support.codex@f3nation.com"}</a>
</p>
`,
};

Expand All @@ -91,10 +107,9 @@ async function sendStatusUpdateNotification(
<p><strong>Submitter:</strong> ${submission.submitterName} (${submission.submitterEmail})</p>
<p><strong>Submission ID:</strong> ${submission.id}</p>
<br>
${
status === "approved"
? '<p style="color: #22c55e; font-weight: bold;">✅ This entry is now live in the Codex!</p>'
: '<p style="color: #ef4444;">This submission has been declined.</p>'
${status === "approved"
? '<p style="color: #22c55e; font-weight: bold;">✅ This entry is now live in the Codex!</p>'
: '<p style="color: #ef4444;">This submission has been declined.</p>'
}
<hr>
<p style="color: #666; font-size: 12px;">
Expand Down Expand Up @@ -194,18 +209,13 @@ export async function updateSubmissionStatusInDatabase(
id: number,
status: "pending" | "approved" | "rejected",
submission?: UserSubmissionBase<any>,
rejectionReason?: string,
): Promise<void> {
await apiUpdateSubmissionStatusInDatabase(id, status);
await apiUpdateSubmissionStatusInDatabase(id, status, rejectionReason);

// Send email notification if submission data is provided and status is approved/rejected
if (submission && (status === "approved" || status === "rejected")) {
console.log(`Attempting to send ${status} email for submission:`, {
submissionId: id,
submitterEmail: submission.submitterEmail,
submitterName: submission.submitterName,
submissionType: submission.submissionType,
});
await sendStatusUpdateNotification(submission, status);
await sendStatusUpdateNotification(submission, status, rejectionReason);
} else {
}
}
Expand Down
1 change: 0 additions & 1 deletion src/app/api/test-db-connection/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export async function GET(request: Request) {
databaseInfo.tables.push(tableInfo);
}

console.log("API Route: Database schema and data inspection successful!");
return NextResponse.json(
{ success: true, data: databaseInfo },
{ status: 200 },
Expand Down
12 changes: 1 addition & 11 deletions src/app/callback/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ function CallbackContent() {
const state = searchParams.get("state");
const errorParam = searchParams.get("error");

// Debug logging
console.log("Callback debug:", {
code: code ? "present" : "missing",
state: state ? "present" : "missing",
errorParam,
url: window.location.href,
});

if (errorParam) {
setError(`OAuth error: ${errorParam}`);
Expand All @@ -67,10 +60,7 @@ function CallbackContent() {
try {
// Verify state parameter structure (handle cross-browser scenarios)
const storedState = localStorage.getItem("oauth_state");
console.log("State verification:", {
receivedState: state,
storedState: storedState ? "present" : "missing (cross-browser)",
});


// Decode and validate state parameter structure
const decodeState = (state: string) => {
Expand Down
23 changes: 3 additions & 20 deletions src/app/submit/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ async function sendSubmissionNotification(
`,
};

console.log(`Sending confirmation email to: ${submitterEmail}`);
await sgMail.send(submitterMsg);
console.log(`Confirmation email sent successfully to: ${submitterEmail}`);

// Send admin notification to the same email address as FROM_EMAIL (support@f3nation.com)
const adminMsg = {
Expand All @@ -82,13 +80,9 @@ async function sendSubmissionNotification(
`,
};

console.log(
`Sending admin notification email to: ${process.env.FROM_EMAIL}`,
);

await sgMail.send(adminMsg);
console.log(
`Admin notification email sent successfully to: ${process.env.FROM_EMAIL}`,
);

} catch (error) {
console.error("Error sending email notification:", error);
console.error("Email error details:", {
Expand Down Expand Up @@ -220,21 +214,15 @@ export async function searchEntriesByName(
export async function submitNewEntrySuggestion(
submission: NewUserSubmission<NewEntrySuggestionData>,
): Promise<void> {
console.log("[SUBMISSION] Creating new entry submission in database...");
await createSubmissionInDatabase(submission);
console.log("[SUBMISSION] Database entry created successfully");

// Send email notification after successful submission
console.log("[SUBMISSION] Attempting to send email notifications...");
console.log("[SUBMISSION] Submitter email:", submission.submitterEmail);
console.log("[SUBMISSION] Submitter name:", submission.submitterName);
await sendSubmissionNotification(
"new",
submission.data,
submission.submitterEmail,
submission.submitterName,
);
console.log("[SUBMISSION] Email notifications completed");
}

/**
Expand All @@ -245,21 +233,16 @@ export async function submitNewEntrySuggestion(
export async function submitEditEntrySuggestion(
submission: NewUserSubmission<EditEntrySuggestionData>,
): Promise<void> {
console.log("[SUBMISSION] Creating edit entry submission in database...");
await createSubmissionInDatabase(submission);
console.log("[SUBMISSION] Database entry created successfully");

// Send email notification after successful submission
console.log("[SUBMISSION] Attempting to send email notifications...");
console.log("[SUBMISSION] Submitter email:", submission.submitterEmail);
console.log("[SUBMISSION] Submitter name:", submission.submitterName);

await sendSubmissionNotification(
"edit",
submission.data,
submission.submitterEmail,
submission.submitterName,
);
console.log("[SUBMISSION] Email notifications completed");
}

/**
Expand Down
8 changes: 0 additions & 8 deletions src/components/admin/EntryForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,6 @@ export function EntryForm({
type: "lexicon",
} as LexiconEntry);

console.log("📝 EntryForm submitting:", {
type,
videoLink: videoLink,
trimmed: videoLink.trim(),
final: videoLink.trim() || undefined,
entryData: entryData,
});

await onFormSubmit(entryData);
} catch (error) {
setLocalError(
Expand Down
31 changes: 18 additions & 13 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,14 +609,6 @@ export const updateEntryInDatabase = async (
const videoLink =
type === "exicon" ? (entry as ExiconEntry).videoLink || null : null;

console.log("🎥 Updating entry:", {
id,
name,
type,
videoLink,
rawVideoLink: (entry as ExiconEntry).videoLink,
});

const aliasesToStore = Array.isArray(entry.aliases)
? entry.aliases.map((alias) =>
typeof alias === "string" ? { name: alias } : alias,
Expand Down Expand Up @@ -937,7 +929,7 @@ export const fetchPendingSubmissionsFromDatabase = async (): Promise<
const client = await getClient();
try {
const res = await client.query(
"SELECT id, submission_type, data, submitter_name, submitter_email, status, timestamp::text FROM user_submissions WHERE status = 'pending' ORDER BY timestamp ASC",
"SELECT id, submission_type, data, submitter_name, submitter_email, status, timestamp::text, rejection_reason, admin_notes FROM user_submissions WHERE status = 'pending' ORDER BY timestamp ASC",
);
return res.rows.map(
(row: {
Expand All @@ -950,6 +942,8 @@ export const fetchPendingSubmissionsFromDatabase = async (): Promise<
timestamp: any;
description: any;
name: any;
rejection_reason: any;
admin_notes: any;
}) => ({
id: Number(row.id),
submissionType: row.submission_type,
Expand All @@ -960,6 +954,8 @@ export const fetchPendingSubmissionsFromDatabase = async (): Promise<
timestamp: row.timestamp,
description: row.description,
name: row.name,
rejectionReason: row.rejection_reason,
adminNotes: row.admin_notes,
}),
);
} catch (err) {
Expand Down Expand Up @@ -1023,13 +1019,22 @@ export async function createSubmissionInDatabase(
export const updateSubmissionStatusInDatabase = async (
id: number,
status: "pending" | "approved" | "rejected",
rejectionReason?: string,
): Promise<void> => {
const client = await getClient();
try {
const res = await client.query(
"UPDATE user_submissions SET status = $1, updated_at = NOW() WHERE id = $2",
[status, id],
);
// If status is rejected, update with rejection reason; otherwise just update status
const res =
status === "rejected" && rejectionReason
? await client.query(
"UPDATE user_submissions SET status = $1, rejection_reason = $2, updated_at = NOW() WHERE id = $3",
[status, rejectionReason, id],
)
: await client.query(
"UPDATE user_submissions SET status = $1, updated_at = NOW() WHERE id = $2",
[status, id],
);

if (res.rowCount === 0) {
throw new Error(`Submission with ID ${id} not found.`);
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export interface UserSubmissionBase<
timestamp: string;
createdAt?: string;
updatedAt?: string;
rejectionReason?: string; // Admin's reason for rejecting the submission
adminNotes?: string; // Internal admin notes
}

export type UserSubmission = UserSubmissionBase<
Expand Down
Loading