Skip to content

Commit 16fe636

Browse files
committed
Add doula match form functionality: implement API endpoint for form submission, create corresponding TypeScript handling, and add thank-you page layout with user guidance.
1 parent 6466f75 commit 16fe636

10 files changed

Lines changed: 314 additions & 52 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Response } from "express";
2+
import * as admin from "firebase-admin";
3+
import { logger } from "firebase-functions";
4+
import { Request } from "firebase-functions/v2/https";
5+
6+
interface DoulaMatchFormRequest extends Request {
7+
body: {
8+
name?: string;
9+
phone: string;
10+
email: string;
11+
zipcode: string;
12+
estimatedDueDate: {
13+
month: string;
14+
day: string;
15+
year: string;
16+
};
17+
services: string[];
18+
birthLocation: string;
19+
otherInfo: string;
20+
};
21+
}
22+
23+
export async function handleDoulaMatchForm(
24+
request: DoulaMatchFormRequest,
25+
response: Response,
26+
): Promise<void> {
27+
response.set("Access-Control-Allow-Origin", "*");
28+
response.set("Access-Control-Allow-Methods", "POST");
29+
response.set("Access-control-Allow-Headers", "Content-Type");
30+
31+
try {
32+
const today = new Date().toISOString();
33+
await admin
34+
.firestore()
35+
.collection("matchRequests")
36+
.add({
37+
...request.body,
38+
submitted: today,
39+
});
40+
41+
response.status(200).send("Okay");
42+
} catch (error: unknown) {
43+
logger.error(error);
44+
response.status(500).send({ error });
45+
}
46+
}

