Replaces every em-dash and en-dash with regular ASCII hyphens across comments, JSX strings, and dev-facing logs. Mostly cosmetic but stops the inconsistent mix that crept in over the last few months (some files used em-dashes in comments, others didn't, some used both). Bundles two small dashboard-layout tweaks that touch a couple of already-modified files: - (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6 pb-6 so page content sits closer to the topbar. - Sidebar now receives the ports list it needs for the footer port switcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
338 lines
8.0 KiB
TypeScript
338 lines
8.0 KiB
TypeScript
/**
|
|
* Standard in-app EOI (Letter of Intent) template.
|
|
*
|
|
* Rendered in-app via pdfme (HTML → PDF pipeline) for ports that prefer the
|
|
* in-app PDF generation path over the Documenso template flow.
|
|
*
|
|
* Merge tokens use the {{section.field}} convention and match the
|
|
* `EoiContext` shape produced by `buildEoiContext` in
|
|
* `src/lib/services/eoi-context.ts`. The tokens are resolved by
|
|
* `resolveTemplate` (Task 11.4 wires the expanded resolver).
|
|
*
|
|
* Related:
|
|
* - Field mapping: docs/eoi-documenso-field-mapping.md
|
|
* - Context builder: src/lib/services/eoi-context.ts
|
|
* - Schema: document_templates (src/lib/db/schema/documents.ts)
|
|
*/
|
|
|
|
export const STANDARD_EOI_MERGE_FIELDS: string[] = [
|
|
'date.today',
|
|
'date.year',
|
|
'port.name',
|
|
'port.defaultCurrency',
|
|
'client.fullName',
|
|
'client.nationality',
|
|
'client.primaryEmail',
|
|
'client.primaryPhone',
|
|
'client.address.street',
|
|
'client.address.city',
|
|
'client.address.country',
|
|
'yacht.name',
|
|
'yacht.hullNumber',
|
|
'yacht.flag',
|
|
'yacht.yearBuilt',
|
|
'yacht.lengthFt',
|
|
'yacht.widthFt',
|
|
'yacht.draftFt',
|
|
'yacht.lengthM',
|
|
'yacht.widthM',
|
|
'yacht.draftM',
|
|
'company.name',
|
|
'company.legalName',
|
|
'company.taxId',
|
|
'company.billingAddress',
|
|
'owner.type',
|
|
'owner.name',
|
|
'owner.legalName',
|
|
'berth.mooringNumber',
|
|
'berth.area',
|
|
'berth.lengthFt',
|
|
'berth.price',
|
|
'berth.priceCurrency',
|
|
'berth.tenureType',
|
|
'interest.stage',
|
|
'interest.leadCategory',
|
|
'interest.dateFirstContact',
|
|
'interest.notes',
|
|
];
|
|
|
|
export function getStandardEoiTemplateHtml(): string {
|
|
return `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>Expression of Interest - Letter of Intent</title>
|
|
<style>
|
|
@page {
|
|
size: letter;
|
|
margin: 0.9in 0.9in 1.0in 0.9in;
|
|
}
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
font-family: "Times New Roman", Georgia, serif;
|
|
font-size: 12pt;
|
|
color: #111;
|
|
line-height: 1.45;
|
|
}
|
|
.header {
|
|
border-bottom: 2px solid #111;
|
|
padding-bottom: 8pt;
|
|
margin-bottom: 18pt;
|
|
}
|
|
.header .port-name {
|
|
font-size: 18pt;
|
|
font-weight: 700;
|
|
letter-spacing: 0.5pt;
|
|
}
|
|
.header .doc-title {
|
|
margin-top: 4pt;
|
|
font-size: 14pt;
|
|
font-weight: 600;
|
|
font-style: italic;
|
|
}
|
|
.meta {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 18pt;
|
|
font-size: 11pt;
|
|
}
|
|
h2.section {
|
|
font-size: 12pt;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.8pt;
|
|
border-bottom: 1px solid #555;
|
|
padding-bottom: 2pt;
|
|
margin: 16pt 0 8pt 0;
|
|
}
|
|
table.fields {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 6pt;
|
|
}
|
|
table.fields td {
|
|
padding: 3pt 6pt 3pt 0;
|
|
vertical-align: top;
|
|
}
|
|
table.fields td.label {
|
|
width: 34%;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
.addr-line {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
.signatures {
|
|
margin-top: 36pt;
|
|
display: table;
|
|
width: 100%;
|
|
}
|
|
.signatures .slot {
|
|
display: table-cell;
|
|
width: 50%;
|
|
padding-right: 18pt;
|
|
vertical-align: top;
|
|
}
|
|
.signatures .slot:last-child {
|
|
padding-right: 0;
|
|
padding-left: 18pt;
|
|
}
|
|
.sig-line {
|
|
border-top: 1px solid #111;
|
|
margin-top: 42pt;
|
|
padding-top: 4pt;
|
|
font-size: 10pt;
|
|
color: #333;
|
|
}
|
|
.footer {
|
|
position: fixed;
|
|
bottom: 0.4in;
|
|
left: 0.9in;
|
|
right: 0.9in;
|
|
text-align: center;
|
|
font-size: 9pt;
|
|
color: #666;
|
|
border-top: 1px solid #ccc;
|
|
padding-top: 4pt;
|
|
}
|
|
.muted {
|
|
color: #666;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div class="port-name">{{port.name}}</div>
|
|
<div class="doc-title">Expression of Interest - Letter of Intent</div>
|
|
</div>
|
|
|
|
<div class="meta">
|
|
<div><strong>Date:</strong> {{date.today}}</div>
|
|
<div><strong>Port:</strong> {{port.name}}</div>
|
|
</div>
|
|
|
|
<p>
|
|
This Expression of Interest (the “EOI”) is entered into between
|
|
<strong>{{port.name}}</strong> and the Applicant named below, and records the
|
|
Applicant’s non-binding intent to proceed toward a berth acquisition at
|
|
the port. It is subject to subsequent definitive documentation.
|
|
</p>
|
|
|
|
<h2 class="section">1. Applicant</h2>
|
|
<table class="fields">
|
|
<tr>
|
|
<td class="label">Full name</td>
|
|
<td>{{client.fullName}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Nationality</td>
|
|
<td>{{client.nationality}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Email</td>
|
|
<td>{{client.primaryEmail}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Phone</td>
|
|
<td>{{client.primaryPhone}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Address</td>
|
|
<td>
|
|
<p class="addr-line">{{client.address.street}}</p>
|
|
<p class="addr-line">{{client.address.city}}</p>
|
|
<p class="addr-line">{{client.address.country}}</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2 class="section">2. Yacht</h2>
|
|
<table class="fields">
|
|
<tr>
|
|
<td class="label">Name</td>
|
|
<td>{{yacht.name}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Hull number</td>
|
|
<td>{{yacht.hullNumber}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Flag</td>
|
|
<td>{{yacht.flag}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Year built</td>
|
|
<td>{{yacht.yearBuilt}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Length (ft / m)</td>
|
|
<td>{{yacht.lengthFt}} ft / {{yacht.lengthM}} m</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Beam (ft / m)</td>
|
|
<td>{{yacht.widthFt}} ft / {{yacht.widthM}} m</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Draft (ft / m)</td>
|
|
<td>{{yacht.draftFt}} ft / {{yacht.draftM}} m</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2 class="section">3. Owner</h2>
|
|
<p>
|
|
Owner type: <strong>{{owner.type}}</strong><br />
|
|
Owner name: <strong>{{owner.name}}</strong>
|
|
<span class="muted"> (legal: {{owner.legalName}})</span>
|
|
</p>
|
|
<table class="fields">
|
|
<tr>
|
|
<td class="label">Company name</td>
|
|
<td>{{company.name}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Legal name</td>
|
|
<td>{{company.legalName}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Tax ID</td>
|
|
<td>{{company.taxId}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Billing address</td>
|
|
<td>{{company.billingAddress}}</td>
|
|
</tr>
|
|
</table>
|
|
<p class="muted" style="font-size:10pt;">
|
|
The company block is populated only where the yacht is company-owned; for
|
|
client-owned yachts these fields render empty.
|
|
</p>
|
|
|
|
<h2 class="section">4. Berth</h2>
|
|
<table class="fields">
|
|
<tr>
|
|
<td class="label">Mooring number</td>
|
|
<td>{{berth.mooringNumber}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Area</td>
|
|
<td>{{berth.area}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Length</td>
|
|
<td>{{berth.lengthFt}} ft</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Price</td>
|
|
<td>{{berth.price}} {{berth.priceCurrency}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Tenure type</td>
|
|
<td>{{berth.tenureType}}</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2 class="section">5. Interest Summary</h2>
|
|
<table class="fields">
|
|
<tr>
|
|
<td class="label">Pipeline stage</td>
|
|
<td>{{interest.stage}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Lead category</td>
|
|
<td>{{interest.leadCategory}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">First contact</td>
|
|
<td>{{interest.dateFirstContact}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Notes</td>
|
|
<td>{{interest.notes}}</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2 class="section">6. Signatures</h2>
|
|
<div class="signatures">
|
|
<div class="slot">
|
|
<div class="sig-line">
|
|
Applicant - {{client.fullName}}
|
|
</div>
|
|
<div class="muted" style="font-size:10pt; margin-top:2pt;">Date: __________________</div>
|
|
</div>
|
|
<div class="slot">
|
|
<div class="sig-line">
|
|
For and on behalf of {{port.name}}
|
|
</div>
|
|
<div class="muted" style="font-size:10pt; margin-top:2pt;">Date: __________________</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
{{port.name}} · Expression of Interest · {{date.year}}
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
}
|