+
+ );
+}
diff --git a/src/forms/ma-court-order/steps/ContactInfoStep.tsx b/src/pages/forms/court-order-ma/_steps/ContactInfoStep.tsx
similarity index 57%
rename from src/forms/ma-court-order/steps/ContactInfoStep.tsx
rename to src/pages/forms/court-order-ma/_steps/ContactInfoStep.tsx
index 01750e10..5e654985 100644
--- a/src/forms/ma-court-order/steps/ContactInfoStep.tsx
+++ b/src/pages/forms/court-order-ma/_steps/ContactInfoStep.tsx
@@ -1,7 +1,6 @@
-import type { StepComponentProps } from "../../../components/react/forms/FormContainer";
-import { FormStep } from "../../../components/react/forms/FormStep";
+import { FormStep } from "@/components/react/forms/FormStep";
-export function ContactInfoStep(_props: StepComponentProps) {
+export function ContactInfoStep() {
return (
+ {/* TODO: Add MemorableDateField component here */}
+
Form fields will go here...
+
+ );
+}
diff --git a/src/forms/ma-court-order/steps/FeeWaiverStep.tsx b/src/pages/forms/court-order-ma/_steps/FeeWaiverStep.tsx
similarity index 63%
rename from src/forms/ma-court-order/steps/FeeWaiverStep.tsx
rename to src/pages/forms/court-order-ma/_steps/FeeWaiverStep.tsx
index 207cafd7..1cb6640f 100644
--- a/src/forms/ma-court-order/steps/FeeWaiverStep.tsx
+++ b/src/pages/forms/court-order-ma/_steps/FeeWaiverStep.tsx
@@ -1,7 +1,6 @@
-import type { StepComponentProps } from "../../../components/react/forms/FormContainer";
-import { FormStep } from "../../../components/react/forms/FormStep";
+import { FormStep } from "@/components/react/forms/FormStep";
-export function FeeWaiverStep(_props: StepComponentProps) {
+export function FeeWaiverStep() {
return (
+ {/* TODO: Add YesNoField and conditional subsection */}
+
Form fields will go here...
+
+ );
+}
diff --git a/src/forms/ma-court-order/steps/PronounsStep.tsx b/src/pages/forms/court-order-ma/_steps/PronounsStep.tsx
similarity index 51%
rename from src/forms/ma-court-order/steps/PronounsStep.tsx
rename to src/pages/forms/court-order-ma/_steps/PronounsStep.tsx
index 7ecad417..e7a70146 100644
--- a/src/forms/ma-court-order/steps/PronounsStep.tsx
+++ b/src/pages/forms/court-order-ma/_steps/PronounsStep.tsx
@@ -1,7 +1,6 @@
-import type { StepComponentProps } from "../../../components/react/forms/FormContainer";
-import { FormStep } from "../../../components/react/forms/FormStep";
+import { FormStep } from "@/components/react/forms/FormStep";
-export function PronounsStep(_props: StepComponentProps) {
+export function PronounsStep() {
return (
{/* TODO: Add YesNoField and conditional PronounSelectField */}
diff --git a/src/pages/forms/court-order-ma/_steps/ReasonStep.tsx b/src/pages/forms/court-order-ma/_steps/ReasonStep.tsx
new file mode 100644
index 00000000..7fdb6e31
--- /dev/null
+++ b/src/pages/forms/court-order-ma/_steps/ReasonStep.tsx
@@ -0,0 +1,10 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+
+export function ReasonStep() {
+ return (
+
+ {/* TODO: Add LongTextField and Banner components here */}
+
Form fields will go here...
+
+ );
+}
diff --git a/src/forms/ma-court-order/steps/ResidentialAddressStep.tsx b/src/pages/forms/court-order-ma/_steps/ResidentialAddressStep.tsx
similarity index 59%
rename from src/forms/ma-court-order/steps/ResidentialAddressStep.tsx
rename to src/pages/forms/court-order-ma/_steps/ResidentialAddressStep.tsx
index d41fe8a6..e1c75629 100644
--- a/src/forms/ma-court-order/steps/ResidentialAddressStep.tsx
+++ b/src/pages/forms/court-order-ma/_steps/ResidentialAddressStep.tsx
@@ -1,7 +1,6 @@
-import type { StepComponentProps } from "../../../components/react/forms/FormContainer";
-import { FormStep } from "../../../components/react/forms/FormStep";
+import { FormStep } from "@/components/react/forms/FormStep";
-export function ResidentialAddressStep(_props: StepComponentProps) {
+export function ResidentialAddressStep() {
return (
+ {/* TODO: Add YesNoField and conditional Banner */}
+
Form fields will go here...
+
+ );
+}
diff --git a/src/forms/ma-court-order/steps/WaivePublicationStep.tsx b/src/pages/forms/court-order-ma/_steps/WaivePublicationStep.tsx
similarity index 51%
rename from src/forms/ma-court-order/steps/WaivePublicationStep.tsx
rename to src/pages/forms/court-order-ma/_steps/WaivePublicationStep.tsx
index e99c5004..d0012bc2 100644
--- a/src/forms/ma-court-order/steps/WaivePublicationStep.tsx
+++ b/src/pages/forms/court-order-ma/_steps/WaivePublicationStep.tsx
@@ -1,10 +1,9 @@
-import type { StepComponentProps } from "../../../components/react/forms/FormContainer";
-import { FormStep } from "../../../components/react/forms/FormStep";
+import { FormStep } from "@/components/react/forms/FormStep";
-export function WaivePublicationStep(_props: StepComponentProps) {
+export function WaivePublicationStep() {
return (
{/* TODO: Add YesNoField, conditional LongTextField and Banner */}
diff --git a/src/pages/forms/[slug].astro b/src/pages/forms/court-order-ma/index.astro
similarity index 68%
rename from src/pages/forms/[slug].astro
rename to src/pages/forms/court-order-ma/index.astro
index 3f58fa20..12fcf2e2 100644
--- a/src/pages/forms/[slug].astro
+++ b/src/pages/forms/court-order-ma/index.astro
@@ -3,36 +3,29 @@ import type { SanityDocument } from "@sanity/client";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import utc from "dayjs/plugin/utc";
-import { FormRenderer } from "~/components/react/forms/FormRenderer/FormRenderer";
-import BaseLayout from "~/layouts/BaseLayout.astro";
-import { loadQuery } from "~/sanity/lib/loadQuery";
+import BaseLayout from "@/layouts/BaseLayout.astro";
+import { loadQuery } from "@/sanity/lib/loadQuery";
+import { MaCourtOrderForm } from "./_Form";
dayjs.extend(utc);
dayjs.extend(localizedFormat);
-const { slug } = Astro.params;
-
-if (!slug) return Astro.redirect("/404");
-
const { data: form } = await loadQuery({
query: `*[_type == "form" && slug.current == $slug][0]`,
params: {
- slug,
+ slug: "court-order-ma",
},
});
-
-if (!form) return Astro.redirect("/404");
---
-
- Form last edited on {" "}
+ Form last revised on {" "}
.
diff --git a/src/pages/forms/index.astro b/src/pages/forms/index.astro
index a7e37f8d..a41249a7 100644
--- a/src/pages/forms/index.astro
+++ b/src/pages/forms/index.astro
@@ -1,8 +1,8 @@
---
import type { SanityDocument } from "@sanity/client";
-import PageHeader from "~/components/PageHeader.astro";
-import BaseLayout from "~/layouts/BaseLayout.astro";
-import { loadQuery } from "~/sanity/lib/loadQuery";
+import PageHeader from "@/components/PageHeader.astro";
+import BaseLayout from "@/layouts/BaseLayout.astro";
+import { loadQuery } from "@/sanity/lib/loadQuery";
const { params } = Astro.props;
diff --git a/src/pages/forms/social-security/_Form.tsx b/src/pages/forms/social-security/_Form.tsx
new file mode 100644
index 00000000..8c3cdcd4
--- /dev/null
+++ b/src/pages/forms/social-security/_Form.tsx
@@ -0,0 +1,136 @@
+import type { FormEvent } from "react";
+import {
+ FormContainer,
+ type Step,
+} from "@/components/react/forms/FormContainer";
+import type { FieldName, FieldType } from "@/constants/fields";
+import { downloadMergedPdf } from "@/pdfs/utils/downloadMergedPdf";
+import { loadPdfs } from "@/pdfs/utils/loadPdfs";
+import { useForm } from "@/utils/useForm";
+import { AddressStep } from "./_steps/AddressStep";
+import { BirthplaceStep } from "./_steps/BirthplaceStep";
+import { CitizenshipStep } from "./_steps/CitizenshipStep";
+import { DateOfBirthStep } from "./_steps/DateOfBirthStep";
+import { EthnicityStep } from "./_steps/EthnicityStep";
+import { FilingForSomeoneElseStep } from "./_steps/FilingForSomeoneElseStep";
+import { NewNameStep } from "./_steps/NewNameStep";
+import { OldNameStep } from "./_steps/OldNameStep";
+import { ParentOneNameStep } from "./_steps/ParentOneNameStep";
+import { ParentTwoNameStep } from "./_steps/ParentTwoNameStep";
+import { PhoneNumberStep } from "./_steps/PhoneNumberStep";
+import { PreviousNamesStep } from "./_steps/PreviousNamesStep";
+import { PreviousSocialSecurityCardStep } from "./_steps/PreviousSocialSecurityCardStep";
+import { RaceStep } from "./_steps/RaceStep";
+import { SexStep } from "./_steps/SexStep";
+
+const FORM_FIELDS: FieldName[] = [
+ "newFirstName",
+ "newMiddleName",
+ "newLastName",
+ "oldFirstName",
+ "oldMiddleName",
+ "oldLastName",
+ "previousLegalNames",
+ "birthplaceCity",
+ "birthplaceState",
+ "birthplaceCountry",
+ "dateOfBirth",
+ "citizenshipStatus",
+ "isHispanicOrLatino",
+ "race",
+ "sexAssignedAtBirth",
+ "mothersFirstName",
+ "mothersMiddleName",
+ "mothersLastName",
+ "fathersFirstName",
+ "fathersMiddleName",
+ "fathersLastName",
+ "hasPreviousSocialSecurityCard",
+ "previousSocialSecurityCardFirstName",
+ "previousSocialSecurityCardMiddleName",
+ "previousSocialSecurityCardLastName",
+ "isCurrentlyUnhoused",
+ "isFilingForSomeoneElse",
+ "relationshipToFilingFor",
+ "relationshipToFilingForOther",
+] as const;
+
+type FormData = {
+ [K in (typeof FORM_FIELDS)[number]]: FieldType;
+};
+
+const STEPS: readonly Step[] = [
+ { id: "new-name", component: NewNameStep },
+ { id: "old-name", component: OldNameStep },
+ { id: "previous-names", component: PreviousNamesStep },
+ { id: "birthplace", component: BirthplaceStep },
+ { id: "date-of-birth", component: DateOfBirthStep },
+ { id: "citizenship", component: CitizenshipStep },
+ { id: "ethnicity", component: EthnicityStep },
+ { id: "race", component: RaceStep },
+ { id: "sex", component: SexStep },
+ { id: "parent-one", component: ParentOneNameStep },
+ { id: "parent-two", component: ParentTwoNameStep },
+ {
+ id: "previous-social-security-card",
+ component: PreviousSocialSecurityCardStep,
+ },
+ { id: "phone-number", component: PhoneNumberStep },
+ { id: "address", component: AddressStep },
+ { id: "filing-for-someone-else", component: FilingForSomeoneElseStep },
+];
+
+export function SocialSecurityForm({
+ title,
+ description,
+}: {
+ title: string;
+ description: string;
+}) {
+ const { onSubmit, ...form } = useForm(FORM_FIELDS);
+
+ const handleSubmit = async (event: FormEvent) => {
+ event.preventDefault();
+
+ try {
+ const pdfs = await loadPdfs([
+ { pdfId: "ss5-application-for-social-security-card" },
+ ]);
+
+ await downloadMergedPdf({
+ title: "Social Security Card",
+ instructions: [
+ "Please review all documents carefully.",
+ "Fill in your social security number—for security, Namesake never asks for this.",
+ "Make an Appointment with a Social Security Administration Office.",
+ "Remember to bring certified copies of your completed court order, along with other required supporting documents.",
+ form.watch("citizenshipStatus") === "legalAlienNotAllowedToWork" ||
+ form.watch("citizenshipStatus") === "other"
+ ? "You must provide a document from a U.S. Federal, State, or local government agency that explains why you need a Social Security number and that you meet all the requirements for the government benefit."
+ : "",
+ ],
+ pdfs,
+ userData: form.getValues(),
+ });
+
+ // Save form to user documents
+ // await saveDocuments({ pdfIds: pdfs.map((pdf) => pdf.id) });
+
+ // Save encrypted responses
+ await onSubmit();
+ } catch (_error) {
+ // TODO: Error handling
+ console.error(_error);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/AddressStep.tsx b/src/pages/forms/social-security/_steps/AddressStep.tsx
new file mode 100644
index 00000000..219058df
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/AddressStep.tsx
@@ -0,0 +1,10 @@
+import { AddressField } from "@/components/react/forms/AddressField";
+import { FormStep } from "@/components/react/forms/FormStep";
+
+export function AddressStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/BirthplaceStep.tsx b/src/pages/forms/social-security/_steps/BirthplaceStep.tsx
new file mode 100644
index 00000000..d5f09f06
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/BirthplaceStep.tsx
@@ -0,0 +1,34 @@
+import { ComboBoxField } from "@/components/react/forms/ComboBoxField";
+import { FormStep } from "@/components/react/forms/FormStep";
+import { ShortTextField } from "@/components/react/forms/ShortTextField";
+import { COUNTRIES } from "@/constants/countries";
+import { BIRTHPLACES } from "@/constants/jurisdictions";
+
+export function BirthplaceStep() {
+ return (
+
+
+ ({
+ label,
+ value,
+ }))}
+ />
+ value !== "US")
+ .map(([value, label]) => ({
+ label,
+ value,
+ }))}
+ />
+ {/* TODO: Add conditional ComboBoxField for birthplaceState when country is US */}
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/CitizenshipStep.tsx b/src/pages/forms/social-security/_steps/CitizenshipStep.tsx
new file mode 100644
index 00000000..bd206b1f
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/CitizenshipStep.tsx
@@ -0,0 +1,32 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { RadioGroupField } from "@/components/react/forms/RadioGroupField";
+
+export function CitizenshipStep() {
+ return (
+
+
+ {/* TODO: Add conditional Banner for legalAlienNotAllowedToWork and other */}
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/DateOfBirthStep.tsx b/src/pages/forms/social-security/_steps/DateOfBirthStep.tsx
new file mode 100644
index 00000000..4eed08ff
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/DateOfBirthStep.tsx
@@ -0,0 +1,10 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { MemorableDateField } from "@/components/react/forms/MemorableDateField";
+
+export function DateOfBirthStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/EthnicityStep.tsx b/src/pages/forms/social-security/_steps/EthnicityStep.tsx
new file mode 100644
index 00000000..67f861e9
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/EthnicityStep.tsx
@@ -0,0 +1,24 @@
+import { Banner } from "@/components/react/common/Banner";
+import { FormStep } from "@/components/react/forms/FormStep";
+import { YesNoField } from "@/components/react/forms/YesNoField";
+
+export function EthnicityStep() {
+ return (
+
+
+
+ The Social Security Administration requests this information for
+ research and statistical purposes.
+
+
+ );
+}
diff --git a/src/forms/social-security/steps/FilingForSomeoneElseStep.tsx b/src/pages/forms/social-security/_steps/FilingForSomeoneElseStep.tsx
similarity index 57%
rename from src/forms/social-security/steps/FilingForSomeoneElseStep.tsx
rename to src/pages/forms/social-security/_steps/FilingForSomeoneElseStep.tsx
index 2da5fcbe..621a713e 100644
--- a/src/forms/social-security/steps/FilingForSomeoneElseStep.tsx
+++ b/src/pages/forms/social-security/_steps/FilingForSomeoneElseStep.tsx
@@ -1,10 +1,16 @@
-import type { StepComponentProps } from "../../../components/react/forms/FormContainer";
-import { FormStep } from "../../../components/react/forms/FormStep";
+import { FormStep } from "@/components/react/forms/FormStep";
+import { YesNoField } from "@/components/react/forms/YesNoField";
-export function FilingForSomeoneElseStep(_props: StepComponentProps) {
+export function FilingForSomeoneElseStep() {
return (
- {/* TODO: Add YesNoField for isFilingForSomeoneElse */}
+
{/* TODO: Add conditional subsection if isFilingForSomeoneElse is true */}
{/* Subsection: "What is your relationship to the person you are filing for?" */}
{/* TODO: Add RadioGroupField for relationshipToFilingFor */}
diff --git a/src/pages/forms/social-security/_steps/NewNameStep.tsx b/src/pages/forms/social-security/_steps/NewNameStep.tsx
new file mode 100644
index 00000000..28937857
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/NewNameStep.tsx
@@ -0,0 +1,13 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { NameField } from "@/components/react/forms/NameField";
+
+export function NewNameStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/OldNameStep.tsx b/src/pages/forms/social-security/_steps/OldNameStep.tsx
new file mode 100644
index 00000000..510b1d15
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/OldNameStep.tsx
@@ -0,0 +1,13 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { NameField } from "@/components/react/forms/NameField";
+
+export function OldNameStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/ParentOneNameStep.tsx b/src/pages/forms/social-security/_steps/ParentOneNameStep.tsx
new file mode 100644
index 00000000..8f2850f7
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/ParentOneNameStep.tsx
@@ -0,0 +1,15 @@
+import { Banner } from "@/components/react/common/Banner";
+import { FormStep } from "@/components/react/forms/FormStep";
+import { NameField } from "@/components/react/forms/NameField";
+
+export function ParentOneNameStep() {
+ return (
+
+
+
+ Use your parent's name at their birth, before marriage. This is also
+ known as a maiden name.
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/ParentTwoNameStep.tsx b/src/pages/forms/social-security/_steps/ParentTwoNameStep.tsx
new file mode 100644
index 00000000..6e5c3c65
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/ParentTwoNameStep.tsx
@@ -0,0 +1,10 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { NameField } from "@/components/react/forms/NameField";
+
+export function ParentTwoNameStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/PhoneNumberStep.tsx b/src/pages/forms/social-security/_steps/PhoneNumberStep.tsx
new file mode 100644
index 00000000..283d2001
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/PhoneNumberStep.tsx
@@ -0,0 +1,10 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { PhoneField } from "@/components/react/forms/PhoneField";
+
+export function PhoneNumberStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/PreviousNamesStep.tsx b/src/pages/forms/social-security/_steps/PreviousNamesStep.tsx
new file mode 100644
index 00000000..e740974d
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/PreviousNamesStep.tsx
@@ -0,0 +1,10 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { LongTextField } from "@/components/react/forms/LongTextField";
+
+export function PreviousNamesStep() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/PreviousSocialSecurityCardStep.tsx b/src/pages/forms/social-security/_steps/PreviousSocialSecurityCardStep.tsx
new file mode 100644
index 00000000..69a8bac0
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/PreviousSocialSecurityCardStep.tsx
@@ -0,0 +1,22 @@
+import { FormStep } from "@/components/react/forms/FormStep";
+import { YesNoField } from "@/components/react/forms/YesNoField";
+
+export function PreviousSocialSecurityCardStep() {
+ return (
+
+
+ {/* TODO: Add conditional subsection if hasPreviousSocialSecurityCard is true */}
+ {/* Subsection: "What is the name shown on your most recent Social Security card?" */}
+ {/* TODO: Add NameField for previousSocialSecurityCardName */}
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/RaceStep.tsx b/src/pages/forms/social-security/_steps/RaceStep.tsx
new file mode 100644
index 00000000..85029140
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/RaceStep.tsx
@@ -0,0 +1,52 @@
+import { Banner } from "@/components/react/common/Banner";
+import { CheckboxGroupField } from "@/components/react/forms/CheckboxGroupField";
+import { FormStep } from "@/components/react/forms/FormStep";
+
+export function RaceStep() {
+ return (
+
+
+
+ The Social Security Administration requests this information for
+ research and statistical purposes.
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/_steps/SexStep.tsx b/src/pages/forms/social-security/_steps/SexStep.tsx
new file mode 100644
index 00000000..df5b52c6
--- /dev/null
+++ b/src/pages/forms/social-security/_steps/SexStep.tsx
@@ -0,0 +1,57 @@
+import { Banner } from "@/components/react/common/Banner";
+import { FormStep } from "@/components/react/forms/FormStep";
+import { RadioGroupField } from "@/components/react/forms/RadioGroupField";
+
+export function SexStep() {
+ return (
+
+
+
+
+
+ Namesake recommends selecting the same gender marker that the Social
+ Security Administration already has on file.
+ {" "}
+ The gender marker is not shown on your Social Security card.
+
+
+ As of January 20, 2025, the Trump administration has directed the
+ Social Security Administration to{" "}
+
+ stop processing gender marker updates
+ {" "}
+ associated with social security records. Per{" "}
+
+ Lambda Legal
+
+ , if you apply for a gender marker change with Social Security now,
+ you will likely be told that they cannot process your request.
+
+
+
+ );
+}
diff --git a/src/pages/forms/social-security/index.astro b/src/pages/forms/social-security/index.astro
new file mode 100644
index 00000000..bf8cd8d5
--- /dev/null
+++ b/src/pages/forms/social-security/index.astro
@@ -0,0 +1,42 @@
+---
+import type { SanityDocument } from "@sanity/client";
+import dayjs from "dayjs";
+import localizedFormat from "dayjs/plugin/localizedFormat";
+import utc from "dayjs/plugin/utc";
+import BaseLayout from "@/layouts/BaseLayout.astro";
+import { loadQuery } from "@/sanity/lib/loadQuery";
+import { SocialSecurityForm } from "./_Form";
+
+dayjs.extend(utc);
+dayjs.extend(localizedFormat);
+
+const { data: form } = await loadQuery({
+ query: `*[_type == "form" && slug.current == $slug][0]`,
+ params: {
+ slug: "social-security",
+ },
+});
+---
+
+
+
+
+ Form last revised on {" "}
+ .
+
+
+
+
diff --git a/src/pages/guides/[slug].astro b/src/pages/guides/[slug].astro
index 80cc2d67..02a1911f 100644
--- a/src/pages/guides/[slug].astro
+++ b/src/pages/guides/[slug].astro
@@ -3,11 +3,11 @@ import type { SanityDocument } from "@sanity/client";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import utc from "dayjs/plugin/utc";
-import TableOfContents from "~/components/TableOfContents.astro";
-import PortableText from "~/components/text/PortableText.astro";
-import ProseLayout from "~/layouts/ProseLayout.astro";
-import { extractHeadings } from "~/sanity/lib/extractHeadings";
-import { loadQuery } from "~/sanity/lib/loadQuery";
+import TableOfContents from "@/components/TableOfContents.astro";
+import PortableText from "@/components/text/PortableText.astro";
+import ProseLayout from "@/layouts/ProseLayout.astro";
+import { extractHeadings } from "@/sanity/lib/extractHeadings";
+import { loadQuery } from "@/sanity/lib/loadQuery";
dayjs.extend(utc);
dayjs.extend(localizedFormat);
diff --git a/src/pages/guides/index.astro b/src/pages/guides/index.astro
index 215b23eb..0e1f0d40 100644
--- a/src/pages/guides/index.astro
+++ b/src/pages/guides/index.astro
@@ -1,8 +1,8 @@
---
import type { SanityDocument } from "@sanity/client";
-import PageHeader from "~/components/PageHeader.astro";
-import BaseLayout from "~/layouts/BaseLayout.astro";
-import { loadQuery } from "~/sanity/lib/loadQuery";
+import PageHeader from "@/components/PageHeader.astro";
+import BaseLayout from "@/layouts/BaseLayout.astro";
+import { loadQuery } from "@/sanity/lib/loadQuery";
const { params } = Astro.props;
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 86245f96..1f1c65a8 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -4,13 +4,13 @@ export const prerender = true;
import { Image } from "astro:assets";
import { RiArrowRightLine } from "@remixicon/react";
import type { SanityDocument } from "@sanity/client";
-import Partners from "~/components/Partners.astro";
-import SupportMap from "~/components/SupportMap.astro";
-import PortableText from "~/components/text/PortableText.astro";
-import siteInfo from "~/constants/site-info";
-import BaseLayout from "~/layouts/BaseLayout.astro";
-import heroIllustration from "~/pages/home/_hero-form.png";
-import { loadQuery } from "~/sanity/lib/loadQuery";
+import Partners from "@/components/Partners.astro";
+import SupportMap from "@/components/SupportMap.astro";
+import PortableText from "@/components/text/PortableText.astro";
+import siteInfo from "@/constants/site-info";
+import BaseLayout from "@/layouts/BaseLayout.astro";
+import heroIllustration from "@/pages/home/_hero-form.png";
+import { loadQuery } from "@/sanity/lib/loadQuery";
// Fetch the home page from Sanity (slug: "index" or empty slug for home)
const { data: page } = await loadQuery({
@@ -85,6 +85,7 @@ if (!page) return Astro.redirect("/404");
display: flex;
flex-direction: column;
align-items: center;
+ padding-block: var(--space-2xl);
}
@keyframes slideInUp {
diff --git a/src/pages/press.astro b/src/pages/press.astro
index 14aed6e4..2bd01f7d 100644
--- a/src/pages/press.astro
+++ b/src/pages/press.astro
@@ -1,9 +1,9 @@
---
import type { SanityDocument } from "@sanity/client";
-import ArticleLink from "~/components/ArticleLink.astro";
-import PageHeader from "~/components/PageHeader.astro";
-import BaseLayout from "~/layouts/BaseLayout.astro";
-import { loadQuery } from "~/sanity/lib/loadQuery";
+import ArticleLink from "@/components/ArticleLink.astro";
+import PageHeader from "@/components/PageHeader.astro";
+import BaseLayout from "@/layouts/BaseLayout.astro";
+import { loadQuery } from "@/sanity/lib/loadQuery";
const { params } = Astro.props;
diff --git a/src/pages/privacy.astro b/src/pages/privacy.astro
index ee7a52ec..3664497a 100644
--- a/src/pages/privacy.astro
+++ b/src/pages/privacy.astro
@@ -1,7 +1,7 @@
---
import { getEntry, render } from "astro:content";
-import TableOfContents from "~/components/TableOfContents.astro";
-import ProseLayout from "~/layouts/ProseLayout.astro";
+import TableOfContents from "@/components/TableOfContents.astro";
+import ProseLayout from "@/layouts/ProseLayout.astro";
const entry = await getEntry("policy", "privacy");
if (entry === undefined) return Astro.redirect("/404");
diff --git a/src/pages/subprocessors.astro b/src/pages/subprocessors.astro
index bb90ca67..1a016dac 100644
--- a/src/pages/subprocessors.astro
+++ b/src/pages/subprocessors.astro
@@ -1,7 +1,7 @@
---
import { getEntry, render } from "astro:content";
-import TableOfContents from "~/components/TableOfContents.astro";
-import ProseLayout from "~/layouts/ProseLayout.astro";
+import TableOfContents from "@/components/TableOfContents.astro";
+import ProseLayout from "@/layouts/ProseLayout.astro";
const entry = await getEntry("policy", "subprocessors");
if (entry === undefined) return Astro.redirect("/404");
diff --git a/src/pages/terms.astro b/src/pages/terms.astro
index 1c7c2217..b2fbbffa 100644
--- a/src/pages/terms.astro
+++ b/src/pages/terms.astro
@@ -1,7 +1,7 @@
---
import { getEntry, render } from "astro:content";
-import TableOfContents from "~/components/TableOfContents.astro";
-import ProseLayout from "~/layouts/ProseLayout.astro";
+import TableOfContents from "@/components/TableOfContents.astro";
+import ProseLayout from "@/layouts/ProseLayout.astro";
const entry = await getEntry("policy", "terms");
if (entry === undefined) return Astro.redirect("/404");
diff --git a/src/pdfs/README.md b/src/pdfs/README.md
index 9fbd1f12..16f3b565 100644
--- a/src/pdfs/README.md
+++ b/src/pdfs/README.md
@@ -16,7 +16,7 @@ Each `.pdf` file should be accompanied by a `.ts` definition of the same name co
```ts
// cjp27-petition-to-change-name-of-adult.ts
-import { definePdf } from "~/utils/pdf";
+import { definePdf } from "@/utils/pdf";
import pdf from "./cjp27-petition-to-change-name-of-adult.pdf";
export default definePdf({
diff --git a/src/pdfs/federal/__tests__/ss5-application-for-social-security-card.test.ts b/src/pdfs/federal/__tests__/ss5-application-for-social-security-card.test.ts
index 1ca4b8e2..61b450f5 100644
--- a/src/pdfs/federal/__tests__/ss5-application-for-social-security-card.test.ts
+++ b/src/pdfs/federal/__tests__/ss5-application-for-social-security-card.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { getPdfForm } from "../../utils";
+import { getPdfForm } from "@/pdfs/utils/getPdfForm";
import ss5Application from "../ss5-application-for-social-security-card";
describe("SS-5 Application for Social Security Card", () => {
diff --git a/src/pdfs/federal/ss5-application-for-social-security-card.ts b/src/pdfs/federal/ss5-application-for-social-security-card.ts
index b7644f2e..163b9f94 100644
--- a/src/pdfs/federal/ss5-application-for-social-security-card.ts
+++ b/src/pdfs/federal/ss5-application-for-social-security-card.ts
@@ -1,6 +1,6 @@
-import { definePdf } from "~/pdfs/utils";
-import { formatBirthplaceStateOrCountry } from "~/utils/formatBirthplaceStateOrCountry";
-import { formatDateMMDDYYYY } from "~/utils/formatDateMMDDYYYY";
+import { definePdf } from "@/pdfs/utils/definePdf";
+import { formatBirthplaceStateOrCountry } from "@/utils/formatBirthplaceStateOrCountry";
+import { formatDateMMDDYYYY } from "@/utils/formatDateMMDDYYYY";
import pdf from "./ss5-application-for-social-security-card.pdf";
export default definePdf({
diff --git a/src/pdfs/index.ts b/src/pdfs/index.ts
index 9649c9b3..a0b1f0aa 100644
--- a/src/pdfs/index.ts
+++ b/src/pdfs/index.ts
@@ -1,4 +1,4 @@
-import type { PDFDefinition, PDFId } from "~/constants/forms";
+import type { PDFDefinition, PDFId } from "@/constants/forms";
export async function getPdfDefinition(pdfId: PDFId) {
const pdfModules = import.meta.glob("/src/forms/*/!(*.test|utils).ts", {
diff --git a/src/pdfs/ma/__tests__/affidavit-of-indigency.test.ts b/src/pdfs/ma/__tests__/affidavit-of-indigency.test.ts
index c83f2345..ba5165ef 100644
--- a/src/pdfs/ma/__tests__/affidavit-of-indigency.test.ts
+++ b/src/pdfs/ma/__tests__/affidavit-of-indigency.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { getPdfForm } from "../../utils";
+import { getPdfForm } from "@/pdfs/utils/getPdfForm";
import affidavitOfIndigency from "../affidavit-of-indigency";
describe("Affidavit of Indigency", () => {
diff --git a/src/pdfs/ma/__tests__/cjd400-motion-to-impound.test.ts b/src/pdfs/ma/__tests__/cjd400-motion-to-impound.test.ts
index d631bb8c..a2c6c25a 100644
--- a/src/pdfs/ma/__tests__/cjd400-motion-to-impound.test.ts
+++ b/src/pdfs/ma/__tests__/cjd400-motion-to-impound.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { getPdfForm } from "../../utils";
+import { getPdfForm } from "@/pdfs/utils/getPdfForm";
import motionToImpoundRecords from "../cjd400-motion-to-impound";
describe("CJD400 Motion to Impound Records", () => {
diff --git a/src/pdfs/ma/__tests__/cjd400-motion-to-waive-publication.test.ts b/src/pdfs/ma/__tests__/cjd400-motion-to-waive-publication.test.ts
index e1a09780..949de630 100644
--- a/src/pdfs/ma/__tests__/cjd400-motion-to-waive-publication.test.ts
+++ b/src/pdfs/ma/__tests__/cjd400-motion-to-waive-publication.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { getPdfForm } from "../../utils";
+import { getPdfForm } from "@/pdfs/utils/getPdfForm";
import motionToWaivePublication from "../cjd400-motion-to-waive-publication";
describe("CJD400 Motion to Waive Publication", () => {
diff --git a/src/pdfs/ma/__tests__/cjp27-petition-to-change-name-of-adult.test.ts b/src/pdfs/ma/__tests__/cjp27-petition-to-change-name-of-adult.test.ts
index e69b66a1..fd7d0f03 100644
--- a/src/pdfs/ma/__tests__/cjp27-petition-to-change-name-of-adult.test.ts
+++ b/src/pdfs/ma/__tests__/cjp27-petition-to-change-name-of-adult.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { getPdfForm } from "../../utils";
+import { getPdfForm } from "@/pdfs/utils/getPdfForm";
import petitionToChangeNameOfAdult from "../cjp27-petition-to-change-name-of-adult";
describe("CJP27 Petition to Change Name of Adult", () => {
diff --git a/src/pdfs/ma/__tests__/cjp34-cori-and-wms-release-request.test.ts b/src/pdfs/ma/__tests__/cjp34-cori-and-wms-release-request.test.ts
index 9e70cf8f..2f9a6a91 100644
--- a/src/pdfs/ma/__tests__/cjp34-cori-and-wms-release-request.test.ts
+++ b/src/pdfs/ma/__tests__/cjp34-cori-and-wms-release-request.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { getPdfForm } from "../../utils";
+import { getPdfForm } from "@/pdfs/utils/getPdfForm";
import coriAndWmsReleaseRequest from "../cjp34-cori-and-wms-release-request";
describe("CJP34 CORI and WMS Release Request", () => {
diff --git a/src/pdfs/ma/affidavit-of-indigency.ts b/src/pdfs/ma/affidavit-of-indigency.ts
index 8c7387ac..1a133e7d 100644
--- a/src/pdfs/ma/affidavit-of-indigency.ts
+++ b/src/pdfs/ma/affidavit-of-indigency.ts
@@ -1,5 +1,5 @@
-import { definePdf } from "~/pdfs/utils";
-import { joinNames } from "~/utils/joinNames";
+import { definePdf } from "@/pdfs/utils/definePdf";
+import { joinNames } from "@/utils/joinNames";
import pdf from "./affidavit-of-indigency.pdf";
export default definePdf({
diff --git a/src/pdfs/ma/cjd400-motion-to-impound.ts b/src/pdfs/ma/cjd400-motion-to-impound.ts
index 57ad83b5..a1105747 100644
--- a/src/pdfs/ma/cjd400-motion-to-impound.ts
+++ b/src/pdfs/ma/cjd400-motion-to-impound.ts
@@ -1,5 +1,5 @@
-import { definePdf } from "~/pdfs/utils";
-import { joinNames } from "~/utils/joinNames";
+import { definePdf } from "@/pdfs/utils/definePdf";
+import { joinNames } from "@/utils/joinNames";
import pdf from "./cjd400-motion-to-waive-publication.pdf";
export default definePdf({
diff --git a/src/pdfs/ma/cjd400-motion-to-waive-publication.ts b/src/pdfs/ma/cjd400-motion-to-waive-publication.ts
index 300964ff..66f8c196 100644
--- a/src/pdfs/ma/cjd400-motion-to-waive-publication.ts
+++ b/src/pdfs/ma/cjd400-motion-to-waive-publication.ts
@@ -1,5 +1,5 @@
-import { definePdf } from "~/pdfs/utils";
-import { joinNames } from "~/utils/joinNames";
+import { definePdf } from "@/pdfs/utils/definePdf";
+import { joinNames } from "@/utils/joinNames";
import pdf from "./cjd400-motion-to-waive-publication.pdf";
export default definePdf({
diff --git a/src/pdfs/ma/cjp27-petition-to-change-name-of-adult.ts b/src/pdfs/ma/cjp27-petition-to-change-name-of-adult.ts
index a89ca945..8ea9404b 100644
--- a/src/pdfs/ma/cjp27-petition-to-change-name-of-adult.ts
+++ b/src/pdfs/ma/cjp27-petition-to-change-name-of-adult.ts
@@ -1,7 +1,7 @@
import languageNameMap from "language-name-map/map";
-import { definePdf } from "~/pdfs/utils";
-import { formatDateMMDDYYYY } from "~/utils/formatDateMMDDYYYY";
-import { joinPronouns } from "~/utils/joinPronouns";
+import { definePdf } from "@/pdfs/utils/definePdf";
+import { formatDateMMDDYYYY } from "@/utils/formatDateMMDDYYYY";
+import { joinPronouns } from "@/utils/joinPronouns";
import pdf from "./cjp27-petition-to-change-name-of-adult.pdf";
export default definePdf({
diff --git a/src/pdfs/ma/cjp34-cori-and-wms-release-request.ts b/src/pdfs/ma/cjp34-cori-and-wms-release-request.ts
index 28ebfcff..60af537b 100644
--- a/src/pdfs/ma/cjp34-cori-and-wms-release-request.ts
+++ b/src/pdfs/ma/cjp34-cori-and-wms-release-request.ts
@@ -1,6 +1,6 @@
-import { definePdf } from "~/pdfs/utils";
-import { formatDateMMDDYYYY } from "~/utils/formatDateMMDDYYYY";
-import { joinNames } from "~/utils/joinNames";
+import { definePdf } from "@/pdfs/utils/definePdf";
+import { formatDateMMDDYYYY } from "@/utils/formatDateMMDDYYYY";
+import { joinNames } from "@/utils/joinNames";
import pdf from "./cjp34-cori-and-wms-release-request.pdf";
export default definePdf({
diff --git a/src/pdfs/utils/__tests__/loadPdfs.test.ts b/src/pdfs/utils/__tests__/loadPdfs.test.ts
index aae22919..29f042f3 100644
--- a/src/pdfs/utils/__tests__/loadPdfs.test.ts
+++ b/src/pdfs/utils/__tests__/loadPdfs.test.ts
@@ -1,11 +1,11 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { loadPdfs } from "../loadPdfs";
-vi.mock("~/pdfs", () => ({
+vi.mock("@/pdfs", () => ({
getPdfDefinition: vi.fn(),
}));
-import { getPdfDefinition } from "~/pdfs";
+import { getPdfDefinition } from "@/pdfs";
describe("loadPdfs", () => {
beforeEach(() => {
diff --git a/src/pdfs/utils/createCoverPage.ts b/src/pdfs/utils/createCoverPage.ts
index 795f2e25..2c4ebe33 100644
--- a/src/pdfs/utils/createCoverPage.ts
+++ b/src/pdfs/utils/createCoverPage.ts
@@ -1,4 +1,4 @@
-import { smartquotes } from "~/utils/smartquotes";
+import { smartquotes } from "@/utils/smartquotes";
import { loadPdfLib } from "./loadPdfLib";
/**
diff --git a/src/pdfs/utils/definePdf.ts b/src/pdfs/utils/definePdf.ts
index 7177843c..dcdf99e1 100644
--- a/src/pdfs/utils/definePdf.ts
+++ b/src/pdfs/utils/definePdf.ts
@@ -1,4 +1,4 @@
-import type { PDFDefinition } from "~/constants/forms";
+import type { PDFDefinition } from "@/constants/forms";
/**
* Define a PDF form.
diff --git a/src/pdfs/utils/downloadMergedPdf.ts b/src/pdfs/utils/downloadMergedPdf.ts
index ac740a74..4e571cfb 100644
--- a/src/pdfs/utils/downloadMergedPdf.ts
+++ b/src/pdfs/utils/downloadMergedPdf.ts
@@ -1,5 +1,5 @@
-import type { FormData } from "~/constants/fields";
-import type { PDFDefinition } from "~/constants/forms";
+import type { FormData } from "@/constants/fields";
+import type { PDFDefinition } from "@/constants/forms";
import { createCoverPage } from "./createCoverPage";
import { downloadPdf } from "./downloadPdf";
import { fillPdf } from "./fillPdf";
diff --git a/src/pdfs/utils/fillPdf.ts b/src/pdfs/utils/fillPdf.ts
index 003185f2..5ed802bc 100644
--- a/src/pdfs/utils/fillPdf.ts
+++ b/src/pdfs/utils/fillPdf.ts
@@ -1,5 +1,5 @@
-import type { FormData } from "~/constants/fields";
-import type { PDFDefinition } from "~/constants/forms";
+import type { FormData } from "@/constants/fields";
+import type { PDFDefinition } from "@/constants/forms";
import { fetchPdf } from "./fetchPdf";
import { loadPdfLib } from "./loadPdfLib";
diff --git a/src/pdfs/utils/getPdfForm.ts b/src/pdfs/utils/getPdfForm.ts
index 13c6f7e1..c89d216e 100644
--- a/src/pdfs/utils/getPdfForm.ts
+++ b/src/pdfs/utils/getPdfForm.ts
@@ -1,5 +1,5 @@
-import type { FormData } from "~/constants/fields";
-import type { PDFDefinition } from "~/constants/forms";
+import type { FormData } from "@/constants/fields";
+import type { PDFDefinition } from "@/constants/forms";
import { fillPdf } from "./fillPdf";
import { loadPdfLib } from "./loadPdfLib";
diff --git a/src/pdfs/utils/index.ts b/src/pdfs/utils/index.ts
deleted file mode 100644
index 16a3b947..00000000
--- a/src/pdfs/utils/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export { createCoverPage } from "./createCoverPage";
-export { definePdf } from "./definePdf";
-export { downloadMergedPdf } from "./downloadMergedPdf";
-export { downloadPdf } from "./downloadPdf";
-export { fetchPdf } from "./fetchPdf";
-export { fillPdf } from "./fillPdf";
-export { getPdfForm } from "./getPdfForm";
-export { loadPdfLib } from "./loadPdfLib";
-export { loadPdfs } from "./loadPdfs";
diff --git a/src/pdfs/utils/loadPdfs.ts b/src/pdfs/utils/loadPdfs.ts
index 43d4b8a6..58dc89c6 100644
--- a/src/pdfs/utils/loadPdfs.ts
+++ b/src/pdfs/utils/loadPdfs.ts
@@ -1,5 +1,5 @@
-import type { PDFId } from "~/constants/forms";
-import { getPdfDefinition } from "~/pdfs";
+import type { PDFId } from "@/constants/forms";
+import { getPdfDefinition } from "@/pdfs";
/**
* Load multiple PDFs.
diff --git a/src/sanity/components/YouTubeInput.tsx b/src/sanity/components/YouTubeInput.tsx
index 0cc0f5f6..77ae50b1 100644
--- a/src/sanity/components/YouTubeInput.tsx
+++ b/src/sanity/components/YouTubeInput.tsx
@@ -1,33 +1,10 @@
import { Box, Card, Flex, Text } from "@sanity/ui";
import { useEffect, useState } from "react";
import type { StringInputProps } from "sanity";
-
-export interface YouTubeOEmbedResponse {
- title: string;
- author_name: string;
- thumbnail_url: string;
-}
-
-export async function fetchYouTubeVideoDetails(
- url: string,
-): Promise {
- try {
- const encodedUrl = encodeURIComponent(url);
- const response = await fetch(
- `https://www.youtube.com/oembed?url=${encodedUrl}&format=json`,
- );
-
- if (!response.ok) {
- return null;
- }
-
- const data: YouTubeOEmbedResponse = await response.json();
-
- return data;
- } catch {
- return null;
- }
-}
+import {
+ fetchYouTubeVideoDetails,
+ type YouTubeOEmbedResponse,
+} from "../../utils/fetchYouTubeVideoDetails";
export function YouTubeInput(props: StringInputProps) {
const { value } = props;
diff --git a/src/sanity/components/YouTubePreview.tsx b/src/sanity/components/YouTubePreview.tsx
index 43aaeb60..0bd57888 100644
--- a/src/sanity/components/YouTubePreview.tsx
+++ b/src/sanity/components/YouTubePreview.tsx
@@ -1,17 +1,14 @@
-import { Flex, Text } from "@sanity/ui";
-import ReactPlayer from "react-player";
+import { Text } from "@sanity/ui";
+import { YouTube } from "../../components/react/common/YouTube";
+import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
import type { PreviewProps } from "sanity";
export function YouTubePreview(props: PreviewProps) {
const { title: url } = props;
- return (
-
- {typeof url === "string" ? (
-
- ) : (
- Add a YouTube URL
- )}
-
+ return typeof url === "string" ? (
+
+ ) : (
+ Add a YouTube URL
);
}
diff --git a/src/sanity/schema/formType.ts b/src/sanity/schema/formType.ts
index b2810eb1..3967929a 100644
--- a/src/sanity/schema/formType.ts
+++ b/src/sanity/schema/formType.ts
@@ -1,6 +1,5 @@
import { OlistIcon } from "@sanity/icons";
import { defineField, defineType } from "sanity";
-import { FORM_REGISTRY_METADATA } from "../../forms/formRegistry";
export const formType = defineType({
name: "form",
@@ -23,18 +22,16 @@ export const formType = defineType({
type: "string",
}),
defineField({
- name: "componentId",
- title: "Form Component",
+ name: "slug",
+ title: "Slug",
+ type: "slug",
description:
- "The React component that powers this form's interactive functionality",
- type: "string",
+ "The URL path for this form (e.g., 'social-security' for /forms/social-security). Must match an existing form page.",
+ validation: (Rule) => Rule.required(),
options: {
- list: FORM_REGISTRY_METADATA.map(({ id, title }) => ({
- title,
- value: id,
- })),
+ source: "title",
+ maxLength: 96,
},
- validation: (Rule) => Rule.required(),
}),
defineField({
name: "state",
@@ -49,18 +46,6 @@ export const formType = defineType({
to: [{ type: "category" }],
validation: (Rule) => Rule.required(),
}),
- defineField({
- name: "slug",
- title: "Slug",
- type: "slug",
- description:
- "The URL path for this form (e.g., 'court-order-ma' for /forms/court-order-ma)",
- validation: (Rule) => Rule.required(),
- options: {
- source: "title",
- maxLength: 96,
- },
- }),
],
preview: {
select: {
diff --git a/src/styles/base.css b/src/styles/base.css
index 38631fed..8a761d31 100644
--- a/src/styles/base.css
+++ b/src/styles/base.css
@@ -66,7 +66,7 @@ body[data-color="brown"]::after {
main {
flex: 1;
- padding: var(--space-2xl) var(--space-l);
+ padding-inline: var(--space-l);
min-width: 0;
width: 100%;
max-width: 1200px;
diff --git a/src/styles/theme.css b/src/styles/theme.css
index 6a439cd0..4d430e2a 100644
--- a/src/styles/theme.css
+++ b/src/styles/theme.css
@@ -74,8 +74,11 @@
--link-color: var(--namesake-black);
--border-color: var(--namesake-black);
--border-color-disabled: color-mix(in srgb, var(--namesake-black) 35%, transparent);
- --field-background: var(--background-color);
--field-text-color: var(--namesake-black);
+}
+
+body {
+ --field-background: var(--background-color);
--overlay-background: var(--background-color);
--button-background: var(--background-color);
--highlight-background: var(--namesake-black);
diff --git a/src/utils/__tests__/formatBirthplaceStateOrCountry.test.ts b/src/utils/__tests__/formatBirthplaceStateOrCountry.test.ts
index c7cc769c..a8f7226d 100644
--- a/src/utils/__tests__/formatBirthplaceStateOrCountry.test.ts
+++ b/src/utils/__tests__/formatBirthplaceStateOrCountry.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
-import { COUNTRIES } from "~/constants/countries";
-import { BIRTHPLACES } from "~/constants/jurisdictions";
+import { COUNTRIES } from "@/constants/countries";
+import { BIRTHPLACES } from "@/constants/jurisdictions";
import { formatBirthplaceStateOrCountry } from "../formatBirthplaceStateOrCountry";
describe("formatBirthplaceStateOrCountry", () => {
diff --git a/src/utils/__tests__/formatTimeEstimate.test.ts b/src/utils/__tests__/formatTimeEstimate.test.ts
index 9fe8cfdb..afe22751 100644
--- a/src/utils/__tests__/formatTimeEstimate.test.ts
+++ b/src/utils/__tests__/formatTimeEstimate.test.ts
@@ -3,28 +3,28 @@ import { formatTimeEstimate } from "../formatTimeEstimate";
describe("formatTimeEstimate", () => {
it("formats time estimate range with default values", () => {
- expect(formatTimeEstimate(5)).toBe("1–5 minutes"); // 5*0.2=1 to 5*1=5
- expect(formatTimeEstimate(3)).toBe("1–3 minutes"); // 3*0.2=0.6→1 to 3*1=3
+ expect(formatTimeEstimate(5)).toBe("2–4 minutes");
+ expect(formatTimeEstimate(3)).toBe("1–3 minutes");
});
it("formats time estimate for a single step", () => {
- expect(formatTimeEstimate(1)).toBe("1 minute"); // 1*0.2=0.2→1, 1*1=1, same so single value
+ expect(formatTimeEstimate(1)).toBe("1 minute");
});
it("shows single value when min equals max", () => {
expect(formatTimeEstimate(5, 2, 2)).toBe("10 minutes");
expect(formatTimeEstimate(1, 1, 1)).toBe("1 minute");
- expect(formatTimeEstimate(3, 1.5, 1.5)).toBe("5 minutes"); // 3 * 1.5 = 4.5, rounds up to 5
+ expect(formatTimeEstimate(3, 1.5, 1.5)).toBe("5 minutes");
});
it("uses singular 'minute' for exactly one minute when min equals max", () => {
expect(formatTimeEstimate(1, 0.5, 0.5)).toBe("1 minute");
- expect(formatTimeEstimate(2, 0.4, 0.4)).toBe("1 minute"); // 2 * 0.4 = 0.8, rounds up to 1
+ expect(formatTimeEstimate(2, 0.4, 0.4)).toBe("1 minute");
});
it("rounds up fractional minutes for both min and max", () => {
- expect(formatTimeEstimate(3, 1.1, 2.1)).toBe("4–7 minutes"); // 3*1.1=3.3→4, 3*2.1=6.3→7
- expect(formatTimeEstimate(5, 0.5, 1.5)).toBe("3–8 minutes"); // 5*0.5=2.5→3, 5*1.5=7.5→8
+ expect(formatTimeEstimate(3, 1.1, 2.1)).toBe("4–7 minutes");
+ expect(formatTimeEstimate(5, 0.5, 1.5)).toBe("3–8 minutes");
});
it("accepts custom min and max minutes per step", () => {
@@ -34,17 +34,12 @@ describe("formatTimeEstimate", () => {
});
it("handles zero steps", () => {
- expect(formatTimeEstimate(0)).toBe("0 minutes"); // 0*0.2=0, 0*1=0, same so single value
- });
-
- it("handles large numbers of steps", () => {
- expect(formatTimeEstimate(20)).toBe("4–20 minutes"); // 20*0.2=4, 20*1=20
- expect(formatTimeEstimate(50, 1, 2)).toBe("50–100 minutes");
+ expect(formatTimeEstimate(0)).toBe("0 minutes");
});
it("handles cases where rounding makes min equal max", () => {
// Edge case: if both round to the same value, show single value
- expect(formatTimeEstimate(1, 1.4, 1.5)).toBe("2 minutes"); // 1.4→2, 1.5→2
+ expect(formatTimeEstimate(1, 1.4, 1.5)).toBe("2 minutes");
});
it("uses en dash (not hyphen) in range", () => {
diff --git a/src/utils/fetchYouTubeVideoDetails.ts b/src/utils/fetchYouTubeVideoDetails.ts
new file mode 100644
index 00000000..46b92338
--- /dev/null
+++ b/src/utils/fetchYouTubeVideoDetails.ts
@@ -0,0 +1,26 @@
+export interface YouTubeOEmbedResponse {
+ title: string;
+ author_name: string;
+ thumbnail_url: string;
+}
+
+export async function fetchYouTubeVideoDetails(
+ url: string,
+): Promise {
+ try {
+ const encodedUrl = encodeURIComponent(url);
+ const response = await fetch(
+ `https://www.youtube.com/oembed?url=${encodedUrl}&format=json`,
+ );
+
+ if (!response.ok) {
+ return null;
+ }
+
+ const data: YouTubeOEmbedResponse = await response.json();
+
+ return data;
+ } catch {
+ return null;
+ }
+}
diff --git a/src/utils/formatBirthplaceStateOrCountry.ts b/src/utils/formatBirthplaceStateOrCountry.ts
index 19390af5..c4b27d75 100644
--- a/src/utils/formatBirthplaceStateOrCountry.ts
+++ b/src/utils/formatBirthplaceStateOrCountry.ts
@@ -1,5 +1,5 @@
-import { COUNTRIES } from "~/constants/countries";
-import { BIRTHPLACES } from "~/constants/jurisdictions";
+import { COUNTRIES } from "@/constants/countries";
+import { BIRTHPLACES } from "@/constants/jurisdictions";
export const formatBirthplaceStateOrCountry = (
birthplaceState?: string,
diff --git a/src/utils/formatTimeEstimate.ts b/src/utils/formatTimeEstimate.ts
index 77dcad3b..fb3d51fc 100644
--- a/src/utils/formatTimeEstimate.ts
+++ b/src/utils/formatTimeEstimate.ts
@@ -7,8 +7,8 @@
*/
export function formatTimeEstimate(
steps: number,
- minMinutesPerStep = 0.2,
- maxMinutesPerStep = 1,
+ minMinutesPerStep = 0.3,
+ maxMinutesPerStep = 0.75,
): string {
const minMinutes = Math.ceil(steps * minMinutesPerStep);
const maxMinutes = Math.ceil(steps * maxMinutesPerStep);
diff --git a/src/utils/useForm.ts b/src/utils/useForm.ts
new file mode 100644
index 00000000..bd70137c
--- /dev/null
+++ b/src/utils/useForm.ts
@@ -0,0 +1,90 @@
+// import { api } from "@convex/_generated/api";
+// import { useMutation, useQuery } from "convex/react";
+// import { usePostHog } from "posthog-js/react";
+import { useMemo, useState } from "react";
+import {
+ type FieldValues,
+ type UseFormProps,
+ useForm as useReactHookForm,
+} from "react-hook-form";
+import type { FieldName } from "@/constants/fields";
+// import { toast } from "sonner";
+// import { useEncryptionKey } from "@/hooks/useEncryptionKey";
+// import { decryptData, encryptData } from "@/utils/encryption";
+
+export function useForm(
+ fields: FieldName[],
+ options?: Omit, "values" | "defaultValues">,
+) {
+ // const posthog = usePostHog();
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ // const encryptionKey = useEncryptionKey();
+
+ const _fieldsList = useMemo(() => fields as string[], [fields]);
+
+ // const encryptedResponses = useQuery(api.userFormResponses.getByFields, {
+ // fields: fieldsList,
+ // });
+
+ // const save = useMutation(api.userFormResponses.set);
+
+ const form = useReactHookForm({
+ mode: "onBlur",
+ ...options,
+ });
+
+ // useEffect(() => {
+ // if (!encryptedResponses?.length || !encryptionKey || isSubmitting) return;
+
+ // Promise.all(
+ // encryptedResponses.map(async (response) => {
+ // try {
+ // const value = await decryptData(response.value, encryptionKey);
+ // form.setValue(
+ // response.field as Path,
+ // value as PathValue>,
+ // {
+ // shouldDirty: false,
+ // },
+ // );
+ // } catch (error) {
+ // posthog.captureException(error);
+ // }
+ // }),
+ // );
+ // }, [encryptedResponses, encryptionKey, isSubmitting]);
+
+ const onSubmit = form.handleSubmit(async (_data) => {
+ try {
+ // if (!encryptionKey) {
+ // throw new Error("No encryption key available.");
+ // }
+
+ setIsSubmitting(true);
+
+ // const entries = Object.entries(data).filter(
+ // ([_, value]) => value !== "" && value !== null && value !== undefined,
+ // );
+
+ // for (const [field, value] of entries) {
+ // const encryptedValue = await encryptData(value, encryptionKey);
+ // await save({ field, value: encryptedValue });
+ // }
+ } catch (_error) {
+ // posthog.captureException(error);
+ // toast.error(
+ // error instanceof Error
+ // ? error.message
+ // : "An error occurred during submission. Please try again.",
+ // );
+ } finally {
+ setIsSubmitting(false);
+ }
+ });
+
+ return {
+ ...form,
+ onSubmit,
+ isSubmitting,
+ };
+}
diff --git a/stats.html b/stats.html
new file mode 100644
index 00000000..db7ca4a8
--- /dev/null
+++ b/stats.html
@@ -0,0 +1,4949 @@
+
+
+
+
+
+
+
+ Rollup Visualizer
+
+
+
+
+
+
+
+
+
diff --git a/tsconfig.json b/tsconfig.json
index 3295dfb4..aa847a96 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,9 +1,10 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
+ "allowJs": true,
"baseUrl": ".",
"paths": {
- "~/*": ["./src/*"]
+ "@/*": ["./src/*"]
},
"jsx": "react-jsx",
"jsxImportSource": "react",
diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts
index ca7fd813..a698857c 100644
--- a/worker-configuration.d.ts
+++ b/worker-configuration.d.ts
@@ -1,7 +1,10 @@
/* eslint-disable */
-// Generated by Wrangler by running `wrangler types` (hash: 187132f48ddf0f604882ba8213fe386f)
-// Runtime types generated with workerd@1.20251105.0 2025-10-22 nodejs_compat
+// Generated by Wrangler by running `wrangler types` (hash: 9d50a76cdc759facba0bb43994cbf977)
+// Runtime types generated with workerd@1.20251118.0 2025-10-22 global_fetch_strictly_public,nodejs_compat
declare namespace Cloudflare {
+ interface GlobalProps {
+ mainModule: typeof import("./dist/_worker.js/index");
+ }
interface Env {
ASSETS: Fetcher;
}
@@ -451,7 +454,7 @@ interface StructuredSerializeOptions {
transfer?: any[];
}
declare abstract class Navigator {
- sendBeacon(url: string, body?: (ReadableStream | string | (ArrayBuffer | ArrayBufferView) | Blob | FormData | URLSearchParams | URLSearchParams)): boolean;
+ sendBeacon(url: string, body?: BodyInit): boolean;
readonly userAgent: string;
readonly hardwareConcurrency: number;
readonly language: string;
@@ -3188,6 +3191,7 @@ interface ContainerStartupOptions {
entrypoint?: string[];
enableInternet: boolean;
env?: Record;
+ hardTimeout?: (number | bigint);
}
/**
* The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other.
@@ -6763,20 +6767,8 @@ declare abstract class Ai {
} ? ReadableStream : AiModelList[Name]["postProcessedOutputs"]>;
models(params?: AiModelsSearchParams): Promise;
toMarkdown(): ToMarkdownService;
- toMarkdown(files: {
- name: string;
- blob: Blob;
- }[], options?: {
- gateway?: GatewayOptions;
- extraHeaders?: object;
- }): Promise;
- toMarkdown(files: {
- name: string;
- blob: Blob;
- }, options?: {
- gateway?: GatewayOptions;
- extraHeaders?: object;
- }): Promise;
+ toMarkdown(files: MarkdownDocument[], options?: ConversionRequestOptions): Promise;
+ toMarkdown(files: MarkdownDocument, options?: ConversionRequestOptions): Promise;
}
type GatewayRetries = {
maxAttempts?: 1 | 2 | 3 | 4 | 5;
@@ -8428,21 +8420,22 @@ declare namespace CloudflareWorkersModule {
protected ctx: ExecutionContext;
protected env: Env;
constructor(ctx: ExecutionContext, env: Env);
+ email?(message: ForwardableEmailMessage): void | Promise;
fetch?(request: Request): Response | Promise;
+ queue?(batch: MessageBatch): void | Promise;
+ scheduled?(controller: ScheduledController): void | Promise;
tail?(events: TraceItem[]): void | Promise;
tailStream?(event: TailStream.TailEvent): TailStream.TailEventHandlerType | Promise;
- trace?(traces: TraceItem[]): void | Promise;
- scheduled?(controller: ScheduledController): void | Promise;
- queue?(batch: MessageBatch): void | Promise;
test?(controller: TestController): void | Promise;
+ trace?(traces: TraceItem[]): void | Promise;
}
export abstract class DurableObject implements Rpc.DurableObjectBranded {
[Rpc.__DURABLE_OBJECT_BRAND]: never;
protected ctx: DurableObjectState;
protected env: Env;
constructor(ctx: DurableObjectState, env: Env);
- fetch?(request: Request): Response | Promise;
alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise;
+ fetch?(request: Request): Response | Promise;
webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise;
webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise;
webSocketError?(ws: WebSocket, error: unknown): void | Promise;
@@ -8505,36 +8498,56 @@ declare module "cloudflare:sockets" {
function _connect(address: string | SocketAddress, options?: SocketOptions): Socket;
export { _connect as connect };
}
+type MarkdownDocument = {
+ name: string;
+ blob: Blob;
+};
type ConversionResponse = {
name: string;
mimeType: string;
-} & ({
- format: "markdown";
+ format: 'markdown';
tokens: number;
data: string;
} | {
- format: "error";
+ name: string;
+ mimeType: string;
+ format: 'error';
error: string;
-});
+};
+type ImageConversionOptions = {
+ descriptionLanguage?: 'en' | 'es' | 'fr' | 'it' | 'pt' | 'de';
+};
+type EmbeddedImageConversionOptions = ImageConversionOptions & {
+ convert?: boolean;
+ maxConvertedImages?: number;
+};
+type ConversionOptions = {
+ html?: {
+ images?: EmbeddedImageConversionOptions & {
+ convertOGImage?: boolean;
+ };
+ };
+ docx?: {
+ images?: EmbeddedImageConversionOptions;
+ };
+ image?: ImageConversionOptions;
+ pdf?: {
+ images?: EmbeddedImageConversionOptions;
+ metadata?: boolean;
+ };
+};
+type ConversionRequestOptions = {
+ gateway?: GatewayOptions;
+ extraHeaders?: object;
+ conversionOptions?: ConversionOptions;
+};
type SupportedFileFormat = {
mimeType: string;
extension: string;
};
declare abstract class ToMarkdownService {
- transform(files: {
- name: string;
- blob: Blob;
- }[], options?: {
- gateway?: GatewayOptions;
- extraHeaders?: object;
- }): Promise;
- transform(files: {
- name: string;
- blob: Blob;
- }, options?: {
- gateway?: GatewayOptions;
- extraHeaders?: object;
- }): Promise;
+ transform(files: MarkdownDocument[], options?: ConversionRequestOptions): Promise;
+ transform(files: MarkdownDocument, options?: ConversionRequestOptions): Promise;
supported(): Promise;
}
declare namespace TailStream {
diff --git a/wrangler.jsonc b/wrangler.jsonc
index 7770d65e..f988d1a4 100644
--- a/wrangler.jsonc
+++ b/wrangler.jsonc
@@ -1,9 +1,9 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "namesake-fyi",
- "main": "./dist/_worker.js/index.js",
- "compatibility_date": "2025-08-15",
- "compatibility_flags": ["nodejs_compat"],
+ "main": "dist/_worker.js/index.js",
+ "compatibility_date": "2025-10-22",
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"assets": {
"binding": "ASSETS",
"directory": "./dist"