functions/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,13 @@ export const contactUsForm = onRequest(
2727
await handleContactUsForm(request, response);
2828
},
2929
);
30+
31+
export const doulaMatchForm = onRequest(
32+
{ invoker: "public" },
33+
async (request, response) => {
34+
const { handleDoulaMatchForm } = await import(
35+
"./doula-match-form/doula-match-form.js"
36+
);
37+
await handleDoulaMatchForm(request, response);
38+
},
39+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@use "../components/callout";

hugo/assets/ts/doula-match-form.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const matchForm: HTMLFormElement | null = document.querySelector(".form");
2+
const contactName: HTMLInputElement | null = document.querySelector("#name");
3+
const phone: HTMLInputElement | null = document.querySelector("#phone");
4+
const email: HTMLInputElement | null = document.querySelector("#email");
5+
const zipcode: HTMLInputElement | null = document.querySelector("#zipcode");
6+
const month: HTMLInputElement | null = document.querySelector("#month");
7+
const day: HTMLInputElement | null = document.querySelector("#day");
8+
const year: HTMLInputElement | null = document.querySelector("#year");
9+
const otherInfo: HTMLTextAreaElement | null =
10+
document.querySelector("#other-info");
11+
const submitButton: HTMLButtonElement | null = document.querySelector(
12+
'button[type="submit"]',
13+
);
14+
const formError: HTMLDivElement | null = document.querySelector("#form-error");
15+
16+
interface DoulaMatchFormRequest {
17+
name?: string;
18+
phone: string;
19+
email: string;
20+
zipcode: string;
21+
estimatedDueDate: {
22+
month: string;
23+
day: string;
24+
year: string;
25+
};
26+
services: string[];
27+
birthLocation: string;
28+
otherInfo: string;
29+
}
30+
31+
async function sendMatchForm(data: DoulaMatchFormRequest): Promise<void> {
32+
const url = matchForm?.dataset["apiUrl"];
33+
if (!url) {
34+
throw new Error("API URL not found");
35+
}
36+
37+
const response = await fetch(url, {
38+
method: "POST",
39+
body: JSON.stringify(data),
40+
headers: {
41+
"Content-Type": "application/json",
42+
},
43+
});
44+
45+
if (!response.ok) {
46+
throw new Error(`HTTP error! status: ${String(response.status)}`);
47+
}
48+
49+
location.href = "/thank-you-for-your-match-request";
50+
}
51+
52+
const doSubmit = async () => {
53+
if (submitButton) {
54+
submitButton.disabled = true;
55+
submitButton.textContent = "Sending...";
56+
}
57+
if (formError) {
58+
formError.textContent = "";
59+
}
60+
61+
// eslint-disable-next-line unicorn/prefer-spread
62+
const services: string[] = Array.from(
63+
document.querySelectorAll<HTMLInputElement>(
64+
'input[type="checkbox"]:checked',
65+
),
66+
).map((checkbox: HTMLInputElement) => checkbox.id);
67+
68+
const birthLocation =
69+
document.querySelector<HTMLInputElement>(
70+
'input[name="birth-location"]:checked',
71+
)?.id ?? "n/a";
72+
73+
const formData: DoulaMatchFormRequest = {
74+
name: contactName?.value ?? "",
75+
phone: phone?.value ?? "",
76+
email: email?.value ?? "",
77+
zipcode: zipcode?.value ?? "",
78+
estimatedDueDate: {
79+
month: month?.value ?? "",
80+
day: day?.value ?? "",
81+
year: year?.value ?? "",
82+
},
83+
services,
84+
birthLocation,
85+
otherInfo: otherInfo?.value ?? "",
86+
};
87+
88+
try {
89+
// console.log(formData);
90+
await sendMatchForm(formData);
91+
} catch (error) {
92+
console.error("Failed to send match form:", error);
93+
if (formError) {
94+
formError.textContent =
95+
"Sorry, there was an error sending your message. Please try again later.";
96+
}
97+
if (submitButton) {
98+
submitButton.disabled = false;
99+
submitButton.textContent = "Submit Information";
100+
}
101+
}
102+
};
103+
104+
if (submitButton) {
105+
submitButton.addEventListener("click", (event: Event) => {
106+
event.preventDefault();
107+
event.stopPropagation();
108+
void doSubmit();
109+
});
110+
}
111+
112+
const birthDoulaCheckbox =
113+
document.querySelector<HTMLInputElement>("#birth-doula");
114+
const birthLocationFieldset = document.querySelector<HTMLFieldSetElement>(
115+
"#birth-location-fieldset",
116+
);
117+
118+
if (birthDoulaCheckbox && birthLocationFieldset) {
119+
birthDoulaCheckbox.addEventListener("change", () => {
120+
birthLocationFieldset.style.display = birthDoulaCheckbox.checked
121+
? "grid"
122+
: "none";
123+
});
124+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Thank You for Your Match Request"
3+
layout: "thank-you-for-your-match-request"
4+
---

hugo/layouts/contact-us/single.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ <h1 class="contact-us-page__title">{{ .Title }}</h1>
1010

1111
<div class="callout">
1212
<svg
13-
class="callout__icon"
14-
viewBox="0 0 64 64"
13+
xmlns="http://www.w3.org/2000/svg"
1514
fill="none"
16-
xmlns="http://www.w3.org/2000/svg">
15+
class="callout__icon"
16+
viewBox="0 0 64 64">
1717
<path
18-
d="M32 5.99973C26.8577 5.99973 21.8309 7.5246 17.5552 10.3815C13.2795 13.2384 9.94702 17.2991 7.97914 22.05C6.01127 26.8008 5.49638 32.0286 6.49959 37.0721C7.50281 42.1156 9.97907 46.7483 13.6152 50.3845C17.2514 54.0207 21.8842 56.4969 26.9277 57.5001C31.9712 58.5034 37.1989 57.9885 41.9498 56.0206C46.7007 54.0527 50.7613 50.7202 53.6182 46.4445C56.4751 42.1689 58 37.142 58 31.9997C57.9927 25.1063 55.2511 18.4974 50.3767 13.623C45.5024 8.74862 38.8934 6.007 32 5.99973ZM32 53.9997C27.6488 53.9997 23.3953 52.7094 19.7775 50.2921C16.1596 47.8747 13.3398 44.4387 11.6747 40.4188C10.0095 36.3988 9.57386 31.9753 10.4227 27.7077C11.2716 23.4402 13.3669 19.5201 16.4437 16.4434C19.5204 13.3666 23.4404 11.2713 27.708 10.4224C31.9756 9.57357 36.3991 10.0092 40.419 11.6744C44.439 13.3395 47.875 16.1593 50.2923 19.7772C52.7097 23.3951 54 27.6485 54 31.9997C53.9934 37.8325 51.6734 43.4244 47.5491 47.5488C43.4247 51.6731 37.8327 53.9931 32 53.9997ZM30 33.9997V19.9997C30 19.4693 30.2107 18.9606 30.5858 18.5855C30.9609 18.2104 31.4696 17.9997 32 17.9997C32.5304 17.9997 33.0392 18.2104 33.4142 18.5855C33.7893 18.9606 34 19.4693 34 19.9997V33.9997C34 34.5302 33.7893 35.0389 33.4142 35.4139C33.0392 35.789 32.5304 35.9997 32 35.9997C31.4696 35.9997 30.9609 35.789 30.5858 35.4139C30.2107 35.0389 30 34.5302 30 33.9997ZM35 42.9997C35 43.5931 34.8241 44.1731 34.4944 44.6664C34.1648 45.1598 33.6962 45.5443 33.1481 45.7714C32.5999 45.9984 31.9967 46.0578 31.4147 45.9421C30.8328 45.8263 30.2982 45.5406 29.8787 45.121C29.4591 44.7015 29.1734 44.1669 29.0577 43.585C28.9419 43.0031 29.0013 42.3999 29.2284 41.8517C29.4554 41.3035 29.84 40.835 30.3333 40.5053C30.8266 40.1757 31.4067 39.9997 32 39.9997C32.7957 39.9997 33.5587 40.3158 34.1213 40.8784C34.6839 41.441 35 42.2041 35 42.9997Z"
19-
fill="var(--teal-500)" />
18+
fill="var(--teal-500)"
19+
d="M32 6a26 26 0 1 0 26 26A26.027 26.027 0 0 0 32 6Zm0 48a22 22 0 1 1 22-22 22.025 22.025 0 0 1-22 22Zm-2-20V20a2 2 0 0 1 4 0v14a2 2 0 1 1-4 0Zm5 9a3 3 0 1 1-5.999 0A3 3 0 0 1 35 43Z" />
2020
</svg>
2121
<p class="callout__text">
2222
Looking for information about Doula Support? Use our

hugo/layouts/find-a-doula/list.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ <h1 style="view-transition-name: title-{{ $searchType }}">
4343
});
4444
</script>
4545
{{ end }}
46+
{{ if in .Permalink "/match/" }}
47+
{{ $opts := dict "targetPath" "doula-match-form.js" }}
48+
{{ $built := resources.Get "ts/doula-match-form.ts" | js.Build $opts | resources.Minify | resources.Fingerprint }}
49+
<script
50+
type="text/javascript"
51+
src="{{ $built.RelPermalink }}"
52+
defer></script>
53+
{{ end }}
4654
{{ end }}

