@at-flux/astroflare 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/chunk-ALMPH3A2.js +0 -0
- package/dist/chunk-DRYHJSYC.js +117 -0
- package/dist/core.cjs +142 -0
- package/dist/core.d.cts +2 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +17 -0
- package/dist/forms/index.cjs +130 -0
- package/dist/forms/index.d.cts +52 -0
- package/dist/forms/index.d.ts +52 -0
- package/dist/forms/index.js +14 -0
- package/dist/index.cjs +142 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/package.json +97 -0
- package/src/components/ClientRouterLoadingSpinner.astro +76 -0
- package/src/components/IconButton.astro +80 -0
- package/src/components/Modal.astro +63 -0
- package/src/components/ModalTrigger.astro +40 -0
- package/src/components/ThemeToggle.astro +127 -0
- package/src/styles/accessibility.css +29 -0
- package/src/styles/no-save.css +34 -0
- package/src/styles/prose.css +105 -0
- package/src/styles/scrollbar.css +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Taylor Siviter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @at-flux/astroflare
|
|
2
|
+
|
|
3
|
+
Reusable headless components, styles, and utilities for Astro + Tailwind v4 + Cloudflare projects.
|
|
4
|
+
|
|
5
|
+
For type-safe DOM helpers, use the separate package **`@at-flux/dom`**.
|
|
6
|
+
|
|
7
|
+
**There is no `@at-flux/astroflare/dom` subpath** (it is not published and must not be used). Add `@at-flux/dom` as its own dependency.
|
|
8
|
+
|
|
9
|
+
## Package entrypoints
|
|
10
|
+
|
|
11
|
+
| Subpath | Contents |
|
|
12
|
+
|---------|----------|
|
|
13
|
+
| `@at-flux/astroflare` | **Core** — forms utilities (same as `./core`) |
|
|
14
|
+
| `@at-flux/astroflare/core` | Forms; also exposes the `forms` namespace |
|
|
15
|
+
| `@at-flux/astroflare/forms` | Resend email + form HTML helpers |
|
|
16
|
+
| `@at-flux/astroflare/components/*` | Astro components (source) |
|
|
17
|
+
| `@at-flux/astroflare/styles/*` | CSS (source) |
|
|
18
|
+
|
|
19
|
+
### Examples
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// Flat imports from core
|
|
23
|
+
import { sendEmail } from '@at-flux/astroflare';
|
|
24
|
+
|
|
25
|
+
// Explicit subpaths
|
|
26
|
+
import { renderEmailTemplate } from '@at-flux/astroflare/forms';
|
|
27
|
+
|
|
28
|
+
// Namespaced (from core / root)
|
|
29
|
+
import { forms } from '@at-flux/astroflare/core';
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Contents
|
|
33
|
+
|
|
34
|
+
### Components (Astro)
|
|
35
|
+
|
|
36
|
+
- `Modal.astro` — Headless modal using native `<dialog>` and `<app-modal>` web component
|
|
37
|
+
- `ModalTrigger.astro` — Trigger that opens a modal by ID using `<modal-trigger>` web component
|
|
38
|
+
- `ThemeToggle.astro` — Dark/light mode toggle using `<theme-toggle>` web component
|
|
39
|
+
- `ClientRouterLoadingSpinner.astro` — Loading spinner for Astro view transitions
|
|
40
|
+
|
|
41
|
+
### Styles (CSS)
|
|
42
|
+
|
|
43
|
+
- `styles/prose.css` — Markdown prose styling using CSS custom properties
|
|
44
|
+
- `styles/no-save.css` — Image protection utilities (prevent right-click, drag, select)
|
|
45
|
+
- `styles/accessibility.css` — Focus styles, reduced motion, selection styling
|
|
46
|
+
- `styles/scrollbar.css` — Branded scrollbar styling
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Local Import (file: protocol)
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@at-flux/astroflare": "file:../../ts-libs/astroflare/packages/astroflare"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Components
|
|
61
|
+
|
|
62
|
+
```astro
|
|
63
|
+
---
|
|
64
|
+
import Modal from '@at-flux/astroflare/components/Modal.astro';
|
|
65
|
+
import ModalTrigger from '@at-flux/astroflare/components/ModalTrigger.astro';
|
|
66
|
+
---
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Styles
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
@import '@at-flux/astroflare/styles/prose.css';
|
|
73
|
+
@import '@at-flux/astroflare/styles/no-save.css';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Testing
|
|
77
|
+
|
|
78
|
+
From repo root:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm install
|
|
82
|
+
pnpm --filter @at-flux/astroflare test
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or in `packages/astroflare`:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pnpm test
|
|
89
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/forms/index.ts
|
|
8
|
+
var forms_exports = {};
|
|
9
|
+
__export(forms_exports, {
|
|
10
|
+
composeEmailAddress: () => composeEmailAddress,
|
|
11
|
+
generateFormResultHtml: () => generateFormResultHtml,
|
|
12
|
+
generateFormSectionHtml: () => generateFormSectionHtml,
|
|
13
|
+
renderEmailTemplate: () => renderEmailTemplate,
|
|
14
|
+
sendEmail: () => sendEmail
|
|
15
|
+
});
|
|
16
|
+
import { Resend } from "resend";
|
|
17
|
+
async function sendEmail(config, payload) {
|
|
18
|
+
if (config.mock) {
|
|
19
|
+
console.info("[astroflare:forms] Mock email payload", payload);
|
|
20
|
+
return { id: "mock-email-id" };
|
|
21
|
+
}
|
|
22
|
+
const resend = new Resend(config.apiKey);
|
|
23
|
+
const { data, error } = await resend.emails.send({
|
|
24
|
+
from: payload.from,
|
|
25
|
+
to: payload.to,
|
|
26
|
+
subject: payload.subject,
|
|
27
|
+
html: payload.html
|
|
28
|
+
});
|
|
29
|
+
if (error) {
|
|
30
|
+
console.error("[astroflare:forms] Resend API error", error);
|
|
31
|
+
throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function composeEmailAddress(local, domain, tag) {
|
|
36
|
+
const taggedLocal = tag ? `${local}+${tag}` : local;
|
|
37
|
+
return `${taggedLocal}@${domain}`;
|
|
38
|
+
}
|
|
39
|
+
function generateFormSectionHtml(section) {
|
|
40
|
+
const itemsHtml = section.items.map((item) => {
|
|
41
|
+
const value = item.value ?? "";
|
|
42
|
+
const isHtml = /<[^>]+>/.test(value);
|
|
43
|
+
return `
|
|
44
|
+
<div style="margin-bottom:16px;">
|
|
45
|
+
<p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
|
|
46
|
+
${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
|
|
47
|
+
</div>
|
|
48
|
+
`;
|
|
49
|
+
}).join("");
|
|
50
|
+
return `
|
|
51
|
+
<section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
|
|
52
|
+
<h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
|
|
53
|
+
<div style="color:#111827;">
|
|
54
|
+
${itemsHtml}
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
function generateFormResultHtml(sections) {
|
|
60
|
+
return sections.map(generateFormSectionHtml).join("\n");
|
|
61
|
+
}
|
|
62
|
+
async function renderEmailTemplate(args) {
|
|
63
|
+
const { title, contentHtml, brandName, footerText } = args;
|
|
64
|
+
const footer = footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString(
|
|
65
|
+
"en-GB"
|
|
66
|
+
)}.`;
|
|
67
|
+
return `<!DOCTYPE html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="utf-8">
|
|
71
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
72
|
+
<title>${title}</title>
|
|
73
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
74
|
+
</head>
|
|
75
|
+
<body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
|
76
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
|
|
77
|
+
<tr>
|
|
78
|
+
<td align="center" style="padding:24px;">
|
|
79
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
|
80
|
+
<tr>
|
|
81
|
+
<td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
|
|
82
|
+
<h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
|
|
83
|
+
${title}
|
|
84
|
+
</h1>
|
|
85
|
+
<p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
|
|
86
|
+
Contact form submission
|
|
87
|
+
</p>
|
|
88
|
+
</td>
|
|
89
|
+
</tr>
|
|
90
|
+
<tr>
|
|
91
|
+
<td style="padding:24px;">
|
|
92
|
+
${contentHtml}
|
|
93
|
+
</td>
|
|
94
|
+
</tr>
|
|
95
|
+
<tr>
|
|
96
|
+
<td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
|
|
97
|
+
<p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
|
|
98
|
+
${footer}
|
|
99
|
+
</p>
|
|
100
|
+
</td>
|
|
101
|
+
</tr>
|
|
102
|
+
</table>
|
|
103
|
+
</td>
|
|
104
|
+
</tr>
|
|
105
|
+
</table>
|
|
106
|
+
</body>
|
|
107
|
+
</html>`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
sendEmail,
|
|
112
|
+
composeEmailAddress,
|
|
113
|
+
generateFormSectionHtml,
|
|
114
|
+
generateFormResultHtml,
|
|
115
|
+
renderEmailTemplate,
|
|
116
|
+
forms_exports
|
|
117
|
+
};
|
package/dist/core.cjs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/core.ts
|
|
21
|
+
var core_exports = {};
|
|
22
|
+
__export(core_exports, {
|
|
23
|
+
composeEmailAddress: () => composeEmailAddress,
|
|
24
|
+
forms: () => forms_exports,
|
|
25
|
+
generateFormResultHtml: () => generateFormResultHtml,
|
|
26
|
+
generateFormSectionHtml: () => generateFormSectionHtml,
|
|
27
|
+
renderEmailTemplate: () => renderEmailTemplate,
|
|
28
|
+
sendEmail: () => sendEmail
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(core_exports);
|
|
31
|
+
|
|
32
|
+
// src/forms/index.ts
|
|
33
|
+
var forms_exports = {};
|
|
34
|
+
__export(forms_exports, {
|
|
35
|
+
composeEmailAddress: () => composeEmailAddress,
|
|
36
|
+
generateFormResultHtml: () => generateFormResultHtml,
|
|
37
|
+
generateFormSectionHtml: () => generateFormSectionHtml,
|
|
38
|
+
renderEmailTemplate: () => renderEmailTemplate,
|
|
39
|
+
sendEmail: () => sendEmail
|
|
40
|
+
});
|
|
41
|
+
var import_resend = require("resend");
|
|
42
|
+
async function sendEmail(config, payload) {
|
|
43
|
+
if (config.mock) {
|
|
44
|
+
console.info("[astroflare:forms] Mock email payload", payload);
|
|
45
|
+
return { id: "mock-email-id" };
|
|
46
|
+
}
|
|
47
|
+
const resend = new import_resend.Resend(config.apiKey);
|
|
48
|
+
const { data, error } = await resend.emails.send({
|
|
49
|
+
from: payload.from,
|
|
50
|
+
to: payload.to,
|
|
51
|
+
subject: payload.subject,
|
|
52
|
+
html: payload.html
|
|
53
|
+
});
|
|
54
|
+
if (error) {
|
|
55
|
+
console.error("[astroflare:forms] Resend API error", error);
|
|
56
|
+
throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
|
|
57
|
+
}
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
function composeEmailAddress(local, domain, tag) {
|
|
61
|
+
const taggedLocal = tag ? `${local}+${tag}` : local;
|
|
62
|
+
return `${taggedLocal}@${domain}`;
|
|
63
|
+
}
|
|
64
|
+
function generateFormSectionHtml(section) {
|
|
65
|
+
const itemsHtml = section.items.map((item) => {
|
|
66
|
+
const value = item.value ?? "";
|
|
67
|
+
const isHtml = /<[^>]+>/.test(value);
|
|
68
|
+
return `
|
|
69
|
+
<div style="margin-bottom:16px;">
|
|
70
|
+
<p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
|
|
71
|
+
${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
|
|
72
|
+
</div>
|
|
73
|
+
`;
|
|
74
|
+
}).join("");
|
|
75
|
+
return `
|
|
76
|
+
<section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
|
|
77
|
+
<h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
|
|
78
|
+
<div style="color:#111827;">
|
|
79
|
+
${itemsHtml}
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
function generateFormResultHtml(sections) {
|
|
85
|
+
return sections.map(generateFormSectionHtml).join("\n");
|
|
86
|
+
}
|
|
87
|
+
async function renderEmailTemplate(args) {
|
|
88
|
+
const { title, contentHtml, brandName, footerText } = args;
|
|
89
|
+
const footer = footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString(
|
|
90
|
+
"en-GB"
|
|
91
|
+
)}.`;
|
|
92
|
+
return `<!DOCTYPE html>
|
|
93
|
+
<html lang="en">
|
|
94
|
+
<head>
|
|
95
|
+
<meta charset="utf-8">
|
|
96
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
97
|
+
<title>${title}</title>
|
|
98
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
99
|
+
</head>
|
|
100
|
+
<body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
|
101
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
|
|
102
|
+
<tr>
|
|
103
|
+
<td align="center" style="padding:24px;">
|
|
104
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
|
105
|
+
<tr>
|
|
106
|
+
<td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
|
|
107
|
+
<h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
|
|
108
|
+
${title}
|
|
109
|
+
</h1>
|
|
110
|
+
<p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
|
|
111
|
+
Contact form submission
|
|
112
|
+
</p>
|
|
113
|
+
</td>
|
|
114
|
+
</tr>
|
|
115
|
+
<tr>
|
|
116
|
+
<td style="padding:24px;">
|
|
117
|
+
${contentHtml}
|
|
118
|
+
</td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
|
|
122
|
+
<p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
|
|
123
|
+
${footer}
|
|
124
|
+
</p>
|
|
125
|
+
</td>
|
|
126
|
+
</tr>
|
|
127
|
+
</table>
|
|
128
|
+
</td>
|
|
129
|
+
</tr>
|
|
130
|
+
</table>
|
|
131
|
+
</body>
|
|
132
|
+
</html>`;
|
|
133
|
+
}
|
|
134
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
135
|
+
0 && (module.exports = {
|
|
136
|
+
composeEmailAddress,
|
|
137
|
+
forms,
|
|
138
|
+
generateFormResultHtml,
|
|
139
|
+
generateFormSectionHtml,
|
|
140
|
+
renderEmailTemplate,
|
|
141
|
+
sendEmail
|
|
142
|
+
});
|
package/dist/core.d.cts
ADDED
package/dist/core.d.ts
ADDED
package/dist/core.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "./chunk-ALMPH3A2.js";
|
|
2
|
+
import {
|
|
3
|
+
composeEmailAddress,
|
|
4
|
+
forms_exports,
|
|
5
|
+
generateFormResultHtml,
|
|
6
|
+
generateFormSectionHtml,
|
|
7
|
+
renderEmailTemplate,
|
|
8
|
+
sendEmail
|
|
9
|
+
} from "./chunk-DRYHJSYC.js";
|
|
10
|
+
export {
|
|
11
|
+
composeEmailAddress,
|
|
12
|
+
forms_exports as forms,
|
|
13
|
+
generateFormResultHtml,
|
|
14
|
+
generateFormSectionHtml,
|
|
15
|
+
renderEmailTemplate,
|
|
16
|
+
sendEmail
|
|
17
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/forms/index.ts
|
|
21
|
+
var forms_exports = {};
|
|
22
|
+
__export(forms_exports, {
|
|
23
|
+
composeEmailAddress: () => composeEmailAddress,
|
|
24
|
+
generateFormResultHtml: () => generateFormResultHtml,
|
|
25
|
+
generateFormSectionHtml: () => generateFormSectionHtml,
|
|
26
|
+
renderEmailTemplate: () => renderEmailTemplate,
|
|
27
|
+
sendEmail: () => sendEmail
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(forms_exports);
|
|
30
|
+
var import_resend = require("resend");
|
|
31
|
+
async function sendEmail(config, payload) {
|
|
32
|
+
if (config.mock) {
|
|
33
|
+
console.info("[astroflare:forms] Mock email payload", payload);
|
|
34
|
+
return { id: "mock-email-id" };
|
|
35
|
+
}
|
|
36
|
+
const resend = new import_resend.Resend(config.apiKey);
|
|
37
|
+
const { data, error } = await resend.emails.send({
|
|
38
|
+
from: payload.from,
|
|
39
|
+
to: payload.to,
|
|
40
|
+
subject: payload.subject,
|
|
41
|
+
html: payload.html
|
|
42
|
+
});
|
|
43
|
+
if (error) {
|
|
44
|
+
console.error("[astroflare:forms] Resend API error", error);
|
|
45
|
+
throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
|
|
46
|
+
}
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
function composeEmailAddress(local, domain, tag) {
|
|
50
|
+
const taggedLocal = tag ? `${local}+${tag}` : local;
|
|
51
|
+
return `${taggedLocal}@${domain}`;
|
|
52
|
+
}
|
|
53
|
+
function generateFormSectionHtml(section) {
|
|
54
|
+
const itemsHtml = section.items.map((item) => {
|
|
55
|
+
const value = item.value ?? "";
|
|
56
|
+
const isHtml = /<[^>]+>/.test(value);
|
|
57
|
+
return `
|
|
58
|
+
<div style="margin-bottom:16px;">
|
|
59
|
+
<p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
|
|
60
|
+
${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
|
|
61
|
+
</div>
|
|
62
|
+
`;
|
|
63
|
+
}).join("");
|
|
64
|
+
return `
|
|
65
|
+
<section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
|
|
66
|
+
<h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
|
|
67
|
+
<div style="color:#111827;">
|
|
68
|
+
${itemsHtml}
|
|
69
|
+
</div>
|
|
70
|
+
</section>
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
function generateFormResultHtml(sections) {
|
|
74
|
+
return sections.map(generateFormSectionHtml).join("\n");
|
|
75
|
+
}
|
|
76
|
+
async function renderEmailTemplate(args) {
|
|
77
|
+
const { title, contentHtml, brandName, footerText } = args;
|
|
78
|
+
const footer = footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString(
|
|
79
|
+
"en-GB"
|
|
80
|
+
)}.`;
|
|
81
|
+
return `<!DOCTYPE html>
|
|
82
|
+
<html lang="en">
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="utf-8">
|
|
85
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
86
|
+
<title>${title}</title>
|
|
87
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
88
|
+
</head>
|
|
89
|
+
<body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
|
90
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
|
|
91
|
+
<tr>
|
|
92
|
+
<td align="center" style="padding:24px;">
|
|
93
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
|
94
|
+
<tr>
|
|
95
|
+
<td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
|
|
96
|
+
<h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
|
|
97
|
+
${title}
|
|
98
|
+
</h1>
|
|
99
|
+
<p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
|
|
100
|
+
Contact form submission
|
|
101
|
+
</p>
|
|
102
|
+
</td>
|
|
103
|
+
</tr>
|
|
104
|
+
<tr>
|
|
105
|
+
<td style="padding:24px;">
|
|
106
|
+
${contentHtml}
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr>
|
|
110
|
+
<td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
|
|
111
|
+
<p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
|
|
112
|
+
${footer}
|
|
113
|
+
</p>
|
|
114
|
+
</td>
|
|
115
|
+
</tr>
|
|
116
|
+
</table>
|
|
117
|
+
</td>
|
|
118
|
+
</tr>
|
|
119
|
+
</table>
|
|
120
|
+
</body>
|
|
121
|
+
</html>`;
|
|
122
|
+
}
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
composeEmailAddress,
|
|
126
|
+
generateFormResultHtml,
|
|
127
|
+
generateFormSectionHtml,
|
|
128
|
+
renderEmailTemplate,
|
|
129
|
+
sendEmail
|
|
130
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CreateEmailResponseSuccess } from 'resend';
|
|
2
|
+
|
|
3
|
+
interface ResendConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Resend API key.
|
|
6
|
+
*/
|
|
7
|
+
apiKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* When true, do not send real email – log to console instead.
|
|
10
|
+
* Useful for local/dev environments.
|
|
11
|
+
*/
|
|
12
|
+
mock?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface EmailPayload {
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
subject: string;
|
|
18
|
+
html: string;
|
|
19
|
+
}
|
|
20
|
+
declare function sendEmail(config: ResendConfig, payload: EmailPayload): Promise<CreateEmailResponseSuccess | {
|
|
21
|
+
id: string;
|
|
22
|
+
}>;
|
|
23
|
+
declare function composeEmailAddress(local: string, domain: string, tag?: string): string;
|
|
24
|
+
interface FormSection {
|
|
25
|
+
title: string;
|
|
26
|
+
items: {
|
|
27
|
+
key: string;
|
|
28
|
+
value: string;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
declare function generateFormSectionHtml(section: FormSection): string;
|
|
32
|
+
declare function generateFormResultHtml(sections: FormSection[]): string;
|
|
33
|
+
declare function renderEmailTemplate(args: {
|
|
34
|
+
title: string;
|
|
35
|
+
contentHtml: string;
|
|
36
|
+
brandName?: string;
|
|
37
|
+
footerText?: string;
|
|
38
|
+
}): Promise<string>;
|
|
39
|
+
|
|
40
|
+
type index_EmailPayload = EmailPayload;
|
|
41
|
+
type index_FormSection = FormSection;
|
|
42
|
+
type index_ResendConfig = ResendConfig;
|
|
43
|
+
declare const index_composeEmailAddress: typeof composeEmailAddress;
|
|
44
|
+
declare const index_generateFormResultHtml: typeof generateFormResultHtml;
|
|
45
|
+
declare const index_generateFormSectionHtml: typeof generateFormSectionHtml;
|
|
46
|
+
declare const index_renderEmailTemplate: typeof renderEmailTemplate;
|
|
47
|
+
declare const index_sendEmail: typeof sendEmail;
|
|
48
|
+
declare namespace index {
|
|
49
|
+
export { type index_EmailPayload as EmailPayload, type index_FormSection as FormSection, type index_ResendConfig as ResendConfig, index_composeEmailAddress as composeEmailAddress, index_generateFormResultHtml as generateFormResultHtml, index_generateFormSectionHtml as generateFormSectionHtml, index_renderEmailTemplate as renderEmailTemplate, index_sendEmail as sendEmail };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { type EmailPayload, type FormSection, type ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, index as i, renderEmailTemplate, sendEmail };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CreateEmailResponseSuccess } from 'resend';
|
|
2
|
+
|
|
3
|
+
interface ResendConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Resend API key.
|
|
6
|
+
*/
|
|
7
|
+
apiKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* When true, do not send real email – log to console instead.
|
|
10
|
+
* Useful for local/dev environments.
|
|
11
|
+
*/
|
|
12
|
+
mock?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface EmailPayload {
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
subject: string;
|
|
18
|
+
html: string;
|
|
19
|
+
}
|
|
20
|
+
declare function sendEmail(config: ResendConfig, payload: EmailPayload): Promise<CreateEmailResponseSuccess | {
|
|
21
|
+
id: string;
|
|
22
|
+
}>;
|
|
23
|
+
declare function composeEmailAddress(local: string, domain: string, tag?: string): string;
|
|
24
|
+
interface FormSection {
|
|
25
|
+
title: string;
|
|
26
|
+
items: {
|
|
27
|
+
key: string;
|
|
28
|
+
value: string;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
declare function generateFormSectionHtml(section: FormSection): string;
|
|
32
|
+
declare function generateFormResultHtml(sections: FormSection[]): string;
|
|
33
|
+
declare function renderEmailTemplate(args: {
|
|
34
|
+
title: string;
|
|
35
|
+
contentHtml: string;
|
|
36
|
+
brandName?: string;
|
|
37
|
+
footerText?: string;
|
|
38
|
+
}): Promise<string>;
|
|
39
|
+
|
|
40
|
+
type index_EmailPayload = EmailPayload;
|
|
41
|
+
type index_FormSection = FormSection;
|
|
42
|
+
type index_ResendConfig = ResendConfig;
|
|
43
|
+
declare const index_composeEmailAddress: typeof composeEmailAddress;
|
|
44
|
+
declare const index_generateFormResultHtml: typeof generateFormResultHtml;
|
|
45
|
+
declare const index_generateFormSectionHtml: typeof generateFormSectionHtml;
|
|
46
|
+
declare const index_renderEmailTemplate: typeof renderEmailTemplate;
|
|
47
|
+
declare const index_sendEmail: typeof sendEmail;
|
|
48
|
+
declare namespace index {
|
|
49
|
+
export { type index_EmailPayload as EmailPayload, type index_FormSection as FormSection, type index_ResendConfig as ResendConfig, index_composeEmailAddress as composeEmailAddress, index_generateFormResultHtml as generateFormResultHtml, index_generateFormSectionHtml as generateFormSectionHtml, index_renderEmailTemplate as renderEmailTemplate, index_sendEmail as sendEmail };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { type EmailPayload, type FormSection, type ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, index as i, renderEmailTemplate, sendEmail };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
composeEmailAddress,
|
|
3
|
+
generateFormResultHtml,
|
|
4
|
+
generateFormSectionHtml,
|
|
5
|
+
renderEmailTemplate,
|
|
6
|
+
sendEmail
|
|
7
|
+
} from "../chunk-DRYHJSYC.js";
|
|
8
|
+
export {
|
|
9
|
+
composeEmailAddress,
|
|
10
|
+
generateFormResultHtml,
|
|
11
|
+
generateFormSectionHtml,
|
|
12
|
+
renderEmailTemplate,
|
|
13
|
+
sendEmail
|
|
14
|
+
};
|