form
All checks were successful
deploy to production / deploy (push) Successful in 49s

This commit is contained in:
2023-09-24 09:38:03 +00:00
parent 7813f0b486
commit eee191955e
32 changed files with 1091 additions and 518 deletions

View File

@@ -0,0 +1,399 @@
<script lang="ts">
import Form from "./form.svelte"
import MobileForm from "./mobileForm.svelte"
import { writable } from "svelte/store"
import { validateFields } from "../../../functions/validateFields"
import { sendForm } from "../../../functions/sendForm"
import { navigate } from "svelte-routing"
import { onMount } from "svelte"
export let col: Column
export let siteId: string
export let rowNr: number
export let row: Row
export let rows: Row[]
let formSend = false
let formValues = writable<FormValues>({})
function submitForm() {
const values: Array<ValueEntry> = Object.entries($formValues).map((entry: ObjectEntry) => {
const key: string = entry[0]
const value: CustomHTMLElement = entry[1]
if (!key.includes("numberLabel")) {
return [key, [value.checked || value.value, value.required]] as ValueEntry
} else {
return [key, [value.value, value, value.getAttribute("name"), value.required]] as ValueEntry
}
})
const fields: Array<ValueEntry> = values.filter((entry: ValueEntry) => !entry[0].includes("label"))
const validation = validateFields(fields)
if (validation.length) {
validation.forEach((error) => {
// @ts-ignore
if (error[0].includes("block")) {
// @ts-ignore
error[1]()
} else {
// @ts-ignore
$formValues[error[1]].classList.add("invalid")
const label = $formValues[`${error[1]}_label`]
const errorElement = document.createElement("div")
errorElement.className = "error-message"
errorElement.textContent = error[0] as string
label?.appendChild(errorElement)
}
})
} else {
// @ts-ignore
const formObj: FormObj = {}
fields.forEach((entry) => {
if (Array.isArray(entry[1]) && entry[1].length == 4) {
// @ts-ignore
if (entry[1][0]) formObj[entry[0]] = entry[1]
} else {
if (!entry[1][0] && !entry[1][1]) return
// @ts-ignore
formObj[entry[0]] = entry[1][0]
}
})
let form: any
row.column.forEach((col) => {
if (col.contentType == "form") form = col
})
if (!form) return
formObj["formRows"] = form.formRows.map((r: FormRow) => r.rowName)
formObj["formTitle"] = form.formEmailTitle
formSend = true
const hny = document.getElementById("hny") as HTMLInputElement
if (hny) formObj["honey"] = hny.checked
sendForm(formObj)
}
}
let innerWidth = window.innerWidth
onMount(() => {
const handleResize = () => {
innerWidth = window.innerWidth
}
window.addEventListener("resize", handleResize)
// Cleanup function
return () => {
window.removeEventListener("resize", handleResize)
}
})
</script>
{#if formSend}
<div class="success-message">
<h1>Formular erfolgreich gesendet!</h1>
<p>Vielen Dank für Ihre Anfrage. Wir werden uns in Kürze bei Ihnen melden.</p>
</div>
{:else}
<form
class="form-rows"
on:submit="{(e) => {
e.preventDefault()
submitForm()
}}"
>
{#each col.formRows ?? [] as formRow, i}
{#if innerWidth < 768}
<MobileForm formRow="{formRow}" formValues="{formValues}" index="{i}" />
{:else}
<Form formRow="{formRow}" formValues="{formValues}" index="{i}" />
{/if}
{/each}
<div class="row additional">
<div class="data-protection">
<label bind:this="{$formValues[`agreement_label`]}">
<input
required="{true}"
class="checkit"
type="checkbox"
on:change="{(e) => {
let element = e.currentTarget
element.classList.remove('invalid')
}}"
bind:this="{$formValues['agreement']}"
/>
<span class="checkit-span"></span>
</label>
<div class="datasec">
<button on:click|preventDefault="{() => navigate('/datenschutz')}" class="link">
Datenschutz
</button>
akzeptieren
</div>
</div>
<input
type="checkbox"
name="contact_me_by_fax_only"
id="hny"
value="1"
style="display:none !important"
tabindex="-1"
autocomplete="off"
/>
<button class="submit-request" type="submit">Anfrage absenden</button>
</div>
</form>
{/if}
<style lang="less" global>
@import "../../../assets/css/variables.less";
input,
select,
textarea,
.data-protection {
margin: 5px 0px;
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
}
.success-message {
h1 {
color: var(--heading-font-color);
font-size: 36px;
margin-top: 50px;
}
p {
font-size: 20px;
margin-top: 20px;
}
}
.invalidBlocks {
border: 2px solid rgb(255, 0, 0) !important;
position: relative;
&::after {
font-size: 0.9rem !important;
color: red !important;
position: absolute;
bottom: 2px;
content: "Bitte wähle entweder eine Kartenanzahl oder einen Wunschbetrag aus.";
}
}
.border-red {
border-color: red !important;
}
.no-margin {
margin-top: 15px !important;
}
.invalid {
border: 2px solid red !important;
}
.error-message {
font-size: 0.9rem !important;
color: red !important;
position: absolute;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
height: 100%;
}
input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
}
.checkit {
display: none;
}
.checkit-span {
height: 20px;
width: 20px;
border: 1px solid grey;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
[type="checkbox"]:checked + span:before {
content: "\2714";
position: absolute;
transform-origin: bottom;
}
.form-rows {
display: flex;
flex-direction: column;
gap: 1.5rem;
width: 100%;
h3 {
font-weight: bold !important;
}
.form-row {
display: flex;
flex-direction: column;
width: 100%;
gap: 1.5rem;
.date {
max-width: 100%;
box-sizing: border-box;
}
label {
width: 100% !important;
font-size: inherit;
input,
select,
textarea {
width: 100%;
font-size: inherit;
}
}
.form-cols {
display: flex;
gap: 1.5rem;
width: 100%;
}
}
.form-column {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-start;
width: 100%;
gap: 1rem;
border-radius: 4px;
}
h3 {
margin-bottom: 0.5rem;
font-size: 1.2rem;
font-weight: bold;
}
p {
margin-bottom: 0.5rem;
color: var(--hover-color);
}
input,
select,
textarea,
.data-protection {
padding: 10px 20px;
border: 0px solid var(--opposite-bg-color);
border-bottom: 3px solid var(--heading-font-color);
outline: 0px solid var(--opposite-bg-color);
color: var(--opposite-bg-color);
background-color: var(--background-color);
resize: none;
}
.data-protection {
display: flex;
flex-wrap: nowrap;
gap: 5px !important;
justify-content: start;
flex-direction: row !important;
}
input[type="date"] {
font-family: inherit;
width: 100% !important;
position: relative;
}
select {
padding: 10px 20px;
border: 0;
border-bottom: 3px solid var(--heading-font-color);
outline: none;
}
select:focus {
border-bottom-color: var(--heading-font-color);
}
#time-select {
appearance: none;
background-image: url("../../../../../media/clock.svg");
background-repeat: no-repeat;
background-position: right 20px center;
background-size: 18px;
option {
padding: 10px 20px;
background-color: var(--background-color);
}
}
@media @mobile {
.date {
width: 100vw !important;
}
}
@media @tablet {
.date {
width: 100% !important;
}
.form-cols {
flex-direction: row;
}
}
}
i .max-width {
max-width: @body-maxwidth !important;
}
.datasec {
display: flex;
align-items: center !important;
.link {
height: 100%;
display: flex;
align-items: flex-end !important;
text-decoration: underline;
margin-right: 3px;
color: rgb(14, 91, 146);
}
}
.additional {
display: flex;
@media @mobile {
flex-direction: column !important;
}
@media @tablet {
flex-direction: row !important;
}
gap: 2.5rem;
div {
flex: 1;
display: flex;
align-items: center;
justify-content: start;
}
.submit-request {
flex: 1;
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
width: 100%;
display: flex;
align-items: center;
justify-content: start;
background-color: var(--heading-font-color);
color: var(--background-color);
padding: 10px 20px;
}
}
</style>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import type { Writable } from "svelte/store"
export let label = "" // Der Text, der neben der Checkbox angezeigt wird
export let bindValue: any // Der Wert der Checkbox
export let formValues: Writable<FormValues> // Das formValues-Objekt aus dem übergeordneten Komponenten
export let id = "" // Eindeutige ID für die Checkbox, um den Label korrekt zuzuweisen
export let name = "" // Name des Formularelements
</script>
<label class="checkbox-container" for="{id}">
<input type="checkbox" bind:checked="{bindValue}" id="{id}" name="{name}" bind:this="{formValues[name]}" />
<span>{label}</span>
</label>
<style>
/* Sie können hier den Stil für Ihre Checkbox anpassen */
</style>

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import type { Writable } from "svelte/store"
export let groupTitle: string
export let checkboxes: { name: string; emailName: string }[]
export let formValues: Writable<FormValues>
export let rowNr: number
</script>
<div class="checkbox-group">
<h3>{groupTitle}</h3>
<div class="containerr">
{#each checkboxes as checkbox, i}
<label class="checkbox-label">
<input
type="checkbox"
class="checkbox-input checkit"
bind:this="{$formValues[`checkbox_${groupTitle}_${rowNr}_${checkbox.emailName}`]}"
/>
<span class="checkit-span"></span>
{checkbox.name}
</label>
{/each}
</div>
</div>
<style lang="less">
.checkbox-group {
.checkbox-input {
vertical-align: middle;
margin-right: 8px;
width: fit-content !important;
}
.checkbox-label {
width: 100%;
vertical-align: middle;
align-items: center;
display: flex;
gap: 10px;
}
& > .containerr {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
}
</style>

View File

@@ -0,0 +1,131 @@
<script lang="ts">
import { CalendarView } from "fluent-svelte"
export let groupTitle: string
export let datePickerProps: DatePickerProps
export let formValues: any
export let rowNr: number
export let formCol: FormColumn
const today = new Date()
const oneYearFromNow = new Date(today)
oneYearFromNow.setFullYear(today.getFullYear() + 1)
const indexToDay: Record<number, string> = {
0: "sunday",
1: "monday",
2: "tuesday",
3: "wednesday",
4: "thursday",
5: "friday",
6: "saturday",
}
function generateBlackoutDates(props: DatePickerProps): Date[] {
let blackoutDates = []
// Funktion, um einen ISO-String in ein vereinfachtes Date-Objekt umzuwandeln
const dateFromISOString = (isoString: string) => {
const date = new Date(isoString)
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
}
const dayToIndex: Record<string, number> = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6,
}
// Initialisiere ein aktuelles Datum auf heute
let currentDate = new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()))
// Schleife durch die nächsten 365 Tage
for (let i = 0; i <= 365; i++) {
// Überprüfen, ob dieses Datum erlaubt ist
const isAllowed = props.allowedDateRanges.some((range) => {
const fromDate = dateFromISOString(range.from)
const toDate = dateFromISOString(range.to)
return currentDate >= fromDate && currentDate <= toDate
})
// Überprüfen, ob der Wochentag ausgeschlossen ist
const currentDayIndex = currentDate.getUTCDay()
const currentDayString = indexToDay[currentDayIndex]
const isExcluded = props.excludeDays?.includes(currentDayString)
// Wenn nicht erlaubt oder ausgeschlossen, zu den Blackout-Daten hinzufügen
if (!isAllowed || isExcluded) {
blackoutDates.push(new Date(currentDate))
}
// Zum nächsten Tag wechseln
currentDate.setUTCDate(currentDate.getUTCDate() + 1)
}
return blackoutDates
}
let value = new Date()
const blackoutDates = generateBlackoutDates(datePickerProps)
function setFormValues() {
$formValues[`datepicker_${groupTitle}_${rowNr}_${formCol.datePickerEmailTitle}`] = {
value: value.toLocaleDateString("de-DE"),
required: !formCol.datePickerNotRequired,
}
}
//needs email conntection
$: {
if (value) {
setFormValues()
}
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="container">
<div
class="datepicker"
on:click|preventDefault|stopPropagation
on:keydown|stopPropagation
on:submit|stopPropagation|preventDefault
>
<CalendarView
weekStart="{1}"
bind:vaue="{value}"
on:change="{(e) => {
value = e.detail
}}"
min="{today}"
max="{oneYearFromNow}"
blackout="{blackoutDates}"
locale="de-DE"
/>
</div>
</div>
<style lang="less">
@import url("https://unpkg.com/fluent-svelte/theme.css");
.container {
width: 100%;
display: flex;
justify-content: center;
--fds-solid-background-quarternary: var(--background-color);
--fds-text-primary: var(--normal-font-color);
--fds-text-secondary: var(--normal-font-color-80);
--fds-text-tertiary: var(--normal-font-color-50);
--fds-text-disabled: var(--normal-font-color-30);
--fds-accent-disabled: var(--opposite-bg-color-5);
--fds-control-strong-stroke-default: var(--opposite-bg-color-80);
.datepicker {
width: fit-content;
margin: 5px 0px;
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
}
}
</style>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import CheckboxGroup from "./checkboxGroup.svelte"
import Datepicker from "./datepicker.svelte"
import FormLabelNumberBlock from "./formLabelNumberBlock.svelte"
import type { Writable } from "svelte/store"
@@ -24,6 +26,9 @@
</script>
<div class="form-row">
<h3 style="margin-bottom: -0.5rem;">
{#if formRow.showRowName}{formRow.rowName || ""}{/if}
</h3>
<div class="form-cols">
{#each formRow.columns as column, columnIndex}
<div class="form-column">
@@ -48,7 +53,7 @@
on:change="{removeInvalid}"
>
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
{#each column?.times || [] as time}
{#each column?.times ?? [] as time}
<option value="{time?.timeFrom}-{time?.timeTo}">
{time?.timeFrom} - {time?.timeTo}
</option>
@@ -114,49 +119,70 @@
</label>
{/if}
{#each column.text || [] as textField, textFieldIndex}
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.textPlaceholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
{#if column.showCheckboxGroup}
<CheckboxGroup
checkboxes="{column.checkboxes}"
groupTitle="{column.groupTitle}"
formValues="{formValues}"
rowNr="{index}"
/>
{/if}
{#if column.showDatePicker}
<Datepicker
datePickerProps="{column.datePickerProps}"
groupTitle="{column.groupTitle}"
formValues="{formValues}"
rowNr="{index}"
formCol="{column}"
/>
{/if}
{#each column.text ?? [] as textField, textFieldIndex}
<div>
<h3 class="textTitle">{textField.textTitle || ""}</h3>
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.textPlaceholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
]}"
>
<input
type="text"
placeholder="{textField?.textPlaceholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
textField.textfieldOrder
}_${index}_${textField.emailName || columnIndex + 'invalidtext' + textFieldIndex}`
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
]}"
/>
</label>
{/if}
>
<input
type="text"
placeholder="{textField?.textPlaceholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
textField.textfieldOrder
}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
/>
</label>
{/if}
</div>
{/each}
</div>
{/each}
</div>
</div>
<style lang="less">
</style>

View File

@@ -1,7 +1,8 @@
<script lang="ts">
import FormLabelNumberBlock from "./formLabelNumberBlock.svelte"
import type { Writable } from "svelte/store"
import CheckboxGroup from "./checkboxGroup.svelte"
import Datepicker from "./datepicker.svelte"
export let formRow: FormRow
export let formValues: Writable<FormValues>
export let index: number
@@ -126,46 +127,69 @@
</label>
</div>
{/if}
{#if column.showCheckboxGroup}
<CheckboxGroup
checkboxes="{column.checkboxes}"
groupTitle="{column.groupTitle}"
formValues="{formValues}"
rowNr="{index}"
/>
{/if}
{#if column.showDatePicker}
<Datepicker
datePickerProps="{column.datePickerProps}"
groupTitle="{column.groupTitle}"
formValues="{formValues}"
rowNr="{index}"
formCol="{column}"
/>
{/if}
{#each column.text ?? [] as textField, textFieldIndex}
<div class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}">
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.textPlaceholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
<h3 class="textTitle">{textField.textTitle || ""}</h3>
<div
class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}"
>
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.textPlaceholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
]}"
>
<input
type="text"
placeholder="{textField?.textPlaceholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
textField.textfieldOrder
}_${index}_${textField.emailName || columnIndex + 'invalidtext' + textFieldIndex}`
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
]}"
/>
</label>
{/if}
>
<input
type="text"
placeholder="{textField?.textPlaceholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
textField.textfieldOrder
}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
/>
</label>
{/if}
</div>
</div>
{/each}
{/each}