Skip to content
Open
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
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}
86 changes: 86 additions & 0 deletions app/(main)/profile/events/viewCertificates/[id]/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import fs from "fs";
import path from "path";
import Link from "next/link";

function getCertificates(eventId) {
const certDir = path.join(process.cwd(), "public", "certificates", eventId);

if (!fs.existsSync(certDir)) {
return [];
}

return fs
.readdirSync(certDir)
.filter((file) => /\.(png|jpe?g|pdf)$/i.test(file))
.sort((a, b) => a.localeCompare(b))
.map((file) => ({
name: file,
url: `/certificates/${eventId}/${file}`,
isImage: /\.(png|jpe?g)$/i.test(file),
}));
}

export default async function Page({ params }) {
const { id } = await params;
const certificates = getCertificates(id);

return (
<main style={{ padding: 24, maxWidth: 1100, margin: "0 auto" }}>
<div style={{ marginBottom: 20 }}>
<Link href="/profile/certificates">← Back to Certificates</Link>
</div>

<h1 style={{ marginBottom: 8 }}>Certificates for event</h1>
<p style={{ marginTop: 0, marginBottom: 24 }}>
Event ID: <strong>{id}</strong>
</p>

{certificates.length === 0 ? (
<div
style={{
padding: 20,
border: "1px solid #e5e7eb",
borderRadius: 12,
background: "#fff7ed",
}}
>
No certificates have been generated for this event yet.
</div>
) : (
<div style={{ display: "grid", gap: 20 }}>
{certificates.map((certificate) => (
<div
key={certificate.name}
style={{
border: "1px solid #e5e7eb",
borderRadius: 12,
padding: 16,
background: "#fff",
}}
>
<h3 style={{ marginTop: 0, marginBottom: 12 }}>{certificate.name}</h3>

{certificate.isImage ? (
<img
src={certificate.url}
alt={certificate.name}
style={{
display: "block",
maxWidth: "100%",
maxHeight: 700,
borderRadius: 8,
border: "1px solid #f1f5f9",
}}
/>
) : (
<a href={certificate.url} target="_blank" rel="noreferrer">
Open PDF
</a>
)}
</div>
))}
</div>
)}
</main>
);
}
11 changes: 11 additions & 0 deletions app/(main)/profile/events/viewCertificates/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Link from "next/link";

export default function Page() {
return (
<main style={{ padding: 24 }}>
<h1>Certificate viewer</h1>
<p>Select an event from the certificate list to view its generated files.</p>
<Link href="/profile/certificates">Back to Certificates</Link>
</main>
);
}
23 changes: 8 additions & 15 deletions app/api/certificate/addCertificateTemplate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { addCertificateTemplate } from "@/lib/services/certificates";
import { body, expressError, handle, json } from "@/lib/api/express";
import { getCurrentUser, isAdmin } from "@/lib/auth/access";

