From f094e035804c194a6850cebe7a064e81d3586607 Mon Sep 17 00:00:00 2001 From: VK-RED Date: Sat, 11 May 2024 12:30:37 +0530 Subject: [PATCH] Create userCert hook to render certificate on browser --- src/hooks/useCert.tsx | 286 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 src/hooks/useCert.tsx diff --git a/src/hooks/useCert.tsx b/src/hooks/useCert.tsx new file mode 100644 index 000000000..22e5bc408 --- /dev/null +++ b/src/hooks/useCert.tsx @@ -0,0 +1,286 @@ +'use client'; + +import { createCertificate } from '@/actions/certificate/createCertificate'; +import { getUserCert } from '@/actions/certificate/getUserCert'; +import { checkCoursePurchased } from '@/actions/purchases/checkCoursePurchased'; +import { useSession } from 'next-auth/react'; +import { PDFDocument, rgb } from 'pdf-lib'; +import { useEffect, useState } from 'react'; + +const getTextProperties = (certificateId: string, userName: string) => { + const hostname = window.location.hostname; + return [ + { text: 'CERTIFICATE', fontSize: 80, offsetY: 450 }, + { text: 'OF COMPLETION', fontSize: 35, offsetY: 400 }, + { + text: 'This certificate is proudly presented to', + fontSize: 30, + offsetY: 100, + }, + { text: userName, fontSize: 65, offsetY: 0 }, + { + text: "For Successfully Completing the '0-1' cohort offered by 100xdevs", + fontSize: 30, + offsetY: -100, + }, + { text: 'HARKIRAT SINGH', fontSize: 30, offsetY: -400 }, + { text: 'Instructor', fontSize: 25, offsetY: -450 }, + { text: `Certificate id: ${certificateId}`, fontSize: 20, offsetY: -500 }, + { + text: `Verify at: https://${hostname}/certificate/verify/${certificateId}`, + fontSize: 20, + offsetY: -530, + }, + ]; +}; + +export function useCertPdf(certificateId?: string, courseId?: string) { + const { status, data: session } = useSession(); + const [pdfData, setPdfData] = useState(null); + const [pdfClicked, setPdfClicked] = useState(false); + const [certImage] = useState(new Image()); + const [signImage] = useState(new Image()); + const [generatingPdf, setGeneratingPdf] = useState(false); + useEffect(() => { + if (!certImage.src) { + certImage.src = '/certiTemplate.png'; + } + if (!signImage.src) { + signImage.src = '/sign.png'; + } + async function generatePdfData() { + setGeneratingPdf(true); + if (certificateId) { + const certificate = await getUserCert(certificateId); + + if (!certificate) { + console.error('No Certificate found , Enter valid CertificateId'); + } else { + const data = await generatePdf( + certificateId, + session?.user?.name || '', + certImage, + signImage, + ); + setPdfData(data.pdfDataUri); + } + } else if (courseId) { + if (status === 'authenticated') { + //check if the user has purchased this courseId + const isCoursePurchased = await checkCoursePurchased(courseId); + if (isCoursePurchased) { + // create a new Cert in the D.B + const { message, newCert } = await createCertificate(courseId); + if (newCert) { + const data = await generatePdf( + newCert.id, + session.user?.name || '', + certImage, + signImage, + ); + setPdfData(data.pdfDataUri); + } else { + console.error(message); + } + } else { + console.error('Cannot find the course'); + } + } + } else { + console.error('Give Either CertificateId or CourseId'); + } + setGeneratingPdf(false); + } + if (pdfClicked) { + generatePdfData(); + } + }, [pdfClicked]); + + return { pdfData, setPdfClicked, generatingPdf }; +} + +const generatePdf = async ( + certificateId: string, + userName: string, + certImage: HTMLImageElement, + signImage: HTMLImageElement, +) => { + const textProperties = getTextProperties(certificateId, userName); + + const certData = getBase64String( + certImage, + certImage.width, + certImage.height, + ); + const signData = getBase64String( + signImage, + signImage.width, + signImage.height, + ); + + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage([2000, 1545]); + + const image = await pdfDoc.embedPng(certData); + + const { width: imgWidth, height: imgHeight } = image.scaleToFit( + page.getWidth(), + page.getHeight(), + ); + page.drawImage(image, { + x: page.getWidth() / 2 - imgWidth / 2, + y: page.getHeight() / 2 - imgHeight / 2, + width: imgWidth, + height: imgHeight, + }); + const font = await pdfDoc.embedFont('Times-Roman'); + for (const { text, fontSize, offsetY } of textProperties) { + const textWidth = font.widthOfTextAtSize(text, fontSize); + const textX = (page.getWidth() - textWidth) / 2; + const textY = page.getHeight() / 2 + offsetY; + page.drawText(text, { + x: textX, + y: textY, + size: fontSize, + color: rgb(0, 0, 0), + font, + }); + } + + const sign = await pdfDoc.embedPng(signData); + const { width: signWidth, height: signHeight } = sign.scaleToFit( + page.getWidth() * 0.5, + page.getHeight() * 0.1, + ); + page.drawImage(sign, { + x: page.getWidth() / 2 - signWidth / 2, + y: page.getHeight() / 2 - 350, + width: signWidth, + height: signHeight, + }); + + const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true }); + return { pdfDataUri }; +}; + +export function useCertPng(certificateId?: string, courseId?: string) { + const { status, data: session } = useSession(); + const [imgUri, setImgUri] = useState(null); + const [imgClicked, setImgClicked] = useState(false); + const [generatingImg, setGeneratingImg] = useState(false); + useEffect(() => { + async function generateImgData() { + setGeneratingImg(true); + if (certificateId) { + const certificate = await getUserCert(certificateId); + + if (!certificate) { + console.error('No Certificate found , Enter valid CertificateId'); + } else { + const data = await generateImg( + session?.user?.name || '', + certificateId, + ); + setImgUri(data); + } + } else if (courseId) { + if (status === 'authenticated') { + //check if the user has purchased this courseId + const isCoursePurchased = await checkCoursePurchased(courseId); + if (isCoursePurchased) { + // create a new Cert in the D.B + const { message, newCert } = await createCertificate(courseId); + if (newCert) { + const data = await generateImg( + session?.user?.name || '', + newCert.id, + ); + setImgUri(data); + } else { + console.error(message); + } + } else { + console.error('Cannot find the course'); + } + } + } else { + console.error('Give Either CertificateId or CourseId'); + } + setGeneratingImg(false); + } + if (imgClicked) { + generateImgData(); + } + }, [imgClicked]); + return { imgUri, setImgClicked, generatingImg }; +} + +const generateImg = async ( + userName: string, + certificateId: string, +): Promise => { + return new Promise((res, rej) => { + const offscreenCanvas = document.createElement('canvas'); + const ctx = offscreenCanvas.getContext('2d'); + + const certificateImage = new Image(); + certificateImage.src = '/certiTemplate.png'; + + const signImage = new Image(); + signImage.src = '/sign.png'; + + certificateImage.onload = () => { + // Set the off-screen canvas dimensions to match the loaded image + offscreenCanvas.width = certificateImage.width; + offscreenCanvas.height = certificateImage.height; + + if (ctx) { + ctx.drawImage( + certificateImage, + 0, + 0, + offscreenCanvas.width, + offscreenCanvas.height, + ); + const textProperties = getTextProperties(certificateId, userName); + + //Fill Certificate Contents + for (let i = 0; i < textProperties.length; i++) { + const { text, fontSize, offsetY } = textProperties[i]; + ctx.font = `${fontSize}px Times Roman`; + if (i === 0 || text === userName) { + ctx.font = `bold ${fontSize}px Times Roman`; + } + const textWidth = ctx.measureText(`${text}`).width; + const textX = (offscreenCanvas.width - textWidth) / 2; + const textY = offscreenCanvas.height / 2 - offsetY; + ctx.fillText(`${text}`, textX, textY); + } + //Fill sign + const signWidth = offscreenCanvas.width / 2 - signImage.width / 2; + const signHeight = offscreenCanvas.height / 2 + 180; + ctx.drawImage(signImage, signWidth, signHeight); + + const dataUrl = offscreenCanvas.toDataURL(); + res(dataUrl); + } else { + rej('Error creating off-screen canvas context'); + } + }; + + certificateImage.onerror = () => { + rej('Error loading certificate image'); + }; + }); +}; + +export const getBase64String = (image: any, width: number, height: number) => { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(image, 0, 0); + + const base64String = canvas.toDataURL('image/png'); + return base64String; +};