parent
fad187b862
commit
f7130b42da
5 changed files with 225 additions and 20 deletions
@ -0,0 +1,118 @@ |
|||||||
|
import { useRef, useState } from 'react'; |
||||||
|
import { Modal } from '../Modal'; |
||||||
|
import { DateTime } from 'luxon'; |
||||||
|
import { Loader2 } from 'lucide-react'; |
||||||
|
|
||||||
|
type CertificateModalProps = { |
||||||
|
onClose: () => void; |
||||||
|
userName: string; |
||||||
|
courseName: string; |
||||||
|
lessonsCount: number; |
||||||
|
quizzesCount: number; |
||||||
|
challengesCount: number; |
||||||
|
issuedDate: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function CertificateModal(props: CertificateModalProps) { |
||||||
|
const { |
||||||
|
userName, |
||||||
|
courseName, |
||||||
|
lessonsCount, |
||||||
|
quizzesCount, |
||||||
|
challengesCount, |
||||||
|
onClose, |
||||||
|
issuedDate, |
||||||
|
} = props; |
||||||
|
|
||||||
|
const certificateRef = useRef<HTMLDivElement>(null); |
||||||
|
const issuedAt = DateTime.fromISO(issuedDate).toFormat('MMMM dd, yyyy'); |
||||||
|
const [isLoading, setIsLoading] = useState(false); |
||||||
|
|
||||||
|
const handleDownloadCertificate = async () => { |
||||||
|
if (!certificateRef.current) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
setIsLoading(true); |
||||||
|
const certificate = certificateRef.current; |
||||||
|
const domtoimage = (await import('dom-to-image')).default; |
||||||
|
if (!domtoimage) { |
||||||
|
throw new Error('Unable to download image'); |
||||||
|
} |
||||||
|
|
||||||
|
const scale = 4; |
||||||
|
const dataUrl = await domtoimage.toJpeg(certificate, { |
||||||
|
height: certificate.offsetHeight * scale, |
||||||
|
width: certificate.offsetWidth * scale, |
||||||
|
bgcolor: 'white', |
||||||
|
style: { |
||||||
|
transform: 'scale(' + scale + ')', |
||||||
|
transformOrigin: 'top left', |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const link = document.createElement('a'); |
||||||
|
link.download = 'certificate.jpg'; |
||||||
|
link.href = dataUrl; |
||||||
|
link.click(); |
||||||
|
setIsLoading(false); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Modal |
||||||
|
onClose={onClose} |
||||||
|
wrapperClassName="max-w-3xl" |
||||||
|
bodyClassName="overflow-hidden bg-transparent shadow-none" |
||||||
|
> |
||||||
|
<div className="rounded-xl bg-white text-black" ref={certificateRef}> |
||||||
|
<div className="flex w-full items-center justify-between gap-2 bg-black p-2 text-white"> |
||||||
|
<div className="flex items-center gap-1 font-medium"> |
||||||
|
<img src="/images/brand.svg" alt="roadmap.sh" className="size-7" /> |
||||||
|
roadmap.sh |
||||||
|
</div> |
||||||
|
|
||||||
|
<span className="text-xs"> |
||||||
|
Issued on <span className="font-medium">{issuedAt}</span> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="certificate-bg flex flex-col items-center py-14 font-mono text-gray-600"> |
||||||
|
<span>Certificate of Completion</span> |
||||||
|
<h3 className="my-2 font-[balsamiq] text-4xl font-medium text-black"> |
||||||
|
{userName} |
||||||
|
</h3> |
||||||
|
<span>Complete a course on roadmap.sh</span> |
||||||
|
|
||||||
|
<h3 className="mt-10 font-[balsamiq] text-5xl font-bold text-black"> |
||||||
|
{courseName} |
||||||
|
</h3> |
||||||
|
|
||||||
|
<div className="mt-4 flex items-center gap-2"> |
||||||
|
<span>{lessonsCount} lessons</span> |
||||||
|
<span>-</span> |
||||||
|
<span>{quizzesCount} quizzes</span> |
||||||
|
<span>-</span> |
||||||
|
<span>{challengesCount} challenge</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mt-20 flex w-full flex-col items-center justify-center"> |
||||||
|
<span className="font-[balsamiq] text-2xl text-black underline underline-offset-[6px]"> |
||||||
|
Kamran Ahmed |
||||||
|
</span> |
||||||
|
<h4 className="text-base font-medium text-black">Kamran Ahmed</h4> |
||||||
|
<span className="text-xs">Founder, roadmap.sh</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button |
||||||
|
className="absolute bottom-4 right-4 flex items-center gap-1 rounded-lg bg-black px-2 py-1 text-white disabled:cursor-progress" |
||||||
|
onClick={handleDownloadCertificate} |
||||||
|
disabled={isLoading} |
||||||
|
> |
||||||
|
{isLoading && <Loader2 className="size-4 animate-spin stroke-[2.5]" />} |
||||||
|
Download Certificate |
||||||
|
</button> |
||||||
|
</Modal> |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue