Skip to content

Add Dynamic Certificate Templates #3073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from 17 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
create type "public"."certificate_templates" as enum ('original', 'modern', 'elegant');

alter table "public"."workspace_courses" add column "cert_template" certificate_templates not null default 'original'::certificate_templates;


8 changes: 7 additions & 1 deletion apps/upskii/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3539,14 +3539,20 @@
"create_description": "Create a new course",
"description": "Manage courses in your workspace that include markdown content, slides, quizzes, and flashcards.",
"course_description": "Course description",
"certificate_template": "Certificate Design",
"select_certificate_template": "Choose a certificate template",
"edit": "Edit course",
"name": "Course name",
"edit_description": "Edit an existing course",
"delete_confirm_title": "Deleting \"{name}\"",
"card_view": "Card View",
"table_view": "Table View",
"no_courses_found": "No courses found",
"no_description_provided": "No description provided"
"no_description_provided": "No description provided",
"image_preview_not_available": "Image Preview Not Available",
"image_preview_not_available_description": "A preview of the certificate template is not available.",
"hide_certificate_preview": "Hide Certificate Preview",
"show_certificate_preview": "Show Certificate Preview"
},
"ws-crawlers": {
"plural": "Crawlers",
Expand Down
8 changes: 7 additions & 1 deletion apps/upskii/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3547,7 +3547,13 @@
"table_view": "Dạng bảng",
"no_courses_found": "Chưa có khóa học nào",
"no_description_provided": "Không có mô tả",
"delete_confirm_title": "Xác nhận xoá \"{name}\""
"delete_confirm_title": "Xác nhận xóa \"{name}\"",
"certificate_template": "Mẫu chứng chỉ",
"select_certificate_template": "Chọn một mẫu chứng chỉ",
"image_preview_not_available": "Chưa có mẫu xem trước",
"image_preview_not_available_description": "Ảnh xem trước của mẫu chứng chỉ không có sẵn.",
"hide_certificate_preview": "Ẩn xem mẫu chứng chỉ",
"show_certificate_preview": "Hiển thị xem mẫu chứng chỉ"
},
"ws-crawlers": {
"plural": "Công cụ cào dữ liệu",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import Certificate from '../certificate-page';
import { ElegantCertificateDocument } from '@/app/api/v1/certificates/templates/elegant-certificate';
import { ModernCertificateDocument } from '@/app/api/v1/certificates/templates/modern-certificate';
import { OGCertificateDocument } from '@/app/api/v1/certificates/templates/og-certificate';

Check warning on line 3 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L2-L3

Added lines #L2 - L3 were not covered by tests
import { getCertificateDetails } from '@/lib/certificate-helper';
import { renderToBuffer } from '@react-pdf/renderer';

Check warning on line 5 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L5

Added line #L5 was not covered by tests
import { createClient } from '@tuturuuu/supabase/next/server';
import { Database } from '@tuturuuu/types/supabase';
import { CertificateViewer } from '@tuturuuu/ui/custom/education/certificates/certificate-viewer';
import type { CertificateData } from '@tuturuuu/ui/custom/education/certificates/types';
import { getLocale, getTranslations } from 'next-intl/server';

Check warning on line 10 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L7-L10

Added lines #L7 - L10 were not covered by tests
import { notFound, redirect } from 'next/navigation';

export type CertificateProps = {
certDetails: {
courseName: string;
studentName: string | null;
courseLecturer: string | null;
completionDate: string;
certificateId: string;
};
wsId: string;
};

interface PageProps {
params: Promise<{
certificateId: string;
Expand All @@ -24,6 +20,7 @@
export default async function CertificatePage({ params }: PageProps) {
const { certificateId, wsId } = await params;
const supabase = await createClient();
const locale = await getLocale();

Check warning on line 23 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L23

Added line #L23 was not covered by tests

// Get current user
const {
Expand All @@ -34,18 +31,58 @@
}

try {
// Validate that the user has access to this certificate and get details

Check warning on line 34 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L34

Added line #L34 was not covered by tests
const certDetails = await getCertificateDetails(
certificateId,
user.id,
wsId
);

// Get translations for PDF generation
const t = await getTranslations({ locale, namespace: 'certificates' });

// Prepare certificate data
const data: CertificateData = {
certData: certDetails,
title: t('title'),
certifyText: t('certify_text'),
completionText: t('completion_text'),
offeredBy: t('offered_by'),
completionDateLabel: t('completion_date'),
certificateIdLabel: t('certificate_id'),
};

const certTemplate: Database['public']['Enums']['certificate_templates'] =
certDetails.certTemplate;
let pdfBuffer: Buffer;
switch (certTemplate) {
case 'elegant':
pdfBuffer = await renderToBuffer(
<ElegantCertificateDocument data={data} />
);
break;
case 'modern':
pdfBuffer = await renderToBuffer(
<ModernCertificateDocument data={data} />
);
break;
default:
pdfBuffer = await renderToBuffer(<OGCertificateDocument data={data} />);
break;
}

// Convert buffer to base64 data URL
const pdfDataUrl = `data:application/pdf;base64,${pdfBuffer.toString('base64')}`;

Check warning on line 76 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L40-L76

Added lines #L40 - L76 were not covered by tests
return (
<>
<Certificate certDetails={certDetails} wsId={wsId} />
</>
<CertificateViewer
certificateId={certificateId}
wsId={wsId}
pdfDataUrl={pdfDataUrl}
/>

Check warning on line 82 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L78-L82

Added lines #L78 - L82 were not covered by tests
);
} catch (error) {
console.error('Error fetching certificate details:', error);
console.error('Error generating certificate:', error);

Check warning on line 85 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/[certificateId]/page.tsx#L85

Added line #L85 was not covered by tests
notFound();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { DownloadButtonPDF } from './[certificateId]/download-button-pdf';
import { CertificatePagination } from './certificate-pagination';
import { getAllCertificatesForUser } from '@/lib/certificate-helper';
import { createClient } from '@tuturuuu/supabase/next/server';
Expand All @@ -12,6 +11,7 @@
CardHeader,
CardTitle,
} from '@tuturuuu/ui/card';
import { DownloadButtonPDF } from '@tuturuuu/ui/custom/education/certificates/download-button-pdf';

Check warning on line 14 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/certificates/page.tsx#L14

Added line #L14 was not covered by tests
import FeatureSummary from '@tuturuuu/ui/custom/feature-summary';
import { Award, Calendar, Eye } from '@tuturuuu/ui/icons';
import { Separator } from '@tuturuuu/ui/separator';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
},
{
id: 'actions',
cell: ({ row }) => <WorkspaceCourseRowActions row={row} />,
cell: ({ row }) => (
<WorkspaceCourseRowActions row={row} enableCerts={true} />
),

Check warning on line 96 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/courses/columns.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/courses/columns.tsx#L94-L96

Added lines #L94 - L96 were not covered by tests
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
description={t('ws-courses.description')}
createTitle={t('ws-courses.create')}
createDescription={t('ws-courses.create_description')}
form={<CourseForm wsId={wsId} />}
form={<CourseForm wsId={wsId} enableCerts={true} />}

Check warning on line 60 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/courses/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/courses/page.tsx#L60

Added line #L60 was not covered by tests
/>
<Separator className="my-4" />

Expand Down
Loading