work on porting over contact form from old site, also added initial db support
to use later
This commit is contained in:
parent
8d989ef36f
commit
f641dac69b
12 changed files with 232 additions and 32 deletions
|
|
@ -13,6 +13,12 @@
|
|||
href="https://fonts.googleapis.com/css2?family=VT323&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script>
|
||||
const yearSpan = document.querySelector("#copyright-year");
|
||||
if (yearSpan) {
|
||||
yearSpan.innerHTML = new Date().getFullYear().toString();
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
:root {
|
||||
--min-body-width: 30ch;
|
||||
|
|
@ -96,9 +102,7 @@
|
|||
</main>
|
||||
<footer>
|
||||
<p>
|
||||
© <script is:inline>
|
||||
document.write(new Date().getFullYear());
|
||||
</script>
|
||||
© <span id="copyright-year">2026</span>
|
||||
badblocks
|
||||
</p>
|
||||
<p>Made from scratch with BAHA: Bun, Astro, Htmx, and Alpine!</p>
|
||||
|
|
|
|||
46
src/lib/HttpFetchClient.ts
Normal file
46
src/lib/HttpFetchClient.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const httpFetchClient = {
|
||||
get: async (url: string, headers: Record<string, string>) => {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers,
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
post: async (url: string, body: JSON, headers: Record<string, string>) => {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
put: async (url: string, body: JSON, headers: Record<string, string>) => {
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
patch: async (url: string, body: JSON, headers: Record<string, string>) => {
|
||||
const response = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
delete: async (url: string, headers: Record<string, string>) => {
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers,
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
export default httpFetchClient;
|
||||
53
src/lib/SmsGatewayClient.ts
Normal file
53
src/lib/SmsGatewayClient.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import Client from "android-sms-gateway";
|
||||
import {
|
||||
ANDROID_SMS_GATEWAY_LOGIN,
|
||||
ANDROID_SMS_GATEWAY_PASSWORD,
|
||||
ANDROID_SMS_GATEWAY_RECIPIENT_PHONE,
|
||||
ANDROID_SMS_GATEWAY_URL,
|
||||
} from "astro:env/server";
|
||||
import httpFetchClient from "@lib/HttpFetchClient";
|
||||
|
||||
class SmsClient {
|
||||
readonly api: Client;
|
||||
|
||||
constructor() {
|
||||
this.api = new Client(
|
||||
ANDROID_SMS_GATEWAY_LOGIN,
|
||||
ANDROID_SMS_GATEWAY_PASSWORD,
|
||||
httpFetchClient,
|
||||
ANDROID_SMS_GATEWAY_URL,
|
||||
);
|
||||
}
|
||||
|
||||
async sendSMS(message: string) {
|
||||
const bundle = {
|
||||
phoneNumbers: [ANDROID_SMS_GATEWAY_RECIPIENT_PHONE], // hard-coded on purpose ;)
|
||||
message: message,
|
||||
};
|
||||
try {
|
||||
const msg_state = await this.api.send(bundle);
|
||||
return {
|
||||
success: true,
|
||||
id: msg_state.id,
|
||||
state: msg_state.state,
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error };
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: string) {
|
||||
try {
|
||||
const msg_state = await this.api.getState(id);
|
||||
return {
|
||||
success: true,
|
||||
id: msg_state.id,
|
||||
state: msg_state.state,
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, id: id, error: error };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SmsClient;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import Layout from "@layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,42 @@
|
|||
---
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import Layout from "@layouts/BaseLayout.astro";
|
||||
import SmsClient from "@lib/SmsGatewayClient.ts";
|
||||
export const prerender = false;
|
||||
|
||||
const errors = { name: "", phone: "", msg: "", form: "" };
|
||||
let success = false;
|
||||
if (Astro.request.method === "POST") {
|
||||
try {
|
||||
const data = await Astro.request.formData();
|
||||
const name = data.get("name")?.toString();
|
||||
const phone = data.get("phone")?.toString();
|
||||
const msg = data.get("msg")?.toString();
|
||||
|
||||
if (typeof name !== "string" || name.length < 1) {
|
||||
errors.name += "Please enter a name. ";
|
||||
}
|
||||
if (typeof phone !== "string") {
|
||||
errors.phone += "Phone is not valid. ";
|
||||
}
|
||||
if (typeof msg !== "string" || msg.length < 20) {
|
||||
errors.msg += "Message must be at least 20 characters. ";
|
||||
}
|
||||
|
||||
const hasErrors = Object.values(errors).some(msg => msg)
|
||||
if (!hasErrors) {
|
||||
const smsClient = new SmsClient();
|
||||
const message = "Web message from " + name + " (" + phone + "):\n\n" + msg;
|
||||
const result = await smsClient.sendSMS(message);
|
||||
if (!result.success) {
|
||||
errors.form += "Sending SMS failed; API returned error. "
|
||||
} else { success = true; }
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
errors.form += error.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<Layout>
|
||||
|
|
@ -17,6 +53,9 @@ export const prerender = false;
|
|||
"msg msg"
|
||||
" . submit";
|
||||
}
|
||||
div {
|
||||
grid-area: header;
|
||||
}
|
||||
label[for="name"] {
|
||||
grid-area: name;
|
||||
}
|
||||
|
|
@ -32,21 +71,30 @@ export const prerender = false;
|
|||
</style>
|
||||
<Fragment slot="content">
|
||||
<h2>Contact</h2>
|
||||
<p>Use the below form to shoot me a quick text!</p>
|
||||
<form x-data="{}">
|
||||
{!success && <form method="post" x-data="{}">
|
||||
<div>
|
||||
<p>Use the below form to shoot me a quick text!</p>
|
||||
{errors.form && <p>{errors.form}</p>}
|
||||
</div>
|
||||
<label for="name">
|
||||
Name
|
||||
<input type="text" id="name" placeholder="John Doe" />
|
||||
<input type="text" id="name" name="name" placeholder="Bad Blocks" />
|
||||
{errors.name && <p>{errors.name}</p>}
|
||||
</label>
|
||||
<label for="phone">
|
||||
Phone
|
||||
<input type="text" id="phone" placeholder="555-555-5555" />
|
||||
<input type="text" id="phone" name="phone" placeholder="555-555-5555" />
|
||||
{errors.phone && <p>{errors.phone}</p>}
|
||||
</label>
|
||||
<label for="msg">
|
||||
Msg
|
||||
<textarea id="msg" placeholder="I think badblocks rocks!"></textarea>
|
||||
<textarea id="msg" name="msg" placeholder="I think badblocks rocks!"></textarea>
|
||||
{errors.msg && <p>{errors.msg}</p>}
|
||||
</label>
|
||||
<label for="cap">
|
||||
<cap-widget data-cap-api-endpoint="<your cap endpoint>"></cap-widget>
|
||||
</label>
|
||||
<button id="submit" type="submit">Submit</button>
|
||||
</form>
|
||||
</form> || <p>Your message has been sent successfully!</p>}
|
||||
</Fragment>
|
||||
</Layout>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import Layout from "@layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import Layout from "@layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue