diff options
| author | Nikhil M Warrier <nikhilmw.dev@gmail.com> | 2026-03-15 18:57:46 +0530 |
|---|---|---|
| committer | Nikhil M Warrier <nikhilmw.dev@gmail.com> | 2026-03-15 18:57:46 +0530 |
| commit | 1394400b767c086ab0d71e9b2f95785b8cfe1b69 (patch) | |
| tree | 80b1d953adc3ffb618b585dfc0b4d4ef4cb62f5d | |
| parent | 17dcdb8076fd470880662be9550c9699b8614e24 (diff) | |
Add autosave and clear button to forms.
| -rw-r--r-- | src/content/forms/execom.mdx | 60 | ||||
| -rw-r--r-- | src/pages/forms/[...id].astro | 81 | ||||
| -rw-r--r-- | src/styles/global.css | 4 |
3 files changed, 82 insertions, 63 deletions
diff --git a/src/content/forms/execom.mdx b/src/content/forms/execom.mdx deleted file mode 100644 index 43dc72a..0000000 --- a/src/content/forms/execom.mdx +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "Interim Execom Recruitment" -id: "interim_execom_recruit_form" -description: "Recruitment form for FOSS-CET's interim executive committee" -date: 2026-03-14 -form: - submit_text: "Submit" - fields: - - id: "name" - label: "Your Name" - type: "text" - required: true - - id: "email" - label: "Email Address" - type: "email" - required: true - - id: "year" - label: "Year" - type: "select" - options: - - '1' - - '2' - - '3' - - '4' - required: true - - id: "role" - label: "Which role/s do you wish to apply for?" - type: "checkbox" - options: - - 'Project Lead' - - 'Event Lead' - - 'Marketing & Design Lead' - - 'Media Lead' - - 'Women in FOSS Lead' - - 'Administrative Committee (2nd, 3rd Years only)' - required: true - - id: "experience" - label: "Relevant Experience, if any:" - type: "textarea" - required: false - - id: "contributions" - label: "If recruited to your role, how do you plan to take the club forward?" - type: "textarea" - required: true - - id: "vision" - label: "What is your vision for FOSS-CET?" - type: "textarea" - required: true - - id: "help" - label: "How would being a part of our club help with your goals/passions?" - type: "textarea" - required: true - - id: "link" - label: "Upload a google drive link to your previous works, if any: (for marketing and design applications)" - type: "textarea" - required: false - ---- - -# Let's talk open source.
\ No newline at end of file diff --git a/src/pages/forms/[...id].astro b/src/pages/forms/[...id].astro index d26c51d..cadf14a 100644 --- a/src/pages/forms/[...id].astro +++ b/src/pages/forms/[...id].astro @@ -4,6 +4,7 @@ import { FORM_POST_URL } from '@/consts' import Layout from '@/layouts/Layout.astro' import { getAllForms } from '@/lib/data-utils' import { formatDate } from '@/lib/utils' +import { Icon } from 'astro-icon/components' import { render } from 'astro:content' export async function getStaticPaths() { @@ -29,7 +30,7 @@ const fields = Astro.props.data.form.fields {form.title} </h1> <div - class="text-muted-foreground mb-4 mb-6 flex w-fit py-2 text-xs sm:px-2 sm:py-0 sm:text-sm" + class="text-muted-foreground mb-6 flex w-fit py-2 text-xs sm:px-2 sm:py-0 sm:text-sm" > <span>{formatDate(form.date)}</span> </div> @@ -40,18 +41,92 @@ const fields = Astro.props.data.form.fields <form action={`${FORM_POST_URL}${form.id}`} + data-form-id={form.id} method="POST" class="flex w-full flex-col items-start gap-12" > {fields.map((field) => <FormField field={field} />)} + <div class="flex w-full items-center justify-end gap-4"> + <span + id="save-indicator" + class="text-muted-foreground ext-sm flex items-center justify-center gap-2 opacity-0 transition-opacity duration-300" + > + <Icon name="lucide:check" class="text-primary" /> + Draft autosaved + </span> - <div class="flex w-full justify-end"> + <!-- Click again to confirm clear. --> + <button + type="button" + onclick="if(this.textContent==='Confirm?') { this.form.reset(); this.textContent='Clear'; this.classList.replace('bg-destructive', 'bg-muted'); } else { this.textContent='Confirm?'; this.classList.replace('bg-muted', 'bg-destructive'); this.classList.add('text-destructive-foreground'); }" + onblur="this.textContent='Clear'; this.classList.replace('bg-destructive', 'bg-muted'); this.classList.remove('text-destructive-foreground');" + class="bg-muted text-foreground rounded-lg px-4 py-2 font-sans text-lg font-bold transition-all duration-200" + > + Clear + </button> <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 > + {form.submit_text || 'Submit'} + </button> </div> </form> </section> + + <script> + const setupAutosave = () => { + const form = document.querySelector<HTMLFormElement>('form[data-form-id]') + const indicator = document.getElementById('save-indicator') + if (!form) return + + const storageKey = `autosave-form-${form.dataset.formId}` + let saveTimeout: ReturnType<typeof setTimeout> + + // load saved data on mount + const savedData = JSON.parse(localStorage.getItem(storageKey) || '{}') + + Object.entries(savedData).forEach(([name, value]) => { + const inputs = form.querySelectorAll(`[name="${name}"]`) + inputs.forEach((input: any) => { + if (input.type === 'checkbox' || input.type === 'radio') { + input.checked = Array.isArray(value) + ? value.includes(input.value) + : input.value === value + } else { + input.value = value + } + }) + }) + + // save data to localStorage on any input change + form.addEventListener('input', () => { + const formData = new FormData(form) + const data: Record<string, any> = {} + + for (const [key, value] of formData.entries()) { + if (data[key]) { + data[key] = [].concat(data[key], value as any) + } else { + data[key] = value + } + } + + localStorage.setItem(storageKey, JSON.stringify(data)) + + // show the "Draft saved" indicator + if (indicator) { + indicator.classList.remove('opacity-0') + } + }) + + // clear localStorage on submit + form.addEventListener('submit', () => { + localStorage.removeItem(storageKey) + }) + } + + document.addEventListener('DOMContentLoaded', setupAutosave) + document.addEventListener('astro:page-load', setupAutosave) + </script> </Layout> diff --git a/src/styles/global.css b/src/styles/global.css index 2d4de9f..b80b9ed 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -103,4 +103,8 @@ ::file-selector-button { @apply border-border outline-ring/50 tracking-tight; } + + button { + @apply cursor-pointer; + } } |
