Add a contact form to the resume #1
@@ -2,7 +2,6 @@ name: release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
build
|
build
|
||||||
|
.wrangler
|
||||||
3
build.sh
3
build.sh
@@ -1,5 +1,6 @@
|
|||||||
mkdir -p build/@picocss/pico/css/
|
mkdir -p build/@picocss/pico/css/
|
||||||
cp *.png *.xml *.svg *.css *.webmanifest *.ico robots.txt _headers build
|
cp -r *.png *.xml *.svg *.css *.webmanifest *.ico robots.txt _headers functions contact build
|
||||||
# https://github.com/cloudflare/workers-sdk/issues/3615
|
# https://github.com/cloudflare/workers-sdk/issues/3615
|
||||||
sed 's/node_modules\///' index.html > build/index.html
|
sed 's/node_modules\///' index.html > build/index.html
|
||||||
|
sed 's/node_modules\///' contact/index.html > build/contact/index.html
|
||||||
cp node_modules/@picocss/pico/css/pico.min.css build/@picocss/pico/css/
|
cp node_modules/@picocss/pico/css/pico.min.css build/@picocss/pico/css/
|
||||||
96
contact/index.html
Normal file
96
contact/index.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Michael Pivato | Contact</title>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="../node_modules/@picocss/pico/css/pico.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="../apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="../favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="../favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="../site.webmanifest" />
|
||||||
|
<link rel="mask-icon" href="../safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
|
<meta
|
||||||
|
name="theme-color"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
content="#13171f"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="theme-color"
|
||||||
|
media="(prefers-color-scheme: light)"
|
||||||
|
content="#2a3140"
|
||||||
|
/>
|
||||||
|
<meta name="description" content="Michael Pivato's Resume: Contact Form" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
<header>
|
||||||
|
<hgroup>
|
||||||
|
<h1>Michael Pivato</h1>
|
||||||
|
<p>Send Michael a message</p>
|
||||||
|
<a href="../">Back to resume</a>
|
||||||
|
</hgroup>
|
||||||
|
</header>
|
||||||
|
<form data-static-form-name="contact">
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input name="name" placeholder="Name" autocomplete="name" />
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
>Organisation
|
||||||
|
<input
|
||||||
|
name="org"
|
||||||
|
placeholder="Organisation"
|
||||||
|
autocomplete="organization"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Email"
|
||||||
|
autocomplete="email"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Mobile
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="mobile"
|
||||||
|
placeholder="Mobile"
|
||||||
|
autocomplete="mobile"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Message
|
||||||
|
<textarea name="message" placeholder="Mesage..."></textarea>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<input type="submit" value="Send Message" />
|
||||||
|
</form>
|
||||||
|
<footer class="container">
|
||||||
|
<small>Michael Pivato • 2025</small>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
72
functions/contact.ts
Normal file
72
functions/contact.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import staticFormsPlugin from "@cloudflare/pages-plugin-static-forms";
|
||||||
|
import { EmailMessage } from "cloudflare:email";
|
||||||
|
|
||||||
|
export const onRequest: PagesFunction = staticFormsPlugin({
|
||||||
|
respondWith: async ({ formData }) => {
|
||||||
|
const fullName = formData.get("name");
|
||||||
|
const organisation = formData.get("org") ?? "Unknown Organisation";
|
||||||
|
const email = formData.get("email");
|
||||||
|
const mobile = formData.get("mobile") ?? "Unknown Mobile";
|
||||||
|
const message = formData.get("message");
|
||||||
|
|
||||||
|
// Must have some kind of identifiable information for me to actually care about them.
|
||||||
|
if ((fullName || email) && message) {
|
||||||
|
// const emailMessage = createMimeMessage();
|
||||||
|
// emailMessage.setSender({
|
||||||
|
// name: "Michael Pivato Contact Form",
|
||||||
|
// addr: "contact@michaelpivato.dev",
|
||||||
|
// });
|
||||||
|
// emailMessage.setRecipient("contact@michaelpivato.dev");
|
||||||
|
// emailMessage.setSubject(`Message from ${fullName ?? email}`);
|
||||||
|
// emailMessage.addMessage({
|
||||||
|
// contentType: "text/plain",
|
||||||
|
// data: `You've received a new message from ${fullName ?? email}.
|
||||||
|
// Full Name: ${formatEmptyString(fullName)}
|
||||||
|
// Organisation: ${formatEmptyString(organisation)}
|
||||||
|
// Email: ${formatEmptyString(email)}
|
||||||
|
// Mobile: ${formatEmptyString(mobile)}
|
||||||
|
|
||||||
|
// Message:
|
||||||
|
// ${message}
|
||||||
|
// `,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const rawEmailMessage = `----
|
||||||
|
From: Michael Pivato Contact Form <contact@michaelpivato.dev>
|
||||||
|
To: Michael Pivato <contact@michaelpivato.dev
|
||||||
|
Subject: Message from ${fullName ?? email}
|
||||||
|
|
||||||
|
You've received a new message from ${fullName ?? email}.
|
||||||
|
Full Name: ${formatEmptyString(fullName)}
|
||||||
|
Organisation: ${formatEmptyString(organisation)}
|
||||||
|
Email: ${formatEmptyString(email)}
|
||||||
|
Mobile: ${formatEmptyString(mobile)}
|
||||||
|
|
||||||
|
Message:
|
||||||
|
${message}
|
||||||
|
----`;
|
||||||
|
|
||||||
|
const cfMessage = new EmailMessage(
|
||||||
|
"contact@michaelpivato.dev",
|
||||||
|
"contact@michaelpivato.dev",
|
||||||
|
rawEmailMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await env.SEB.send(cfMessage);
|
||||||
|
} catch (e) {
|
||||||
|
return new Response(e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Full Name: " + fullName ?? "fullname");
|
||||||
|
console.log("Organisation: " + organisation);
|
||||||
|
console.log("Email: " + email ?? "email");
|
||||||
|
console.log("Mobile: " + mobile ?? "mobile");
|
||||||
|
console.log("Message: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect("https://michaelpivato.dev");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatEmptyString = (s: string) => s ?? "Not Specified";
|
||||||
13
functions/tsconfig.json
Normal file
13
functions/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
"lib": [
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"@cloudflare/workers-types"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@
|
|||||||
<hgroup>
|
<hgroup>
|
||||||
<h1>Michael Pivato</h1>
|
<h1>Michael Pivato</h1>
|
||||||
<p>Career summary and interests</p>
|
<p>Career summary and interests</p>
|
||||||
|
<a href="contact">Contact</a>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</header>
|
</header>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
1092
package-lock.json
generated
1092
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -7,9 +7,17 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
"type": "module",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@picocss/pico": "^2.0.0"
|
"@cloudflare/pages-plugin-static-forms": "^1.0.3",
|
||||||
|
"@picocss/pico": "^2.0.0",
|
||||||
|
"mimetext": "^3.0.27"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20250129.0",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"wrangler": "^3.107.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
wrangler.toml
Normal file
6
wrangler.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
send_email = [
|
||||||
|
{name = "michael", destination_address = "contact@michaelpivato.dev"},
|
||||||
|
]
|
||||||
|
|
||||||
|
compatibility_flags = [ "nodejs_compat" ]
|
||||||
|
compatibility_date = "2024-09-23"
|
||||||
Reference in New Issue
Block a user