Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-02-20 10:15:42 +01:00
parent 16503a7df4
commit 1f484fcab0
4 changed files with 147 additions and 149 deletions

View File

@@ -132,7 +132,7 @@ export function NDAPage(props: {
useEffect(() => { useEffect(() => {
if (isCompleted) { if (isCompleted) {
navigate("/overview", { replace: true }); void navigate("/overview", { replace: true });
} }
}, [isCompleted, navigate]); }, [isCompleted, navigate]);

View File

@@ -133,12 +133,13 @@ func (g *CertificateGenerator) Generate(
ctx, ctx,
htmlBuf.Bytes(), htmlBuf.Bytes(),
html2pdf.RenderConfig{ html2pdf.RenderConfig{
PageFormat: html2pdf.PageFormatA4, PageFormat: html2pdf.PageFormatA4,
Orientation: html2pdf.OrientationPortrait, Orientation: html2pdf.OrientationPortrait,
MarginTop: html2pdf.NewMarginMillimeters(20), MarginTop: html2pdf.NewMarginMillimeters(20),
MarginBottom: html2pdf.NewMarginMillimeters(20), MarginBottom: html2pdf.NewMarginMillimeters(20),
MarginLeft: html2pdf.NewMarginMillimeters(20), MarginLeft: html2pdf.NewMarginMillimeters(20),
MarginRight: html2pdf.NewMarginMillimeters(20), MarginRight: html2pdf.NewMarginMillimeters(20),
PrintBackground: true,
}, },
) )
if err != nil { if err != nil {

View File

@@ -3,6 +3,15 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<style> <style>
@page {
@bottom-right {
content: counter(page) "/" counter(pages);
font-family: Arial, Helvetica, sans-serif;
font-size: 8px;
color: #777;
}
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -16,95 +25,55 @@
line-height: 1.4; line-height: 1.4;
} }
/* -- Top bar -- */ /* -- Logo row -- */
.top-bar { .logo-row {
border-top: 4px solid #000;
padding-top: 10px;
margin-bottom: 6px;
display: table;
width: 100%;
}
.top-bar-left {
display: table-cell;
vertical-align: top;
}
.top-bar-right {
display: table-cell;
vertical-align: top;
text-align: right; text-align: right;
width: 120px; padding-bottom: 8px;
} }
.brand { .logo-row svg {
font-size: 14px;
font-weight: 700;
color: #000;
letter-spacing: 0.5px;
}
.cert-title {
font-size: 16px;
font-weight: 700;
font-style: italic;
color: #000;
border-bottom: 2px solid #000;
padding-bottom: 4px;
display: inline-block; display: inline-block;
margin-bottom: 6px; vertical-align: middle;
} }
/* -- Meta line (ID + Status) -- */ /* -- Title bar -- */
.meta-line { .title-bar {
display: table;
width: 100%; width: 100%;
margin-bottom: 2px; border-collapse: collapse;
font-size: 9.5px; margin-bottom: 4px;
} }
.meta-left { .title-bar td {
display: table-cell; padding: 5px 6px;
vertical-align: top; font-size: 12px;
font-weight: 700;
color: #000;
background: #ccc;
} }
.meta-right { /* -- Meta table -- */
display: table-cell; .meta-table {
vertical-align: top;
text-align: right;
}
/* -- Info rows -- */
.info-row {
font-size: 9.5px;
line-height: 1.5;
margin-bottom: 1px;
}
/* -- Info grid (multi-column) -- */
.info-grid {
display: table;
width: 100%; width: 100%;
border-collapse: collapse;
margin-bottom: 2px; margin-bottom: 2px;
} }
.info-col { .meta-table td {
display: table-cell; padding: 2px 6px;
vertical-align: top; vertical-align: top;
font-size: 9.5px; font-size: 9.5px;
line-height: 1.5; line-height: 1.5;
} }
.info-col-left { .meta-table .lbl {
width: 38%; width: 130px;
font-weight: 700;
color: #333;
white-space: nowrap;
} }
.info-col-mid { .meta-table .val {
width: 30%; word-break: break-all;
}
.info-col-right {
width: 32%;
} }
/* -- Section header bar -- */ /* -- Section header bar -- */
@@ -119,8 +88,7 @@
font-size: 9.5px; font-size: 9.5px;
font-weight: 700; font-weight: 700;
color: #000; color: #000;
background: #e8e8e8; background: #ccc;
border-top: 2px solid #000;
} }
/* -- Signer content -- */ /* -- Signer content -- */
@@ -130,15 +98,17 @@
} }
.signer-table td { .signer-table td {
padding: 6px 6px; padding: 3px 6px;
vertical-align: top; vertical-align: top;
font-size: 9.5px; font-size: 9.5px;
line-height: 1.5; line-height: 1.5;
} }
.signer-name { .signer-table .lbl {
width: 130px;
font-weight: 700; font-weight: 700;
font-size: 9.5px; color: #333;
white-space: nowrap;
} }
/* -- Events table -- */ /* -- Events table -- */
@@ -152,8 +122,7 @@
padding: 5px 6px; padding: 5px 6px;
font-weight: 700; font-weight: 700;
color: #000; color: #000;
background: #e8e8e8; background: #ccc;
border-top: 2px solid #000;
} }
.events-table tbody td { .events-table tbody td {
@@ -176,9 +145,10 @@
} }
.detail-table .lbl { .detail-table .lbl {
width: 170px; width: 130px;
font-weight: 700; font-weight: 700;
color: #333; color: #333;
white-space: nowrap;
} }
/* -- Mono -- */ /* -- Mono -- */
@@ -259,74 +229,101 @@
</style> </style>
</head> </head>
<body> <body>
<!-- Top bar with title and brand --> <!-- Probo logo top-right -->
<div class="top-bar"> <div class="logo-row">
<div class="top-bar-left"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" width="90" height="24" viewBox="0 0 90 24">
<div class="cert-title">Certificate Of Completion</div> <g clip-path="url(#clip-a)">
</div> <rect width="24" height="24" x=".85" fill="#1a1a1a" rx="5.14"/>
<div class="top-bar-right"> <path stroke="#ffffff" stroke-width="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66"/>
<div class="brand">probo</div> <path stroke="url(#grad-b)" stroke-width="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66"/>
</div> <path fill="#ffffff" d="M19.07 7.1a1.77 1.77 0 0 0 3-1.86l-1.5.93-1.5.94ZM8.82 25.18l1.71-.41c-2.3-9.67.01-14.66 2.54-16.83A6.25 6.25 0 0 1 17 6.33c1.22-.01 1.87.44 2.08.78l1.5-.94 1.5-.93c-1.1-1.74-3.16-2.46-5.13-2.44-2.03.03-4.26.81-6.17 2.45-3.9 3.35-6.16 9.92-3.67 20.33l1.72-.41Z"/>
</g>
<path fill="#1a1a1a" d="M66.87 0v7.32a4.84 4.84 0 0 1 1.48-.74 5.63 5.63 0 0 1 1.76-.28c1.63 0 3 .55 4.1 1.68a5.5 5.5 0 0 1 1.67 4.06c0 1.6-.56 2.93-1.69 4.06a5.49 5.49 0 0 1-4.08 1.69 5.8 5.8 0 0 1-4.06-1.56 5.48 5.48 0 0 1-1.71-4.27V0h2.53Zm3.24 8.72a3.2 3.2 0 0 0-3.24 3.32 3.18 3.18 0 0 0 3.24 3.32 3.2 3.2 0 0 0 3.24-3.32c0-1.9-1.43-3.32-3.24-3.32ZM52.75 7.98a5.53 5.53 0 0 1 4.08-1.68c1.61 0 2.93.55 4.06 1.68a5.51 5.51 0 0 1 1.69 4.06c0 1.58-.55 2.95-1.69 4.09a5.59 5.59 0 0 1-4.06 1.66 5.64 5.64 0 0 1-4.08-1.66 5.53 5.53 0 0 1-1.69-4.09c0-1.6.56-2.92 1.7-4.06Zm.84 4.06c0 .98.32 1.77.93 2.4.63.6 1.4.92 2.31.92.92 0 1.7-.31 2.3-.92.63-.64.94-1.42.94-2.4 0-.97-.31-1.76-.94-2.37a3.03 3.03 0 0 0-2.3-.95 3.22 3.22 0 0 0-3.24 3.32ZM79.32 7.98A5.53 5.53 0 0 1 83.4 6.3c1.6 0 2.93.55 4.06 1.68a5.51 5.51 0 0 1 1.69 4.06c0 1.58-.56 2.95-1.7 4.09a5.59 5.59 0 0 1-4.05 1.66 5.64 5.64 0 0 1-4.08-1.66 5.53 5.53 0 0 1-1.69-4.09c0-1.6.55-2.92 1.69-4.06Zm.84 4.06c0 .98.32 1.77.92 2.4.64.6 1.4.92 2.32.92.92 0 1.69-.31 2.3-.92.63-.64.94-1.42.94-2.4 0-.97-.31-1.76-.95-2.37a3.03 3.03 0 0 0-2.29-.95 3.22 3.22 0 0 0-3.24 3.32ZM34.24 16.76V24h-2.53V12.12c0-1.82.58-3.24 1.71-4.27a5.87 5.87 0 0 1 4.06-1.55c1.56 0 2.98.55 4.08 1.68a5.51 5.51 0 0 1 1.7 4.06 5.66 5.66 0 0 1-5.77 5.75 5.43 5.43 0 0 1-2.88-.77l-.36-.26Zm3.24-8.04a3.2 3.2 0 0 0-3.24 3.32c0 1.9 1.42 3.32 3.24 3.32a3.2 3.2 0 0 0 3.24-3.32 3.2 3.2 0 0 0-3.24-3.32ZM47.4 17.65h-2.5v-6.7c0-3.23 1.82-4.52 4.27-4.52h.6v2.42h-.47c-1.26 0-1.9.71-1.9 2.1v6.7Z"/>
<defs>
<linearGradient id="grad-b" x1="18.77" x2="1.2" y1="13.56" y2="13.56" gradientUnits="userSpaceOnUse">
<stop stop-color="#101E1C" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff"/>
</linearGradient>
<clipPath id="clip-a">
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14"/>
</clipPath>
</defs>
</svg>
</div> </div>
<!-- Signature ID + Status --> <!-- Title bar -->
<div class="meta-line"> <table class="title-bar">
<div class="meta-left"> <tr>
Signature Id: {{.SignatureID}} <td>Certificate Of Completion</td>
</div> </tr>
<div class="meta-right"> </table>
<strong>Status: Completed</strong>
</div>
</div>
<!-- Subject --> <!-- Metadata -->
<div class="info-row"> <table class="meta-table">
<strong>Subject:</strong> {{.DocumentTypeName}} <tr>
</div> <td class="lbl">Status</td>
<td class="val">Completed</td>
<!-- Document / Source info grid --> </tr>
<div class="info-grid" style="margin-top: 4px;"> <tr>
<div class="info-col info-col-left"> <td class="lbl">Subject</td>
<div>Document Type: {{.DocumentType}}</div> <td class="val">{{.DocumentTypeName}}</td>
<div>File Hash (SHA-256):</div> </tr>
<div class="mono">{{.FileHash}}</div> <tr>
</div> <td class="lbl">Document Type</td>
<div class="info-col info-col-mid"> <td class="val">{{.DocumentType}}</td>
&nbsp; </tr>
</div> <tr>
<div class="info-col info-col-right"> <td class="lbl">Signature Id</td>
<div>Organization Id:</div> <td class="val mono">{{.SignatureID}}</td>
<div class="mono">{{.OrganizationID}}</div> </tr>
<div style="margin-top: 2px;">File Id:</div> <tr>
<div class="mono">{{.FileID}}</div> <td class="lbl">Organization Id</td>
</div> <td class="val mono">{{.OrganizationID}}</td>
</div> </tr>
<tr>
<td class="lbl">File Id</td>
<td class="val mono">{{.FileID}}</td>
</tr>
<tr>
<td class="lbl">File Hash (SHA-256)</td>
<td class="val mono">{{.FileHash}}</td>
</tr>
</table>
<!-- Signer Events --> <!-- Signer Events -->
<table class="section-bar"> <table class="section-bar">
<tr> <tr>
<td style="width: 40%;">Signer Events</td> <td>Signer</td>
<td style="width: 60%;">Timestamp</td>
</tr> </tr>
</table> </table>
<table class="signer-table"> <table class="signer-table">
<tr> <tr>
<td style="width: 40%;"> <td class="lbl">Name</td>
<div class="signer-name">{{.SignerFullName}}</div> <td>{{.SignerFullName}}</td>
<div>{{.SignerEmail}}</div> </tr>
<div>Security Level: Email, Account Authentication</div> <tr>
<div>Using IP Address: {{.SignerIPAddress}}</div> <td class="lbl">Email</td>
<div style="margin-top: 6px; font-size: 8.5px;"> <td>{{.SignerEmail}}</td>
<strong>Electronic Record and Signature Disclosure:</strong><br/> </tr>
Accepted <tr>
</div> <td class="lbl">Signed At</td>
</td> <td>{{.SignedAt}}</td>
<td style="width: 60%;"> </tr>
<div><strong>Signed:</strong> {{.SignedAt}}</div> <tr>
<div style="margin-top: 8px; font-size: 8.5px;"> <td class="lbl">Security Level</td>
User Agent: {{.SignerUserAgent}} <td>Email, Account Authentication</td>
</div> </tr>
</td> <tr>
<td class="lbl">IP Address</td>
<td class="mono">{{.SignerIPAddress}}</td>
</tr>
<tr>
<td class="lbl">User Agent</td>
<td style="font-size: 8.5px;">{{.SignerUserAgent}}</td>
</tr>
<tr>
<td class="lbl">ERSD Consent</td>
<td>Accepted</td>
</tr> </tr>
</table> </table>

View File

@@ -173,16 +173,16 @@ func (c *Converter) GeneratePDF(ctx context.Context, htmlDocument []byte, cfg Re
chromedp.ActionFunc( chromedp.ActionFunc(
func(ctx context.Context) (err error) { func(ctx context.Context) (err error) {
pdfBytes, _, err = page.PrintToPDF(). pdfBytes, _, err = page.PrintToPDF().
WithPrintBackground(cfg.PrintBackground). WithPrintBackground(cfg.PrintBackground).
WithPaperWidth(width). WithPaperWidth(width).
WithPaperHeight(height). WithPaperHeight(height).
WithMarginTop(marginTop). WithMarginTop(marginTop).
WithMarginBottom(marginBottom). WithMarginBottom(marginBottom).
WithMarginLeft(marginLeft). WithMarginLeft(marginLeft).
WithMarginRight(marginRight). WithMarginRight(marginRight).
WithScale(scale). WithScale(scale).
WithPreferCSSPageSize(false). WithPreferCSSPageSize(false).
Do(ctx) Do(ctx)
return err return err
}, },