/**
* POST /api/certificate/addCertificateTemplate
* Port of controllers/certificate/certificateController.js — admin only.
*/
export async function POST(request: Request) {
return handle(async () => {
const user = await getCurrentUser();
Expand All @@ -15,21 +11,18 @@ export async function POST(request: Request) {
const b = await body<{
eventId?: string;
template?: string;
fields?: unknown;
fields?: unknown[];
}>(request);

const fields = Array.isArray(b.fields)
? b.fields
: typeof b.fields === "string"
? JSON.parse(b.fields)
: [];
if (!b.eventId) return expressError(400, "Event ID is required");
if (!b.template) return expressError(400, "A template image is required");

const record = await addCertificateTemplate({
eventId: b.eventId ?? "",
template: b.template ?? "",
fields,
const data = await addCertificateTemplate({
eventId: b.eventId,
template: b.template,
fields: (b.fields ?? []) as any,
});

return json({ success: true, message: "Template saved", certificate: record });
return json({ success: true, data });
});
}
9 changes: 3 additions & 6 deletions app/api/certificate/dummyCertificate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import { dummyCertificate } from "@/lib/services/certificates";
import { body, expressError, handle, json } from "@/lib/api/express";
import { getCurrentUser, isAdmin } from "@/lib/auth/access";

/**
* POST /api/certificate/dummyCertificate
* Port of controllers/certificate/testNameController.js — preview data for the
* admin certificate designer.
*/
export async function POST(request: Request) {
return handle(async () => {
const user = await getCurrentUser();
Expand All @@ -18,8 +13,10 @@ export async function POST(request: Request) {
fieldValues?: Record<string, string>;
}>(request);

if (!b.eventId) return expressError(400, "Event ID is required");

const data = await dummyCertificate({
eventId: b.eventId ?? "",
eventId: b.eventId,
fieldValues: b.fieldValues,
});

Expand Down
28 changes: 28 additions & 0 deletions app/api/certificate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { dummyCertificate } from "@/lib/services/certificates";
import { body, expressError, handle, json } from "@/lib/api/express";
import { getCurrentUser, isAdmin } from "@/lib/auth/access";

/**
* POST /api/certificate/dummyCertificate
* Port of controllers/certificate/testNameController.js — preview data for the
* admin certificate designer.
*/
export async function POST(request: Request) {
return handle(async () => {
const user = await getCurrentUser();
if (!user) return expressError(401, "Token is required");
if (!isAdmin(user)) return expressError(403, "Unauthorized");

const b = await body<{
eventId?: string;
fieldValues?: Record<string, string>;
}>(request);

const data = await dummyCertificate({
eventId: b.eventId ?? "",
fieldValues: b.fieldValues,
});

return json({ success: true, ...data });
});
}
17 changes: 16 additions & 1 deletion app/api/certificate/sendCertificatesAndEvents/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export async function POST(request: Request) {
eventId?: string;
recipients?: Array<{ email: string; fieldValues?: Record<string, string> }>;
emails?: string[];
resend?: boolean;
}>(request);

// Accepts either a rich recipient list or a plain array of addresses.
Expand All @@ -29,8 +30,22 @@ export async function POST(request: Request) {
const data = await sendCertificatesAndEvents({
eventId: b.eventId ?? "",
recipients,
resend: b.resend === true,
});

return json({ success: true, message: "Certificates processed", data });
const status = data.failures.length > 0 ? 207 : 200;
return json(
{
success: data.failures.length === 0,
message:
data.failures.length > 0
? "Some certificates could not be emailed"
: "Certificates sent successfully",
data,
failed: data.failures,
},
status,
);
});
}

5 changes: 4 additions & 1 deletion app/api/certificate/testCertificateSending/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ export async function POST(request: Request) {
name: b.name ?? user.name ?? to,
eventName: b.eventName ?? "a FED KIIT event",
certificateId: b.certificateId ?? "TEST-CERTIFICATE",
isTest: true,
});

if (!result.sent) return expressError(502, "Could not send the test email");
if (!result.sent) {
return expressError(502, result.reason || "Could not send the test email");
}

return json({ success: true, message: `Test certificate sent to ${to}` });
});
Expand Down
24 changes: 24 additions & 0 deletions app/api/certificates/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";

export async function GET(_req: Request, context: { params: Promise<{ id: string }> | { id: string } }) {
try {
const params = await (context as any).params;
const id = params?.id;
if (!id) return NextResponse.json([], { status: 200 });

const certDir = path.join(process.cwd(), "public", "certificates", id);
if (!fs.existsSync(certDir)) return NextResponse.json([], { status: 200 });

const files = fs
.readdirSync(certDir)
.filter((f) => /\.(png|jpe?g|pdf)$/i.test(f))
.map((name) => ({ name, url: `/certificates/${id}/${name}` }));

return NextResponse.json(files, { status: 200 });
} catch (err) {
console.error("cert api err", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
26 changes: 26 additions & 0 deletions app/api/certificates/counts/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";

export async function POST(req: Request) {
try {
const body = await req.json();
const ids: string[] = Array.isArray(body?.ids) ? body.ids : [];

const result: Record<string, number> = {};
for (const id of ids) {
const certDir = path.join(process.cwd(), "public", "certificates", id);
if (!fs.existsSync(certDir)) {
result[id] = 0;
continue;
}
const files = fs.readdirSync(certDir).filter((f) => /\.(png|jpe?g|pdf)$/i.test(f));
result[id] = files.length;
}

return NextResponse.json(result, { status: 200 });
} catch (err) {
console.error("cert counts err", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
5 changes: 5 additions & 0 deletions app/profile/events/SendCertificate/[id]/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import SendCertificate from "@/src/sections/Profile/Admin/Form/CertificatesForm/SendCertificate";

export default function Page() {
return <SendCertificate />;
}
5 changes: 5 additions & 0 deletions app/profile/events/createCertificates/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import CertificatesForm from "@/src/sections/Profile/Admin/Form/CertificatesForm/CertificatesForm";

export default function Page() {
return <CertificatesForm />;
}
Loading