Merge pull request #18 from OpenSignLabs/staging

update
This commit is contained in:
Raktima
2023-11-16 10:32:15 +05:30
committed by GitHub
11 changed files with 280 additions and 245 deletions
+8 -2
View File
@@ -44,10 +44,16 @@ DO_REGION=us-west
# local storage
USE_LOCAL=FALSE
# Email mailgun config (The app will not initialize if any of these 3 variables are not set) *********************************************************************************************************************
MAILGUN_API_KEY=XXXXX
# Email using Mailgun or SMTP if set enable config (The app will not initialize if any of these variables are not set) *********************************************************************************************************************
MAILGUN_API_KEY=
MAILGUN_DOMAIN=mail.yourdomain.com
MAILGUN_SENDER=postmaster@mail.yourdomain.com
SMTP_ENABLE=
SMTP_HOST=
SMTP_PORT=
SMTP_USER_EMAIL=
SMTP_PASS=
# Base64 encoded PFX or p12 document signing certificate file *********************************************************************************************************************
PFX_BASE64='MIIKLwIBAzCCCeUGCSqGSIb3DQEHAaCCCdYEggnSMIIJzjCCBEIGCSqGSIb3DQEH
+4 -5
View File
@@ -1,8 +1,7 @@
<h1 align="center"><a href='https://www.opensignlabs.com'>OpenSign™</a></h1>
<div align="center">
<h1 align="center"><a href='https://www.opensignlabs.com'><img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/e518cc9c-5de3-47da-950b-f93336b9f14e></a>
</h1><div align="center">
The free and open source alternative to DocuSign
[The free and open source alternative to DocuSign](https://www.opensignlabs.com)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/opensignlabs/opensign.svg)](http://isitmaintained.com/project/opensignlabs/opensign "Average time to resolve an issue")
[![All Contributors](https://img.shields.io/github/all-contributors/opensignlabs/opensign?color=ee8449&style=flat-square)](#contributors)
@@ -23,7 +22,7 @@ The free and open source alternative to DocuSign
<a href="https://www.linkedin.com/company/opensign%E2%84%A2/about/">LinkedIn</a>
## An open-source document e-signing solution
## The open-source document e-signing solution
---
</div>
@@ -5,7 +5,7 @@ async function SendMailv1(request) {
const recipient = request.params.email;
const otp = request.params.otp;
const res = await Parse.Cloud.sendEmail({
from: 'Test user' + ' <' + process.env.MAILGUN_SENDER + '>',
from: 'Test user' + ' <' + process.env.SMTP_ENABLE ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER + '>',
recipient: recipient,
subject: 'Your OpenSign™ OTP',
text: 'This email is a test.',
@@ -1,14 +1,29 @@
import fs from 'node:fs';
import https from 'https';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import https from 'https';
import { createTransport } from 'nodemailer';
const mailgun = new Mailgun(formData);
const mailgunClient = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
});
const mailgunDomain = process.env.MAILGUN_DOMAIN;
let transporterSMTP;
let mailgunClient;
if (process.env.SMTP_ENABLE) {
transporterSMTP = createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: process.env.SMTP_SECURE || true,
auth: {
user: process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS,
},
});
} else {
const mailgun = new Mailgun(formData);
mailgunClient = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
});
}
async function sendmail(req) {
try {
@@ -38,7 +53,8 @@ async function sendmail(req) {
const pdfName = req.params.pdfName && `${req.params.pdfName}.pdf`;
const file = {
filename: pdfName || 'exported.pdf',
data: PdfBuffer, //fs.readFileSync('./exports/exported_file_1223.pdf'),
content: process.env.SMTP_ENABLE ? PdfBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: process.env.SMTP_ENABLE ? undefined : PdfBuffer,
};
// const html = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body style='text-align: center;'> <p style='font-weight: bolder; font-size: large;'>Hello!</p> <p>This is a html checking mail</p><p><button style='background-color: lightskyblue; cursor: pointer; border-radius: 5px; padding: 10px; border-style: solid; border-width: 2px; text-decoration: none; font-weight: bolder; color:blue'>Verify email</button></p></body></html>"
@@ -46,14 +62,60 @@ async function sendmail(req) {
const from = req.params.from || '';
const messageParams = {
from: from + ' <' + process.env.MAILGUN_SENDER + '>',
from:
from + ' <' + process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
attachment: file,
attachments: process.env.SMTP_ENABLE ? [file] : undefined,
attachment: process.env.SMTP_ENABLE ? undefined : file,
};
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
if (!res.err) {
return {
status: 'success',
};
}
} else {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
return {
status: 'success',
};
}
}
}
} else {
const from = req.params.from || '';
const messageParams = {
from:
from + ' <' + process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
};
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
if (!res.err) {
return {
status: 'success',
};
}
} else {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
@@ -62,23 +124,6 @@ async function sendmail(req) {
};
}
}
} else {
const from = req.params.from || '';
const messageParams = {
from: from + ' <' + process.env.MAILGUN_SENDER + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
};
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
return {
status: 'success',
};
}
}
} catch (err) {
console.log('err ', err);
+47 -30
View File
@@ -17,10 +17,11 @@ import S3Adapter from 'parse-server-s3-adapter';
import FSFilesAdapter from 'parse-server-fs-adapter';
import AWS from 'aws-sdk';
import { app as customRoute } from './cloud/customRoute/customApp.js';
import { createTransport } from 'nodemailer';
const spacesEndpoint = new AWS.Endpoint(process.env.DO_ENDPOINT);
// console.log("configuration ", configuration);
if (process.env.USE_LOCAL !== "TRUE") {
if (process.env.USE_LOCAL !== 'TRUE') {
const s3Options = {
bucket: process.env.DO_SPACE, // globalConfig.S3FilesAdapter.bucket,
baseUrl: process.env.DO_BASEURL,
@@ -36,18 +37,31 @@ if (process.env.USE_LOCAL !== "TRUE") {
var fsAdapter = new S3Adapter(s3Options);
} else {
var fsAdapter = new FSFilesAdapter({
"filesSubDirectory": "files" // optional, defaults to ./files
filesSubDirectory: 'files', // optional, defaults to ./files
});
}
let transporterMail;
let mailgunClient;
let mailgunDomain;
if (process.env.MAILGUN_API_KEY) {
if (process.env.SMTP_ENABLE) {
transporterMail = createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: process.env.SMTP_SECURE || true,
auth: {
user: process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS,
},
});
} else if (process.env.MAILGUN_API_KEY) {
const mailgun = new Mailgun(formData);
mailgunClient = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
});
mailgunDomain = process.env.MAILGUN_DOMAIN;
}
@@ -64,36 +78,39 @@ export const config = {
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Open Sign',
allowClientClassCreation: false,
emailAdapter: process.env.MAILGUN_API_KEY
? {
module: 'parse-server-api-mail-adapter',
options: {
// The email address from which emails are sent.
sender: process.env.MAILGUN_SENDER,
// The email templates.
templates: {
// The template used by Parse Server to send an email for password
// reset; this is a reserved template name.
passwordResetEmail: {
subjectPath: './files/password_reset_email_subject.txt',
textPath: './files/password_reset_email.txt',
htmlPath: './files/password_reset_email.html',
emailAdapter:
process.env.SMTP_ENABLE || process.env.MAILGUN_API_KEY
? {
module: 'parse-server-api-mail-adapter',
options: {
// The email address from which emails are sent.
sender: process.env.SMTP_ENABLE ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER,
// The email templates.
templates: {
// The template used by Parse Server to send an email for password
// reset; this is a reserved template name.
passwordResetEmail: {
subjectPath: './files/password_reset_email_subject.txt',
textPath: './files/password_reset_email.txt',
htmlPath: './files/password_reset_email.html',
},
// The template used by Parse Server to send an email for email
// address verification; this is a reserved template name.
verificationEmail: {
subjectPath: './files/verification_email_subject.txt',
textPath: './files/verification_email.txt',
htmlPath: './files/verification_email.html',
},
},
// The template used by Parse Server to send an email for email
// address verification; this is a reserved template name.
verificationEmail: {
subjectPath: './files/verification_email_subject.txt',
textPath: './files/verification_email.txt',
htmlPath: './files/verification_email.html',
apiCallback: async ({ payload, locale }) => {
if (mailgunClient) {
const mailgunPayload = ApiPayloadConverter.mailgun(payload);
await mailgunClient.messages.create(mailgunDomain, mailgunPayload);
} else if (transporterMail) await transporterMail.sendMail(payload);
},
},
apiCallback: async ({ payload, locale }) => {
const mailgunPayload = ApiPayloadConverter.mailgun(payload);
await mailgunClient.messages.create(mailgunDomain, mailgunPayload);
},
},
}
: null,
}
: null,
filesAdapter: fsAdapter,
auth: {
google: {
+20 -4
View File
@@ -18,16 +18,18 @@
"express-sse": "^0.5.3",
"form-data": "^4.0.0",
"jsonschema": "^1.4.1",
"mailgun.js": "^9.0.1",
"mailgun.js": "^9.3.0",
"mongoose": "^7.2.1",
"multer": "^1.4.5-lts.1",
"multer-s3": "^2.10.0",
"node-forge": "^1.3.1",
"node-signpdf": "^1.5.1",
"nodemailer": "^6.9.7",
"openai": "^4.8.0",
"parse": "4.1.0",
"parse-server": "6.3.1",
"parse-server-api-mail-adapter": "^3.0.0",
"parse-server-fs-adapter": "1.0.1",
"parse-server-s3-adapter": "^1.2.0",
"pdf-lib": "^1.16.0",
"pdfkit": "^0.13.0",
@@ -6016,9 +6018,9 @@
}
},
"node_modules/mailgun.js": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-9.0.1.tgz",
"integrity": "sha512-bVZ1zGh5UfQcChqIwIAil99hzoTeB3aHqAUbqmIBLMLcKfx+FF2qvhbc00WtwrFhe5dBQqQw6zPj0QBlqV4nXg==",
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-9.3.0.tgz",
"integrity": "sha512-iRqCglCdi+Q5anFpeRKHiytT/i34E14p/WtTE57VSeq5bATK+zQ8UnpFgPRaqGGTJqyGjqcO9m5YDRRB4/qirw==",
"dependencies": {
"axios": "^1.3.3",
"base-64": "^1.0.0",
@@ -6799,6 +6801,14 @@
"node-forge": "^1.2.1"
}
},
"node_modules/nodemailer": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz",
"integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemon": {
"version": "2.0.22",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
@@ -7403,6 +7413,12 @@
"node": ">=12"
}
},
"node_modules/parse-server-fs-adapter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-server-fs-adapter/-/parse-server-fs-adapter-1.0.1.tgz",
"integrity": "sha512-5IRKAT2QkzHrYrBESY4E8jsV1sl+XVBTKGyUvBPgBR2QzsTTqiyU/lFZD8PAJavA+TH1BLyLTUMZbhZhswNVbQ==",
"deprecated": "use @parse/fs-files-adapter"
},
"node_modules/parse-server-s3-adapter": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/parse-server-s3-adapter/-/parse-server-s3-adapter-1.2.0.tgz",
+3 -2
View File
@@ -27,18 +27,19 @@
"express-sse": "^0.5.3",
"form-data": "^4.0.0",
"jsonschema": "^1.4.1",
"mailgun.js": "^9.0.1",
"mailgun.js": "^9.3.0",
"mongoose": "^7.2.1",
"multer": "^1.4.5-lts.1",
"multer-s3": "^2.10.0",
"node-forge": "^1.3.1",
"node-signpdf": "^1.5.1",
"nodemailer": "^6.9.7",
"openai": "^4.8.0",
"parse": "4.1.0",
"parse-server": "6.3.1",
"parse-server-api-mail-adapter": "^3.0.0",
"parse-server-s3-adapter": "^1.2.0",
"parse-server-fs-adapter": "1.0.1",
"parse-server-s3-adapter": "^1.2.0",
"pdf-lib": "^1.16.0",
"pdfkit": "^0.13.0",
"razorpay": "^2.8.6",
@@ -1,7 +1,15 @@
import React, { useEffect, useState } from "react";
import "./certificate.css";
import opensignLogo from "../../assests/open-sign-logo.png";
import { Page, Text, View, Document, StyleSheet } from "@react-pdf/renderer";
import {
Page,
Text,
View,
Document,
StyleSheet,
Image
} from "@react-pdf/renderer";
function Certificate({ pdfData }) {
const [isMultiSigners, setIsMultiSigners] = useState();
@@ -49,70 +57,34 @@ function Certificate({ pdfData }) {
fontSize: "11px",
marginBottom: "10px",
color: "gray"
},
image: {
width: "71px",
height: "17px"
}
});
const generatedDate = () => {
const newDate = new Date();
const localExpireDate = newDate.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric"
});
var currentOffset = newDate.getTimezoneOffset();
var ISTOffset = 330; // IST offset UTC +5:30
var ISTTime = new Date(
newDate.getTime() + (ISTOffset + currentOffset) * 60000
);
// ISTTime now represents the time in IST coordinates
var hoursIST = ISTTime.getHours();
var minutesIST = ISTTime.getMinutes();
const utcTime = newDate.toUTCString();
return (
<Text
style={{
textAlign: "right",
color: "gray",
fontSize: "10px",
marginBottom: "30px"
fontSize: "10px"
}}
>
Generated On {localExpireDate} {hoursIST}:{minutesIST} IST
Generated On {utcTime}
</Text>
);
};
const changeCompletedDate = () => {
const completedOn = pdfData[0].updatedAt;
const newDate = new Date(completedOn);
const localExpireDate = newDate.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric"
});
const utcTime = newDate.toUTCString();
var currentOffset = newDate.getTimezoneOffset();
var ISTOffset = 330; // IST offset UTC +5:30
var ISTTime = new Date(
newDate.getTime() + (ISTOffset + currentOffset) * 60000
);
// ISTTime now represents the time in IST coordinates
var hoursIST = ISTTime.getHours();
var minutesIST = ISTTime.getMinutes();
return (
<Text style={styles.textStyle2}>
{localExpireDate} {hoursIST}:{minutesIST} IST
</Text>
);
return <Text style={styles.textStyle2}>{utcTime}</Text>;
};
const signerName = (data) => {
@@ -143,7 +115,19 @@ function Certificate({ pdfData }) {
{/** Page defines a single page of content. */}
<Page size="A4" style={styles.page}>
<View style={styles.section1}>
{generatedDate()}
<View
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "30px"
}}
>
<Image src={opensignLogo} style={styles.image} />
{generatedDate()}
</View>
<View style={{ justifyContent: "center" }}>
<Text
style={{
@@ -182,7 +166,9 @@ function Certificate({ pdfData }) {
</Text>
<Text style={styles.textStyle}>
Organization : &nbsp;
<Text style={styles.textStyle2}>__</Text>
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Company}
</Text>
</Text>
<Text style={styles.textStyle}>
Completed on : &nbsp;{changeCompletedDate()}
@@ -627,8 +627,8 @@ function SignYourSelf() {
const bottomY = xyPosData.isDrag
? xyPosData.yBottom * scale - height
: xyPosData.firstYPos
? xyPosData.yBottom * scale - height + xyPosData.firstYPos
: xyPosData.yBottom * scale - height;
? xyPosData.yBottom * scale - height + xyPosData.firstYPos
: xyPosData.yBottom * scale - height;
singleSign = {
pdfFile: pdfBase64Url,
@@ -664,15 +664,7 @@ function SignYourSelf() {
// console.log("json ", json);
setPdfUrl(json.result.data);
if (json.result.data) {
const docStatus = {
isCompleted: true
};
setDocumentStatus(docStatus);
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
getDocumentDetails();
}
})
.catch((err) => {
@@ -82,13 +82,13 @@ function Header({
//handle download signed pdf
const handleDownloadPdf = () => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
};
const sanitizeFileName = (pdfName) => {
// Replace spaces with underscore
return pdfName.replace(/ /g, '_');
}
return pdfName.replace(/ /g, "_");
};
//certificate generate and download component in mobile view
const CertificateDropDown = () => {
//after generate download certifcate pdf
@@ -109,6 +109,7 @@ function Header({
window.URL.revokeObjectURL(url);
}
};
return (
<PDFDownloadLink
onClick={(e) => e.preventDefault()}
@@ -118,7 +119,21 @@ function Header({
{({ blob, url, loading, error }) => (
<>
{loading ? (
"Loading document..."
<div
style={{
border: "none",
backgroundColor: "#fff"
}}
>
<i
className="fa fa-certificate"
style={{
marginRight: "2px"
}}
aria-hidden="true"
></i>
Certificate
</div>
) : (
<div
style={{
@@ -149,6 +164,63 @@ function Header({
</PDFDownloadLink>
);
};
const CertificateComponent = () => {
return (
<PDFDownloadLink
style={{ textDecoration: "none" }}
document={<Certificate pdfData={pdfDetails} />}
fileName={`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`}
>
{({ blob, url, loading, error }) =>
loading ? (
<button
type="button"
className="defaultBtn certificateBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
>
<i
className="fa fa-certificate"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
</button>
) : (
<button
type="button"
className="defaultBtn certificateBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
>
<i
className="fa fa-certificate"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
</button>
)
}
</PDFDownloadLink>
);
};
return (
<div
style={{ padding: !isGuestSigner && "5px 0px 5px 0px" }}
@@ -215,7 +287,7 @@ function Header({
border: "none",
fontWeight: "650",
fontSize: "16px",
padding:"0px 3px 0px 5px"
padding: "0px 3px 0px 5px"
}}
>
<i className="fa fa-ellipsis-v" aria-hidden="true"></i>
@@ -255,10 +327,12 @@ function Header({
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
) :isSignYourself && (
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
) : (
isSignYourself && (
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
)
)}
<DropdownMenu.Item
className="DropdownMenuItem"
@@ -360,40 +434,7 @@ function Header({
pdfUrl || isAlreadySign.mssg ? (
<div style={{ display: "flex", flexDirection: "row" }}>
{pdfDetails[0] && pdfDetails.length > 0 && (
<PDFDownloadLink
style={{ textDecoration: "none" }}
document={<Certificate pdfData={pdfDetails} />}
fileName={`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`}
>
{({ blob, url, loading, error }) =>
loading ? (
"Loading document..."
) : (
<button
type="button"
className="defaultBtn certificateBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
>
<i
className="fa fa-certificate"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
</button>
)
}
</PDFDownloadLink>
<CertificateComponent />
)}
<button
onClick={handleToPrint}
@@ -518,42 +559,7 @@ function Header({
) : isPdfRequestFiles ? (
alreadySign ? (
<div style={{ display: "flex", flexDirection: "row" }}>
{isCompleted.isCertificate && (
<PDFDownloadLink
style={{ textDecoration: "none" }}
document={<Certificate pdfData={pdfDetails} />}
fileName={`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`}
>
{({ blob, url, loading, error }) =>
loading ? (
"Loading document..."
) : (
<button
type="button"
className="defaultBtn certificateBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
>
<i
className="fa fa-certificate"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
</button>
)
}
</PDFDownloadLink>
)}
{isCompleted.isCertificate && <CertificateComponent />}
<button
onClick={handleToPrint}
type="button"
@@ -640,40 +646,7 @@ function Header({
)
) : pdfUrl || (documentStatus && documentStatus.isCompleted) ? (
<div style={{ display: "flex", flexDirection: "row" }}>
<PDFDownloadLink
style={{ textDecoration: "none" }}
document={<Certificate pdfData={pdfDetails} />}
fileName={`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`}
>
{({ blob, url, loading, error }) =>
loading ? (
"Loading document..."
) : (
<button
type="button"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="defaultBtn certificateBtn"
>
<i
className="fa fa-certificate"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
</button>
)
}
</PDFDownloadLink>
<CertificateComponent />
<button
onClick={handleToPrint}
type="button"
@@ -752,4 +725,4 @@ function Header({
);
}
export default Header;
export default Header;
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB