diff options
| author | Nikhil M Warrier <nikhilmw.dev@gmail.com> | 2026-03-13 08:52:29 +0530 |
|---|---|---|
| committer | Nikhil M Warrier <nikhilmw.dev@gmail.com> | 2026-03-13 08:52:29 +0530 |
| commit | 30a3651c17bfd8a771feaaaa4cb700788b757f80 (patch) | |
| tree | 1a7560a6d9e0144c59a4792bcb629ea150564301 /src | |
| parent | 102a717ae001564349c2f0b2992459d6d5f91d08 (diff) | |
feat: Add forms
Diffstat (limited to 'src')
| -rw-r--r-- | src/consts.ts | 2 | ||||
| -rw-r--r-- | src/content.config.ts | 38 | ||||
| -rw-r--r-- | src/content/forms/test.mdx | 26 | ||||
| -rw-r--r-- | src/lib/data-utils.ts | 5 | ||||
| -rw-r--r-- | src/pages/forms/[...id].astro | 68 |
5 files changed, 138 insertions, 1 deletions
diff --git a/src/consts.ts b/src/consts.ts index f5e8992..93100c0 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -13,6 +13,8 @@ export const SITE: Site = { eventsPerPage: 3, } +export const FORM_POST_URL = 'https://fosscet-forms.val.run/' + export const NAV_LINKS: SocialLink[] = [ { href: '/blog', diff --git a/src/content.config.ts b/src/content.config.ts index 1bd3526..c9c4810 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -48,6 +48,42 @@ const members = defineCollection({ }), }) +const forms = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/forms' }), + schema: () => + z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + // sqlite-safe + id: z.string().regex(/^[a-zA-Z0-9_]+$/, { + message: + "Form 'id' can only contain letters, numbers, and underscores.", + }), + submit_text: z.string().default('Submit'), + form: z.object({ + submit_text: z.string().default('Submit'), + fields: z + .array( + z.object({ + id: z.string().regex(/^[a-zA-Z0-9_]+$/, { + message: + "Field 'name' can only contain letters, numbers, and underscores.", + }), + label: z.string(), + type: z + .enum(['text', 'email', 'number', 'textarea', 'tel', 'url']) + .default('text'), + required: z.boolean().default(false), + }), + ) + .min(1, { + message: 'A form must have at least one field defined.', + }), + }), + }), +}) + const projects = defineCollection({ loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/projects' }), schema: ({ image }) => @@ -62,4 +98,4 @@ const projects = defineCollection({ }), }) -export const collections = { events, blog, members, projects } +export const collections = { events, blog, members, projects, forms } diff --git a/src/content/forms/test.mdx b/src/content/forms/test.mdx new file mode 100644 index 0000000..6e3d731 --- /dev/null +++ b/src/content/forms/test.mdx @@ -0,0 +1,26 @@ +--- +title: "Get in Touch" +id: "main_contact_form" +description: "Reach out to the FOSSCET team." +date: 2026-03-13 +form: + submit_text: "Send Message" + fields: + - id: "name" + label: "Your Name" + type: "text" + required: true + - id: "email" + label: "Email Address" + type: "email" + required: true + - id: "message" + label: "What's on your mind?" + type: "textarea" + required: true +--- + +# Let's talk open source. + +This is a sample form. +Everything below this text is generated automatically from the metadata. diff --git a/src/lib/data-utils.ts b/src/lib/data-utils.ts index 6e4439f..114fb97 100644 --- a/src/lib/data-utils.ts +++ b/src/lib/data-utils.ts @@ -26,6 +26,11 @@ export async function getAllEvents(): Promise<CollectionEntry<'events'>[]> { return events.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) } +export async function getAllForms(): Promise<CollectionEntry<'forms'>[]> { + const forms = await getCollection('forms') + return forms.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) +} + export function groupEventsByYear( events: CollectionEntry<'events'>[], ): Record<string, CollectionEntry<'events'>[]> { diff --git a/src/pages/forms/[...id].astro b/src/pages/forms/[...id].astro new file mode 100644 index 0000000..713e599 --- /dev/null +++ b/src/pages/forms/[...id].astro @@ -0,0 +1,68 @@ +--- +import { FORM_POST_URL } from '@/consts' +import Layout from '@/layouts/Layout.astro' +import { getAllForms } from '@/lib/data-utils' + +export async function getStaticPaths() { + const forms = await getAllForms() + return forms.map((form) => ({ + params: { id: form.id }, + props: form, + })) +} + +const form = Astro.props.data +const fields = Astro.props.data.form.fields +--- + +<Layout> + <section + class="border-border mx-auto flex w-full max-w-3xl flex-col items-center gap-y-6 rounded-xl border p-8" + > + <form + action={`${FORM_POST_URL}${form.id}`} + method="POST" + class="flex w-full flex-col items-start gap-12" + > + <h1 class="w-full text-center text-3xl"> + {form.title} + </h1> + + { + fields.map((field) => ( + <div class="flex w-full flex-col gap-2"> + <label for={field.id} class="text-muted-foreground pl-1"> + {field.label} + </label> + + {field.type === 'textarea' ? ( + <textarea + id={field.id} + name={field.id} + required={field.required} + rows="4" + class="border-border rounded-xl border px-4 py-2" + /> + ) : ( + <input + type={field.type || 'text'} + id={field.id} + name={field.id} + required={field.required} + class="border-border rounded-xl border px-4 py-2" + /> + )} + </div> + )) + } + + <div class="flex w-full justify-end"> + <button + type="submit" + class="bg-primary text-primary-foreground rounded-lg px-4 py-2 font-sans text-lg font-bold" + >{form.submit_text || 'Submit'}</button + > + </div> + </form> + </section> +</Layout> |