hugo/layouts/partials/find-a-doula/match.html

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,88 @@
11
<p>Answer a few questions to find a doula who matches your needs.</p>
22
<div class="form-container">
3-
<form class="form">
3+
<form
4+
class="form"
5+
novalidate
6+
data-api-url="{{ if hugo.IsProduction }}
7+
/api/doula-match-form
8+
{{ else }}
9+
http://127.0.0.1:5001/doula-cooperative/us-central1/doulaMatchForm
10+
{{ end }}">
411
<div class="form-group single-column">
512
<label class="form__label " for="name">Name (first and last)</label>
6-
<input class="form__input " type="text" id="name" name="name" />
13+
<input
14+
class="form__input "
15+
type="text"
16+
id="name"
17+
name="name"
18+
autocomplete="name" />
719
</div>
820

921
<div class="form__group single-column">
1022
<label class="form__label" for="phone">Phone (required)</label>
11-
<input class="form__input" type="tel" id="phone" name="phone" />
23+
<input
24+
class="form__input"
25+
type="tel"
26+
id="phone"
27+
name="phone"
28+
autocomplete="tel" />
1229
</div>
1330

1431
<div class="form__group single-column">
1532
<label class="form__label" for="email">Email (required)</label>
16-
<input class="form__input" type="email" id="email" name="email" />
33+
<input
34+
class="form__input"
35+
type="email"
36+
id="email"
37+
name="email"
38+
autocomplete="email" />
1739
</div>
1840

