* feat: add README for @cio/db package and remove existingAccounts.json * load session, load profile and organization, add create profile hook * chore: cache session request The trick was passing all the cookies to the backend. Previously I was only passing the better-auth.session_token and not better-auth.session_data. Read about the difference here https://www.better-auth.com/docs/concepts/cookies#custom-cookies * feat: login, logout, onboarding, verify email, reset password. * feat: complete change email and email verification * feat: standardise how we make rpc calls and create org endpoints * fix ui errors * fix: handle potential undefined customerId and standardize avatar URL property * refactor: standardize custom domain handling and update telegram chat ID references * chore: add tailwind prefix to @cio/ui this fixed the media query issues because @cio/ui classes were overriding this dashboard tailwind build * chore: clean up org sidebar * chore: persist mode state * feat: implement order breadcrumbs * chore: upgrade to better auth 1.4 * chore: address common auth flow bugs and setup app for others to setup locally.
115 lines
2.7 KiB
TypeScript
115 lines
2.7 KiB
TypeScript
import type { TCertificateDownload } from '@cio/utils/validation/course';
|
|
import { getCloudflarePdfBuffer } from '@api/utils/cloudflare';
|
|
|
|
export interface CertificateGenerationResult {
|
|
success: boolean;
|
|
buffer?: Buffer;
|
|
error?: string;
|
|
}
|
|
|
|
export const generateCertificate = async (data: TCertificateDownload) => {
|
|
const { html, styles } = generateCertificateHtml(data);
|
|
return getCloudflarePdfBuffer(html, styles);
|
|
};
|
|
|
|
function generateCertificateHtml(data: TCertificateDownload): { html: string; styles: string } {
|
|
const { studentName, courseName, courseDescription, orgLogoUrl, orgName, facilitator } = data;
|
|
|
|
const html = `
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Certificate of Completion</title>
|
|
</head>
|
|
<body>
|
|
<div class="certificate">
|
|
<div class="header">
|
|
<img src="${orgLogoUrl}" alt="${orgName} Logo" class="logo">
|
|
<h1 class="title">Certificate of Completion</h1>
|
|
</div>
|
|
<div class="content">
|
|
<h2 class="student-name">${studentName}</h2>
|
|
<p class="course-name">${courseName}</p>
|
|
<p class="description">${courseDescription}</p>
|
|
</div>
|
|
<div class="footer">
|
|
<div class="signature">
|
|
<p>${facilitator || 'Course Facilitator'}</p>
|
|
<p>${orgName}</p>
|
|
</div>
|
|
<p class="date">${new Date().toLocaleDateString()}</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const styles = `
|
|
body {
|
|
font-family: 'Arial', sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
background-color: #f5f5f5;
|
|
}
|
|
.certificate {
|
|
width: 1123px;
|
|
height: 794px;
|
|
margin: 0 auto;
|
|
background-color: white;
|
|
position: relative;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
|
}
|
|
.header {
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
.logo {
|
|
max-width: 200px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.title {
|
|
font-size: 36px;
|
|
color: #333;
|
|
margin-bottom: 10px;
|
|
}
|
|
.content {
|
|
text-align: center;
|
|
padding: 40px;
|
|
}
|
|
.student-name {
|
|
font-size: 48px;
|
|
color: #2c3e50;
|
|
margin-bottom: 20px;
|
|
}
|
|
.course-name {
|
|
font-size: 24px;
|
|
color: #34495e;
|
|
margin-bottom: 10px;
|
|
}
|
|
.description {
|
|
font-size: 18px;
|
|
color: #7f8c8d;
|
|
margin-bottom: 30px;
|
|
}
|
|
.footer {
|
|
position: absolute;
|
|
bottom: 40px;
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
.signature {
|
|
margin-top: 20px;
|
|
}
|
|
.date {
|
|
margin-top: 10px;
|
|
color: #95a5a6;
|
|
}
|
|
`;
|
|
const pdf = {
|
|
html: html,
|
|
styles: styles
|
|
};
|
|
|
|
return pdf;
|
|
}
|