/ src / lib / components / rpgf-application-form / rpgf-application-form.svelte
rpgf-application-form.svelte
  1  <script lang="ts">
  2    import { run } from 'svelte/legacy';
  3  
  4    import type {
  5      ApplicationAnswerDto,
  6      ApplicationFormFields,
  7    } from '$lib/utils/rpgf/types/application';
  8    import DividerField from './components/divider-field.svelte';
  9    import EmailField from './components/email-field.svelte';
 10    import ListField from './components/list-field.svelte';
 11    import MarkdownField from './components/markdown-field.svelte';
 12    import SelectField from './components/select-field.svelte';
 13    import TextAreaField from './components/text-area-field.svelte';
 14    import TextField from './components/text-field.svelte';
 15    import UrlField from './components/url-field.svelte';
 16  
 17    interface Props {
 18      fields: ApplicationFormFields;
 19      disabled?: boolean;
 20      forceRevealErrors?: boolean;
 21      answers?: ApplicationAnswerDto;
 22      valid?: boolean;
 23    }
 24  
 25    let fieldsValidStates: Record<string, boolean> = $state({});
 26  
 27    let {
 28      fields,
 29      disabled = false,
 30      forceRevealErrors = false,
 31      answers = $bindable(),
 32      valid = $bindable(false),
 33    }: Props = $props();
 34    run(() => {
 35      valid = Object.values(fieldsValidStates).every((v) => v);
 36    });
 37  
 38    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 39    let internalAnswers: Record<string, any> = $state(
 40      answers?.reduce(
 41        (acc, answer) => {
 42          acc[answer.fieldId] = answer;
 43          return acc;
 44        },
 45        // eslint-disable-next-line @typescript-eslint/no-explicit-any
 46        {} as Record<string, any>,
 47      ) ?? {},
 48    );
 49  
 50    run(() => {
 51      answers = Object.values(internalAnswers).filter((a) => a !== undefined);
 52    });
 53  </script>
 54  
 55  <form class:disabled>
 56    {#each fields as applicationField}
 57      {@const type = applicationField.type}
 58  
 59      {#if type === 'divider'}
 60        <DividerField />
 61      {:else if type === 'markdown'}
 62        <MarkdownField field={applicationField} />
 63      {:else if type === 'list'}
 64        <ListField field={applicationField} bind:answer={internalAnswers[applicationField.id]} />
 65      {:else if type === 'select'}
 66        <SelectField
 67          field={applicationField}
 68          bind:answer={internalAnswers[applicationField.id]}
 69          bind:valid={fieldsValidStates[applicationField.id]}
 70        />
 71      {:else if type === 'text'}
 72        <TextField
 73          forceRevealError={forceRevealErrors}
 74          field={applicationField}
 75          bind:answer={internalAnswers[applicationField.id]}
 76          bind:valid={fieldsValidStates[applicationField.id]}
 77        />
 78      {:else if type === 'textarea'}
 79        <TextAreaField
 80          forceRevealError={forceRevealErrors}
 81          field={applicationField}
 82          bind:answer={internalAnswers[applicationField.id]}
 83          bind:valid={fieldsValidStates[applicationField.id]}
 84        />
 85      {:else if type === 'url'}
 86        <UrlField
 87          forceRevealError={forceRevealErrors}
 88          field={applicationField}
 89          bind:answer={internalAnswers[applicationField.id]}
 90          bind:valid={fieldsValidStates[applicationField.id]}
 91        />
 92      {:else if type === 'email'}
 93        <EmailField
 94          forceRevealError={forceRevealErrors}
 95          field={applicationField}
 96          bind:answer={internalAnswers[applicationField.id]}
 97          bind:valid={fieldsValidStates[applicationField.id]}
 98        />
 99      {/if}
100    {/each}
101  </form>
102  
103  <style>
104    form {
105      display: flex;
106      width: 100%;
107      flex-direction: column;
108      gap: 3rem;
109      transition: opacity 0.3s;
110    }
111  
112    form.disabled {
113      opacity: 0.5;
114      pointer-events: none;
115    }
116  </style>