1941
<div class="form__group single-column">
2042
<label class="form__label" for="zipcode">ZIP Code (required)</label>
21-
<input class="form__input" type="text" id="zipcode" name="zipcode" />
43+
<input
44+
class="form__input"
45+
type="text"
46+
id="zipcode"
47+
name="zipcode"
48+
autocomplete="postal-code" />
2249
</div>
2350

2451
<fieldset class="full-width">
2552
<legend>Baby's estimated date of arrival or birthdate</legend>
2653
<div class="date-grid">
2754
<div class="form-group single-column">
2855
<label class="form__label" for="month">Month</label>
29-
<input class="form__input" type="text" id="month" name="month" />
56+
<input
57+
class="form__input"
58+
type="text"
59+
id="month"
60+
name="month"
61+
autocomplete="off" />
3062
</div>
3163
<div class="form-group single-column">
3264
<label class="form__label" for="day">Day</label>
33-
<input class="form__input" type="text" id="day" name="day" />
65+
<input
66+
class="form__input"
67+
type="text"
68+
id="day"
69+
name="day"
70+
autocomplete="off" />
3471
</div>
3572
<div class="form-group single-column">
3673
<label class="form__label" for="year">Year</label>
37-
<input class="form__input" type="text" id="year" name="year" />
74+
<input
75+
class="form__input"
76+
type="text"
77+
id="year"
78+
name="year"
79+
autocomplete="off" />
3880
</div>
3981
</div>
4082
</fieldset>
4183

4284
<fieldset>
43-
<legend>I am a parent looking for...</legend>
85+
<legend>I am looking for...</legend>
4486
<div class="input-grid">
4587
<input class="form__input" type="checkbox" id="birth-doula" /><label
4688
class="form__label"
@@ -55,14 +97,20 @@
5597
for="postpartum-doula"
5698
>postpartum doula support</label
5799
>
58-
<input class="form__input" type="checkbox" id="lactation" /><label
100+
<input
101+
class="form__input"
102+
type="checkbox"
103+
id="lactation-support" /><label
59104
class="form__label"
60-
for="lactation"
105+
for="lactation-support"
61106
>lactation support</label
62107
>
63-
<input class="form__input" type="checkbox" id="classes" /><label
108+
<input
109+
class="form__input"
110+
type="checkbox"
111+
id="childbirth-classes" /><label
64112
class="form__label"
65-
for="classes"
113+
for="childbirth-classes"
66114
>childbirth classes</label
67115
>
68116
<input
@@ -76,7 +124,7 @@
76124
</div>
77125
</fieldset>
78126

79-
<fieldset>
127+
<fieldset id="birth-location-fieldset" style="display: none;">
80128
<legend>My birth is planned at...</legend>
81129
<div class="input-grid">
82130
<input
@@ -131,40 +179,9 @@
131179
rows="5"></textarea>
132180
</div>
133181

182+
<div id="form-error" class="form__error-message" aria-live="polite"></div>
134183
<button type="submit" class="button">Submit Information</button>
135184
</form>
136185

137-
<div class="callout">
138-
<svg
139-
class="callout__icon"
140-
viewBox="0 0 64 64"
141-
fill="none"
142-
xmlns="http://www.w3.org/2000/svg">
143-
<path
144-
d="M35 45C35 45.5933 34.8241 46.1734 34.4944 46.6667C34.1648 47.1601 33.6962 47.5446 33.1481 47.7716C32.5999 47.9987 31.9967 48.0581 31.4147 47.9424C30.8328 47.8266 30.2982 47.5409 29.8787 47.1213C29.4591 46.7018 29.1734 46.1672 29.0577 45.5853C28.9419 45.0033 29.0013 44.4001 29.2284 43.852C29.4554 43.3038 29.84 42.8352 30.3333 42.5056C30.8266 42.1759 31.4067 42 32 42C32.7957 42 33.5587 42.3161 34.1213 42.8787C34.6839 43.4413 35 44.2043 35 45ZM32 18C26.485 18 22 22.0375 22 27V28C22 28.5304 22.2107 29.0391 22.5858 29.4142C22.9609 29.7893 23.4696 30 24 30C24.5304 30 25.0392 29.7893 25.4142 29.4142C25.7893 29.0391 26 28.5304 26 28V27C26 24.25 28.6925 22 32 22C35.3075 22 38 24.25 38 27C38 29.75 35.3075 32 32 32C31.4696 32 30.9609 32.2107 30.5858 32.5858C30.2107 32.9609 30 33.4696 30 34V36C30 36.5304 30.2107 37.0391 30.5858 37.4142C30.9609 37.7893 31.4696 38 32 38C32.5304 38 33.0392 37.7893 33.4142 37.4142C33.7893 37.0391 34 36.5304 34 36V35.82C38.56 34.9825 42 31.345 42 27C42 22.0375 37.515 18 32 18ZM58 32C58 37.1423 56.4751 42.1691 53.6182 46.4448C50.7613 50.7205 46.7007 54.053 41.9498 56.0209C37.1989 57.9887 31.9712 58.5036 26.9277 57.5004C21.8842 56.4972 17.2514 54.0209 13.6152 50.3848C9.97907 46.7486 7.50281 42.1159 6.49959 37.0723C5.49638 32.0288 6.01127 26.8011 7.97914 22.0502C9.94702 17.2994 13.2795 13.2387 17.5552 10.3818C21.8309 7.52487 26.8577 6 32 6C38.8934 6.00728 45.5024 8.74889 50.3767 13.6233C55.2511 18.4976 57.9927 25.1066 58 32ZM54 32C54 27.6488 52.7097 23.3953 50.2923 19.7775C47.875 16.1596 44.439 13.3398 40.419 11.6747C36.3991 10.0095 31.9756 9.57385 27.708 10.4227C23.4404 11.2716 19.5204 13.3669 16.4437 16.4437C13.3669 19.5204 11.2716 23.4404 10.4227 27.708C9.57386 31.9756 10.0095 36.3991 11.6747 40.419C13.3398 44.439 16.1596 47.8749 19.7775 50.2923C23.3953 52.7097 27.6488 54 32 54C37.8327 53.9934 43.4247 51.6734 47.5491 47.549C51.6734 43.4247 53.9934 37.8327 54 32Z"
145-
fill="#1B5665" />
146-
</svg>
147-
148-
<p class="callout__text">What happens after I submit the form?</p>
149-
</div>
150-
151-
<div class="info-content">
152-
<p>
153-
If you're looking for doula support, your information will be shared with
154-
cooperative members.
155-
</p>
156-
<p>
157-
Doulas who are available around your estimated arrival date will email you
158-
to set up a complimentary consultation. You may receive 2 or 20 emails
159-
depending on who is available.
160-
</p>
161-
<p>
162-
It is common for pregnant people to meet with a few doulas to determine
163-
who they'd feel most comfortable working with.
164-
</p>
165-
<p>
166-
When you're ready to hire one of the doulas you've met with, you can
167-
contact them directly to sign their client agreement and pay their fee.
168-
</p>
169-
</div>
186+
{{ partial "find-a-doula/what-happens-next.html" . }}
170187
</div>

0 commit comments

Comments
 (0)