From 4f8a75c427bbe0fca026325e946a25689eee54dc Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Mon, 10 Feb 2025 15:49:08 +0100
Subject: [PATCH 01/72] Add min and max cost

---
 aleksis/apps/paweljong/forms.py               |  4 +++-
 .../0031_event_max_cost_event_min_cost.py     | 23 +++++++++++++++++++
 aleksis/apps/paweljong/models.py              |  2 ++
 .../templated_email/event_created.email       |  4 ++++
 4 files changed, 32 insertions(+), 1 deletion(-)
 create mode 100644 aleksis/apps/paweljong/migrations/0031_event_max_cost_event_min_cost.py

diff --git a/aleksis/apps/paweljong/forms.py b/aleksis/apps/paweljong/forms.py
index 4376595..3122199 100644
--- a/aleksis/apps/paweljong/forms.py
+++ b/aleksis/apps/paweljong/forms.py
@@ -62,7 +62,7 @@ class EditEventForm(ExtensibleForm):
             Row("display_name", "slug", "description"),
             Row("place", "published"),
             Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")),
-            Fieldset(_("Event details"), Row("cost", "max_participants"), "information", "additional_fields", "contact_information_visible_fields"),
+            Fieldset(_("Event details"), Row("cost", "min_cost", "max_cost", "max_participants"), "information", "additional_fields", "contact_information_visible_fields"),
             Fieldset(_("Terms"), "terms"),
             Fieldset(_("Info mailings"), "info_mailings"),
         ),
@@ -81,6 +81,8 @@ class EditEventForm(ExtensibleForm):
             "date_registration",
             "date_retraction",
             "cost",
+            "min_cost",
+            "max_cost",
             "max_participants",
             "terms",
             "information",
diff --git a/aleksis/apps/paweljong/migrations/0031_event_max_cost_event_min_cost.py b/aleksis/apps/paweljong/migrations/0031_event_max_cost_event_min_cost.py
new file mode 100644
index 0000000..c52e061
--- /dev/null
+++ b/aleksis/apps/paweljong/migrations/0031_event_max_cost_event_min_cost.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.1.4 on 2025-02-10 14:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('paweljong', '0030_event_contact_information_visible_fields'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='event',
+            name='max_cost',
+            field=models.IntegerField(blank=True, default=None, null=True, verbose_name='Maximum cost in €'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='min_cost',
+            field=models.IntegerField(default=0, verbose_name='Minimum cost in €'),
+        ),
+    ]
diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index a897813..bc7b59e 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -167,6 +167,8 @@ class Event(ExtensibleModel):
 
     # Other details
     cost = models.IntegerField(verbose_name=_("Cost in €"))
+    min_cost = models.IntegerField(verbose_name=_("Minimum cost in €"), default=0)
+    max_cost = models.IntegerField(verbose_name=_("Maximum cost in €"), default=None, null=True, blank=True)
     max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants"))
     information = RichTextField(verbose_name=_("Information about the event"))
     terms = models.ManyToManyField(Terms, verbose_name=_("Terms"), related_name="event", blank=True)
diff --git a/aleksis/apps/paweljong/templates/templated_email/event_created.email b/aleksis/apps/paweljong/templates/templated_email/event_created.email
index ff48163..6e2ac2d 100644
--- a/aleksis/apps/paweljong/templates/templated_email/event_created.email
+++ b/aleksis/apps/paweljong/templates/templated_email/event_created.email
@@ -13,6 +13,8 @@
     * {% trans "Registration deadline" %}: {{ new_event.date_registration }}
     * {% trans "Retraction deadline" %}: {{ new_event.date_retraction }}
     * {% trans "Cost" %}: {{ new_event.cost }}
+    * {% trans "Minimum cost" %}: {{ new_event.min_cost }}
+    * {% trans "Maximum cost" %}: {{ new_event.max_cost }}
     * {% trans "Max. participants" %}: {{ new_event.max_participants }}
     * {% trans "Owners" %}:
     {% for owner in new_event.group.owners.all %}
@@ -41,6 +43,8 @@
             <li> {% trans "Registration deadline" %}: {{ new_event.date_registration }}</li>
             <li> {% trans "Retraction deadline" %}: {{ new_event.date_retraction }}</li>
             <li> {% trans "Cost" %}: {{ new_event.cost }}</li>
+            <li> {% trans "Minimum cost" %}: {{ new_event.min_cost }}</li>
+            <li> {% trans "Maximum cost" %}: {{ new_event.max_cost }}</li>
             <li> {% trans "Max. participants" %}: {{ new_event.max_participants }}</li>
             <li> {% trans "Owners" %}</li>
             <ul>
-- 
GitLab


From 8fe8a87ded4523644285a15c0add954ceac77949 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 10:03:59 +0100
Subject: [PATCH 02/72] WIP: Rebuild event registration form in vue

---
 .../frontend/components/event/events.graphql  |  41 ++
 .../eventAdditionalFieldMixin.js              |  38 +
 .../EventRegistrationForm.vue                 | 691 ++++++++++++++++++
 .../eventRegistrationMutation.graphql         |   8 +
 .../event_registration/helpers.graphql        |  19 +
 aleksis/apps/paweljong/frontend/index.js      |   6 +-
 .../apps/paweljong/frontend/messages/en.json  | 196 +++++
 aleksis/apps/paweljong/schema/__init__.py     |  49 ++
 .../{schema.py => schema/checkpoint.py}       |  66 +-
 aleksis/apps/paweljong/schema/event.py        |  10 +
 .../schema/event_additional_field.py          |  46 ++
 .../paweljong/schema/event_registration.py    |  26 +
 aleksis/apps/paweljong/schema/terms.py        |  10 +
 13 files changed, 1139 insertions(+), 67 deletions(-)
 create mode 100644 aleksis/apps/paweljong/frontend/components/event/events.graphql
 create mode 100644 aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js
 create mode 100644 aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
 create mode 100644 aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
 create mode 100644 aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
 create mode 100644 aleksis/apps/paweljong/schema/__init__.py
 rename aleksis/apps/paweljong/{schema.py => schema/checkpoint.py} (50%)
 create mode 100644 aleksis/apps/paweljong/schema/event.py
 create mode 100644 aleksis/apps/paweljong/schema/event_additional_field.py
 create mode 100644 aleksis/apps/paweljong/schema/event_registration.py
 create mode 100644 aleksis/apps/paweljong/schema/terms.py

diff --git a/aleksis/apps/paweljong/frontend/components/event/events.graphql b/aleksis/apps/paweljong/frontend/components/event/events.graphql
new file mode 100644
index 0000000..73d7458
--- /dev/null
+++ b/aleksis/apps/paweljong/frontend/components/event/events.graphql
@@ -0,0 +1,41 @@
+query eventById($id: ID!) {
+  eventById(id: $id) {
+    id
+    displayName
+    description
+    cost
+    minCost
+    maxCost
+    terms {
+      id
+    }
+    additionalFields {
+      id
+    }
+  }
+}
+
+query eventBySlug($slug: String!) {
+  event: eventBySlug(slug: $slug) {
+    id
+    displayName
+    description
+    cost
+    minCost
+    maxCost
+    terms {
+      id
+      title
+      term
+      confirmationText
+    }
+    additionalFields {
+      id
+      title
+      fieldType
+      required
+      helpText
+    }
+    contactInformationVisibleFields
+  }
+}
diff --git a/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js b/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js
new file mode 100644
index 0000000..17e08f3
--- /dev/null
+++ b/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js
@@ -0,0 +1,38 @@
+/**
+ * Vue mixin containing code getting the respective vue component for event additional fields.
+ *
+ * Only used by event registration form, but factored out for readability.
+ */
+
+// TODO: add some more rule checking (e.g. for emails)
+
+import DateField from "aleksis.core/components/generic/forms/DateField.vue";
+import DateTimeField from "aleksis.core/components/generic/forms/DateTimeField.vue";
+import TimeField from "aleksis.core/components/generic/forms/TimeField.vue";
+import PositiveSmallIntegerField from "aleksis.core/components/generic/forms/PositiveSmallIntegerField.vue";
+
+const eventAdditionalFieldMixin = {
+  methods: {
+    fieldComponentForAdditionalField(additionalField) {
+      if (additionalField.fieldType == "CHARFIELD" || additionalField.fieldType == "EMAILFIELD" || additionalField.fieldType == "URLFIELD" || additionalField.fieldType == "GENERICIPADDRESSFIELD") {
+        return "v-text-field";
+      } else if (additionalField.fieldType == "BOOLEANFIELD" || additionalField.fieldType == "NULLBOOLEANFIELD") {
+        // TODO: implement proper null boolean input
+        return "v-checkbox"
+      } else if (additionalField.fieldType == "DATEFIELD") {
+        return DateField;
+      } else if (additionalField.fieldType == "DATETIMEFIELD") {
+        return DateTimeField;
+      } else if (additionalField.fieldType == "TIMEFIELD") {
+        return TimeField;
+      } else if (additionalField.fieldType == "INTEGERFIELD" || additionalField.fieldType == "DECIMALFIELD") {
+        // TODO: implement proper decimal input
+        return PositiveSmallIntegerField;
+      }
+      return "";
+    },
+  },
+};
+
+export default eventAdditionalFieldMixin;
+  
\ No newline at end of file
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
new file mode 100644
index 0000000..2251168
--- /dev/null
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -0,0 +1,691 @@
+<script setup>
+import ControlRow from "aleksis.core/components/generic/multi_step/ControlRow.vue";
+import DateField from "aleksis.core/components/generic/forms/DateField.vue";
+import SexSelect from "aleksis.core/components/generic/forms/SexSelect.vue";
+
+import PrimaryActionButton from "aleksis.core/components/generic/buttons/PrimaryActionButton.vue";
+import SecondaryActionButton from "aleksis.core/components/generic/buttons/SecondaryActionButton.vue";
+</script>
+
+<template>
+    <main-container>
+      <div v-if="event && eventRegistrationSent">
+        <v-card>
+          <v-card-title>
+            <v-icon class="mr-2" color="success">mdi-check-circle-outline</v-icon>
+            {{ $t("paweljong.event_registration.form.submitted.thank_you") }}
+          </v-card-title>
+          <v-card-text class="text-body-1 black--text">
+            {{ $t("paweljong.event_registration.form.submitted.submitted_successfully") }}
+          </v-card-text>
+        </v-card>
+      </div>
+      <div v-else-if="event">
+        <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
+        <v-stepper v-model="step" class="mb-4">
+          <v-stepper-header>
+            <v-stepper-step :complete="step > 1" step="1">
+              {{ $t("paweljong.event_registration.form.steps.email.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 2" step="2">
+              {{ $t("paweljong.event_registration.form.steps.register.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 3" step="3">
+              {{ $t("paweljong.event_registration.form.steps.contact_details.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 4" step="4">
+              {{ $t("paweljong.event_registration.form.steps.guardians.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 5" step="5">
+              {{ $t("paweljong.event_registration.form.steps.additional.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 6" step="6">
+              {{ $t("paweljong.event_registration.form.steps.financial.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 7" step="7">
+              {{ $t("paweljong.event_registration.form.steps.consent.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+            <v-stepper-step :complete="step > 8" step="8">
+              {{ $t("paweljong.event_registration.form.steps.confirm.title") }}
+            </v-stepper-step>
+            <v-divider></v-divider>
+          </v-stepper-header>
+          <v-stepper-items>
+            <v-stepper-content step="1">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.email.title") }}</h2>
+              <div class="mb-4">
+                <v-row v-if="emailMode == 'postbuero'">
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.email.localPart"
+                      :label="$t('postbuero.mail_addresses.data_table.local_part')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.email.domain"
+                      :label="$t('postbuero.mail_addresses.data_table.domain')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row v-else-if="emailMode == 'own'">
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.user.email"
+                      :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
+                      required
+                      :rules="rules.email"
+                      prepend-icon="mdi-email-outline"
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.user.confirmEmail"
+                      :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
+                      required
+                      :rules="rules.confirmEmail"
+                      prepend-icon="mdi-email-outline"
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <template v-else>
+                  <v-card-text>
+                    {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
+                  </v-card-text>
+                  <v-card-actions>
+                    <primary-action-button
+                      @click="emailMode = 'postbuero'"
+                      i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis"
+                    />
+                    <primary-action-button
+                      @click="emailMode = 'own'"
+                      i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_own"
+                    />
+                  </v-card-actions>
+                </template>
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+
+            <v-stepper-content step="2">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.register.title") }}</h2>
+              <div class="mb-4">
+                <v-row>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.person.firstName"
+                      :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
+                      required
+                      :rules="rules.name"
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.person.lastName"
+                      :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
+                      required
+                      :rules="rules.name"
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.user.username"
+                      :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
+                      required
+                      :rules="rules.name"
+                      prepend-icon="mdi-account-outline"
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.user.password"
+                      :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
+                      required
+                      type="password"
+                      prepend-icon="mdi-form-textbox-password"
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.user.confirmPassword"
+                      :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
+                      required
+                      type="password"
+                      :rules="rules.confirmPassword"
+                      prepend-icon="mdi-form-textbox-password"
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+
+            <v-stepper-content step="3">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.contact_details.title") }}</h2>
+              <div class="mb-4">
+                <v-row>
+                  <v-col v-if="isFieldVisible('date_of_birth')">
+                    <date-field
+                      outlined
+                      v-model="data.person.dateOfBirth"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
+                      required
+                      prepend-icon="mdi-cake-variant-outline"
+                    />
+                  </v-col>
+                  <v-col v-if="isFieldVisible('sex')">
+                    <sex-select
+                      outlined
+                      v-model="data.person.sex"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
+                      persistent-hint
+                      required
+                    />
+                  </v-col>
+                  <v-col v-if="isFieldVisible('mobile_number')">
+                    <v-text-field
+                      outlined
+                      v-model="data.person.mobileNumber"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
+                      required
+                      prepend-icon="mdi-phone-outline"
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col v-if="isFieldVisible('street')">
+                    <v-text-field
+                      outlined
+                      v-model="data.person.address.street"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                  <v-col v-if="isFieldVisible('housenumber')">
+                    <v-text-field
+                      outlined
+                      v-model="data.person.address.housenumber"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col v-if="isFieldVisible('postal_code')">
+                    <v-text-field
+                      outlined
+                      v-model="data.person.address.postalCode"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                  <v-col v-if="isFieldVisible('place')">
+                    <v-text-field
+                      outlined
+                      v-model="data.person.address.place"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row v-if="isFieldVisible('school_details')">
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.school"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.schoolPlace"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.schoolClass"
+                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+
+            <v-stepper-content step="4">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.guardians.title") }}</h2>
+              <div class="mb-4">
+                <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
+                  <v-card-title>
+                    {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
+                  </v-card-title>
+                  <v-card-text>
+                    <v-row>
+                      <v-col>
+                        <v-text-field
+                          outlined
+                          v-model="guardian.firstName"
+                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
+                          required
+                        ></v-text-field>
+                      </v-col>
+                      <v-col>
+                        <v-text-field
+                          outlined
+                          v-model="guardian.lastName"
+                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
+                          required
+                        ></v-text-field>
+                      </v-col>
+                    </v-row>
+                    <v-row>
+                      <v-col>
+                        <v-text-field
+                          outlined
+                          v-model="guardian.email"
+                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
+                          required
+                        ></v-text-field>
+                      </v-col>
+                      <v-col>
+                        <v-text-field
+                          outlined
+                          v-model="guardian.mobileNumber"
+                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
+                          required
+                        ></v-text-field>
+                      </v-col>
+                    </v-row>
+                  </v-card-text>
+                </v-card>
+                <v-row class="mb-4">
+                  <v-col>
+                    <v-spacer />
+                    <secondary-action-button
+                      @click="addGuardian"
+                      i18n-key="paweljong.event_registration.form.steps.guardians.add"
+                    >
+                    </secondary-action-button>
+                  </v-col>
+                </v-row>
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+
+            <v-stepper-content step="5">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.additional.title") }}</h2>
+              <div class="mb-4">
+                <v-row>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.medicalInformation"
+                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
+                  <v-col>
+                    <component
+                      :is="fieldComponentForAdditionalField(additionalField)"
+                      v-model="data.additionalFields[additionalField.id]"
+                      outlined
+                      :label="additionalField.title"
+                      :hint="additionalField.helpText"
+                      persistent-hint
+                      :required="additionalField.required"
+                    />
+                  </v-col>
+                </v-row>
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+
+            <v-stepper-content step="6">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.financial.title") }}</h2>
+              <div class="mb-4">
+                <v-row>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.medicalInformation"
+                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.medicalInformation"
+                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.medicalInformation"
+                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      required
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+
+            <v-stepper-content step="7">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.consent.title") }}</h2>
+              <div class="mb-4">
+                <v-card v-for="term in event.terms" :key="`term-card-${term.id}`">
+                  <v-card-title>
+                    {{ term.title }}
+                  </v-card-title>
+                  <v-card-text v-html="term.term" />
+                </v-card>
+                <v-checkbox
+                  v-for="term in event.terms"
+                  :key="`term-checkbox-${term.id}`"
+                  required
+                  :label="term.confirmationText"
+                  v-model="data.terms[term.id]"
+                />
+                <v-checkbox
+                  v-if="event.dateRetraction"
+                  required
+                  :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
+                  v-model="data.retractionConsent"
+                />
+              </div>
+              <v-divider class="mb-4" />
+              <control-row
+                :step="step"
+                @set-step="setStep"
+              />
+            </v-stepper-content>
+  
+            <v-stepper-content step="8">
+              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.confirm.title") }}</h2>
+              <!-- <v-list class="mb-4">
+                <v-list-item>
+                  <v-list-item-content>
+                    <v-row>
+                      <v-col class="text-body-1 font-weight-medium">
+                        <v-icon color="primary" left class="mr-4"
+                          >mdi-account-outline</v-icon
+                        >
+                        {{ $t("order.personal_data.label_name") }}
+                      </v-col>
+                      <v-col class="text-body-1">
+                        {{ data.fullName }}
+                      </v-col>
+                    </v-row>
+                  </v-list-item-content>
+                </v-list-item>
+                <v-divider />
+                <v-list-item>
+                  <v-list-item-content>
+                    <v-row>
+                      <v-col class="text-body-1 font-weight-medium">
+                        <v-icon color="primary" left class="mr-4"
+                          >mdi-email-outline</v-icon
+                        >
+                        {{ $t("order.personal_data.label_email") }}
+                      </v-col>
+                      <v-col class="text-body-1">
+                        {{ data.email }}
+                      </v-col>
+                    </v-row>
+                  </v-list-item-content>
+                </v-list-item>
+              </v-list> -->
+
+              <v-divider class="my-4" />
+  
+              <message-box type="info" class="mb-4">
+                {{ $t("paweljong.event_registration.form.steps.confirm.hint") }}
+              </message-box>
+              <ApolloMutation
+                :mutation="require('./eventRegistrationMutation.graphql')"
+                :variables="{
+                  event: event.id,
+                  eventRegistration: dataForSubmit,
+                }"
+                @done="eventRegistrationDone"
+              >
+                <template #default="{ mutate, loading, error }">
+                  <control-row
+                    :step="step"
+                    final-step
+                    @set-step="setStep"
+                    @confirm="mutate"
+                    :next-loading="loading"
+                  />
+                </template>
+              </ApolloMutation>
+            </v-stepper-content>
+          </v-stepper-items>
+        </v-stepper>
+        <v-card>
+          <v-card-text>
+            <div>HELP TEXT</div>
+          </v-card-text>
+        </v-card>
+      </div>
+      <div v-else-if="$apollo.queries.event.loading">
+        <v-skeleton-loader type="heading, text, actions" />
+      </div>
+    </main-container>
+  </template>
+  
+  <script>
+  import { eventBySlug } from "../event/events.graphql";
+
+  import eventAdditionalFieldMixin from "../event_additional_field/eventAdditionalFieldMixin";
+
+  export default {
+    name: "EventRegistrationForm",
+    apollo: {
+      event: {
+        query: eventBySlug,
+        variables() {
+          return {
+            slug: this.slug,
+          };
+        },
+      }
+    },
+    mixins: [eventAdditionalFieldMixin],
+    watch: {
+    },
+    methods: {
+      setStep(step) {
+        this.step = step;
+      },
+      eventRegistrationDone({ data }) {
+        if (data.sendEventRegistration.ok) {
+          this.eventRegistrationSent = true;
+        }
+      },
+      isFieldVisible(fieldName) {
+        return this.event?.contactInformationVisibleFields.includes(fieldName);
+      },
+      addGuardian() {
+        this.data.person.guardians.push({
+            firstName: "",
+            lastName: "",
+            email: "",
+            mobileNumber: "",
+        });
+      },
+    },
+    props: {
+      slug: {
+        type: String,
+        required: true,
+      }
+    },
+    computed: {
+      rules() {
+        return {
+          name: [
+            (v) => !!v || this.$t("order.rules.name.required"),
+            (v) => v.length <= 255 || this.$t("order.rules.name.max"),
+          ],
+          email: [
+            (v) => /.+@.+\..+/.test(v) || this.$t("paweljong.event_registration.form.rules.email.valid"),
+          ],
+          confirmEmail: [
+            (v) => this.data.user.email == v || this.$t("paweljong.event_registration.form.rules.confirm_email.no_match"),
+          ],
+          confirmPassword: [
+            (v) => this.data.user.password == v || this.$t("paweljong.event_registration.form.rules.confirm_password.no_match"),
+          ],
+          street: [
+            (v) => !!v || this.$t("order.rules.street.required"),
+            (v) => v.length <= 255 || this.$t("order.rules.street.max"),
+          ],
+          housenumber: [
+            (v) => !!v || this.$t("order.rules.housenumber.required"),
+            (v) => v.length <= 255 || this.$t("order.rules.housenumber.max"),
+          ],
+          postalCode: [
+            (v) => !!v || this.$t("order.rules.postal_code.required"),
+            (v) => /^\d{5}$/.test(v) || this.$t("order.rules.postal_code.valid"),
+          ],
+          place: [
+            (v) => !!v || this.$t("order.rules.place.required"),
+            (v) => v.length <= 255 || this.$t("order.rules.place.max"),
+          ],
+        };
+      },
+      dataForSubmit() {
+        return {
+          ...this.data,
+        };
+      },
+    },
+    data() {
+      return {
+        eventRegistrationSent: false,
+        step: 1,
+        emailMode: null,
+        data: {
+          email: {
+            localPart: "",
+            domain: "",
+          },
+          person: {
+            firstName: "",
+            lastName: "",
+            dateOfBirth: "",
+            sex: "",
+            address: {
+              street: "",
+              housenumber: "",
+              postalCode: "",
+              place: "",
+            },
+            guardians: [
+              {
+                firstName: "",
+                lastName: "",
+                email: "",
+                mobileNumber: "",
+              },
+            ],
+            email: "",
+            mobileNumber: "",
+          },
+          user: {
+            username: "",
+            email: "",
+            confirmEmail: "",
+            password: "",
+            confirmPassword: "",
+          },
+          school: "",
+          schoolPlace: "",
+          schoolClass: "",
+          payment: {
+            paymentMethod: "",
+            voucherCode: "",
+            amount: "",
+          },
+          medicalInformation: "",
+          comment: "",
+          additionalFields: {},
+          terms: {},
+          retractionConsent: false,
+        },
+      };
+    },
+  };
+  </script>
+  
+  <style></style>
+  
\ No newline at end of file
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
new file mode 100644
index 0000000..35d25ba
--- /dev/null
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
@@ -0,0 +1,8 @@
+mutation sendOrder(
+  $event: ID!
+  $eventRegistration: EventRegistrationInputType!
+) {
+  sendEventRegistration(event: $event, eventRegistration: $eventRegistration) {
+    ok
+  }
+}
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
new file mode 100644
index 0000000..6c0cf30
--- /dev/null
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
@@ -0,0 +1,19 @@
+query whoAmI {
+  whoAmI {
+    id
+    username
+    person {
+      id
+      firstName
+      lastName
+      guardians {
+        id
+        firstName
+        lastName
+        mobileNumber
+        email
+      }
+      email
+    }
+  }
+}
diff --git a/aleksis/apps/paweljong/frontend/index.js b/aleksis/apps/paweljong/frontend/index.js
index a0891db..a3229cc 100644
--- a/aleksis/apps/paweljong/frontend/index.js
+++ b/aleksis/apps/paweljong/frontend/index.js
@@ -30,11 +30,9 @@ export default {
     },
     {
       path: "event/:slug/register/",
-      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      component: () => import("./components/event_registration/EventRegistrationForm.vue"),
       name: "paweljong.registerEventBySlug",
-      props: {
-        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-      },
+      props: true,
     },
     {
       path: "group_persons/:pk/add/",
diff --git a/aleksis/apps/paweljong/frontend/messages/en.json b/aleksis/apps/paweljong/frontend/messages/en.json
index 802ccc0..fcefaff 100644
--- a/aleksis/apps/paweljong/frontend/messages/en.json
+++ b/aleksis/apps/paweljong/frontend/messages/en.json
@@ -51,6 +51,202 @@
         "optional": "Optional"
       },
       "help_text": "Helptext"
+    },
+    "event_registration": {
+      "form": {
+        "submitted": {
+          "thank_you": "Thanks!",
+          "submitted_successfully": "Your registration was submitted successfully."
+        },
+        "steps": {
+          "email": {
+            "title": "E-Mail address",
+            "choose_mode": {
+              "help_text": "To continue, you'll need to provide an e-mail address. You can choose between registering an e-mail address with AlekSIS and using your own, existing e-mail address.",
+              "continue_aleksis": "Continue using new AlekSIS e-mail address",
+              "continue_own": "Continue using own e-mail address"
+            },
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "email": {
+                "label": "E-Mail address"
+              },
+              "confirm_email": {
+                "label": "Confirm e-mail address"
+              }
+            }
+          },
+          "register": {
+            "title": "Account",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "first_name": {
+                "label": "First name"
+              },
+              "last_name": {
+                "label": "Last name"
+              },
+              "username": {
+                "label": "Username"
+              },
+              "password": {
+                "label": "Password"
+              },
+              "confirm_password": {
+                "label": "Confirm password"
+              }
+            }
+          },
+          "contact_details": {
+            "title": "Contact information",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "first_name": {
+                "label": "First name"
+              },
+              "last_name": {
+                "label": "Last name"
+              },
+              "date_of_birth": {
+                "label": "Date of birth"
+              },
+              "sex": {
+                "label": "Sex",
+                "help_text": "For various reasons, e.g. because we have to keep gender segregation during the night for legal reasons, we need to know if you are a boy or a girl."
+              },
+              "street": {
+                "label": "Street"
+              },
+              "housenumber": {
+                "label": "Housenumber"
+              },
+              "postal_code": {
+                "label": "Postal code"
+              },
+              "place": {
+                "label": "Place"
+              },
+              "email": {
+                "label": "E-Mail address",
+                "help_text": "Please use your personal e-mail address here, which you will check personally. Important information will always be sent to your parents as well. Do not use an e-mail address owned by your parents here."
+              },
+              "mobile_number": {
+                "label": "Mobile number",
+                "help_text": "Your mobile number helps us to reach you in an emergency during the event, e.g. if you are alone with your group at a conference or similar. If you don't have a cell phone, you can leave the field blank."
+              },
+              "school": {
+                "label": "School",
+                "help_text": "Please enter the name of your school."
+              },
+              "school_place": {
+                "label": "School place",
+                "help_text": "Enter the place (city) where your school is located."
+              },
+              "school_class": {
+                "label": "School class",
+                "help_text": "Please enter the class you are in (e.g. 8a)."
+              }
+            }
+          },
+          "guardians": {
+            "title": "Legal guardians",
+            "counter": "Guardian {i}",
+            "add": "Add guardian",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "first_name": {
+                "label": "Guardian's first name",
+                "help_text": "Please enter the first name of the legal guardian who will fill in the registration with you and who can be reached during the event in an emergency."
+              },
+              "last_name": {
+                "label": "Guardian's last name",
+                "help_text": "Please enter the last name of the legal guardian who will fill in the registration with you and who can be reached during the event in an emergency."
+              },
+              "email": {
+                "label": "Guardian's email address"
+              },
+              "mobile_number": {
+                "label": "Guardian's mobile number",
+                "help_text": "We need the mobile phone number for emergencies if we urgently need to reach your parents during the event."
+              }
+            }
+          },
+          "additional": {
+            "title": "Additional information",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "medical_information": {
+                "label": "Medical information / intolerances",
+                "help_text": "If there are any medically important things we need to consider, e.g. when making food or to make sure you take prescribed medication, please enter it here."
+              }
+            }
+          },
+          "financial": {
+            "title": "Payment",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "": {
+                "": ""
+              }
+            }
+          },
+          "consent": {
+            "title": "Consent",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "retraction_consent": {
+                "label": "I confirm that the retraction of the registration is not possible anymore after {date}"
+              }
+            }
+          },
+          "confirm": {
+            "title": "Confirm",
+            "hint": "After this step, you won't be able to edit your registration.",
+            "help_texts": {
+              "participant": "",
+              "guardian": ""
+            },
+            "fields": {
+              "comment": {
+                "label": "Other remarks",
+                "help_text": "You can write down any remarks you want to tell us here."
+              }
+            }
+          }
+        },
+        "rules": {
+          "email": {
+            "valid": "This is not a valid e-mail address"
+          },
+          "confirm_email": {
+            "no_match": "The e-mail addresses do not match"
+          },
+          "confirm_password": {
+            "no_match": "The passwords do not match"
+          }
+        }
+      }
     }
   }
 }
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
new file mode 100644
index 0000000..dd070d2
--- /dev/null
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -0,0 +1,49 @@
+from django.core.exceptions import PermissionDenied
+
+import graphene
+from graphene_django import DjangoObjectType
+from graphql import GraphQLError
+
+from aleksis.core.schema.base import FilterOrderList
+
+from .checkpoint import CheckpointCheckInMutation
+from .event import EventType
+from .event_additional_field import (
+    EventAdditionalFieldBatchCreateMutation,
+    EventAdditionalFieldBatchDeleteMutation,
+    EventAdditionalFieldBatchPatchMutation,
+    EventAdditionalFieldType
+)
+from .terms import TermsType
+from ..models import Event
+
+
+class Query(graphene.ObjectType):
+    event_additional_fields = FilterOrderList(EventAdditionalFieldType)
+
+    event_by_id = graphene.Field(EventType, id=graphene.ID())
+    event_by_slug = graphene.Field(EventType, slug=graphene.String())
+
+    terms = FilterOrderList(TermsType)
+
+    @staticmethod
+    def resolve_event_by_id(root, info, id):
+        event = Event.objects.get(pk=id)
+        # if not info.context.user.has_perm("paweljong.view_person_rule", event):
+        #     return None
+        return event
+
+    @staticmethod
+    def resolve_event_by_slug(root, info, slug):
+        event = Event.objects.get(slug=slug)
+        # if not info.context.user.has_perm("paweljong.view_person_rule", event):
+        #     return None
+        return event
+
+
+class Mutation(graphene.ObjectType):
+    checkpoint_check_in = CheckpointCheckInMutation.Field()
+
+    create_event_additional_fields = EventAdditionalFieldBatchCreateMutation.Field()
+    delete_event_additional_fields = EventAdditionalFieldBatchDeleteMutation.Field()
+    update_event_additional_fields = EventAdditionalFieldBatchPatchMutation.Field()
diff --git a/aleksis/apps/paweljong/schema.py b/aleksis/apps/paweljong/schema/checkpoint.py
similarity index 50%
rename from aleksis/apps/paweljong/schema.py
rename to aleksis/apps/paweljong/schema/checkpoint.py
index 7db42cc..5c1b03c 100644
--- a/aleksis/apps/paweljong/schema.py
+++ b/aleksis/apps/paweljong/schema/checkpoint.py
@@ -3,24 +3,12 @@ from django.utils import timezone
 
 import graphene
 from graphene_django import DjangoObjectType
-from graphql import GraphQLError
 
-from aleksis.core.models import Person
-from aleksis.core.schema.base import (
-    BaseBatchCreateMutation,
-    BaseBatchDeleteMutation,
-    BaseBatchPatchMutation,
-    FilterOrderList,
-    PermissionsTypeMixin,
-)
+from aleksis.core.schema.base import PermissionsTypeMixin
 from aleksis.core.util.core_helpers import has_person
+from aleksis.core.models import Person
 
-from .models import Checkpoint, Event, EventAdditionalField
-
-
-class EventType(PermissionsTypeMixin, DjangoObjectType):
-    class Meta:
-        model = Event
+from ..models import Checkpoint, Event
 
 
 class CheckpointType(PermissionsTypeMixin, DjangoObjectType):
@@ -28,11 +16,6 @@ class CheckpointType(PermissionsTypeMixin, DjangoObjectType):
         model = Checkpoint
 
 
-class EventAdditionalFieldType(PermissionsTypeMixin, DjangoObjectType):
-    class Meta:
-        model = EventAdditionalField
-
-
 class CheckpointCheckInMutation(graphene.Mutation):
     class Arguments:
         event_slug = graphene.String(required=True)
@@ -84,46 +67,3 @@ class CheckpointCheckInMutation(graphene.Mutation):
         checkpoint.save()
 
         return CheckpointCheckInMutation(checkpoint=checkpoint)
-
-
-class EventAdditionalFieldBatchCreateMutation(BaseBatchCreateMutation):
-    class Meta:
-        model = EventAdditionalField
-        permissions = ("paweljong.create_event_additional_field_rule",)
-        only_fields = (
-            "title",
-            "field_type",
-            "required",
-            "help_text",
-        )
-
-
-class EventAdditionalFieldBatchDeleteMutation(BaseBatchDeleteMutation):
-    class Meta:
-        model = EventAdditionalField
-        permissions = ("paweljong.delete_event_additional_field_rule",)
-
-
-class EventAdditionalFieldBatchPatchMutation(BaseBatchPatchMutation):
-    class Meta:
-        model = EventAdditionalField
-        permissions = ("paweljong.edit_event_additional_field_rule",)
-        only_fields = (
-            "id",
-            "title",
-            "field_type",
-            "required",
-            "help_text",
-        )
-
-
-class Query(graphene.ObjectType):
-    event_additional_fields = FilterOrderList(EventAdditionalFieldType)
-
-
-class Mutation(graphene.ObjectType):
-    checkpoint_check_in = CheckpointCheckInMutation.Field()
-
-    create_event_additional_fields = EventAdditionalFieldBatchCreateMutation.Field()
-    delete_event_additional_fields = EventAdditionalFieldBatchDeleteMutation.Field()
-    update_event_additional_fields = EventAdditionalFieldBatchPatchMutation.Field()
diff --git a/aleksis/apps/paweljong/schema/event.py b/aleksis/apps/paweljong/schema/event.py
new file mode 100644
index 0000000..539969c
--- /dev/null
+++ b/aleksis/apps/paweljong/schema/event.py
@@ -0,0 +1,10 @@
+from graphene_django import DjangoObjectType
+
+from aleksis.core.schema.base import PermissionsTypeMixin
+
+from ..models import Event
+
+
+class EventType(PermissionsTypeMixin, DjangoObjectType):
+    class Meta:
+        model = Event
diff --git a/aleksis/apps/paweljong/schema/event_additional_field.py b/aleksis/apps/paweljong/schema/event_additional_field.py
new file mode 100644
index 0000000..a011428
--- /dev/null
+++ b/aleksis/apps/paweljong/schema/event_additional_field.py
@@ -0,0 +1,46 @@
+from graphene_django import DjangoObjectType
+
+from aleksis.core.schema.base import (
+    BaseBatchCreateMutation,
+    BaseBatchDeleteMutation,
+    BaseBatchPatchMutation,
+    PermissionsTypeMixin,
+)
+
+from ..models import EventAdditionalField
+
+
+class EventAdditionalFieldType(PermissionsTypeMixin, DjangoObjectType):
+    class Meta:
+        model = EventAdditionalField
+
+
+class EventAdditionalFieldBatchCreateMutation(BaseBatchCreateMutation):
+    class Meta:
+        model = EventAdditionalField
+        permissions = ("paweljong.create_event_additional_field_rule",)
+        only_fields = (
+            "title",
+            "field_type",
+            "required",
+            "help_text",
+        )
+
+
+class EventAdditionalFieldBatchDeleteMutation(BaseBatchDeleteMutation):
+    class Meta:
+        model = EventAdditionalField
+        permissions = ("paweljong.delete_event_additional_field_rule",)
+
+
+class EventAdditionalFieldBatchPatchMutation(BaseBatchPatchMutation):
+    class Meta:
+        model = EventAdditionalField
+        permissions = ("paweljong.edit_event_additional_field_rule",)
+        only_fields = (
+            "id",
+            "title",
+            "field_type",
+            "required",
+            "help_text",
+        )
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
new file mode 100644
index 0000000..a63bdf9
--- /dev/null
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -0,0 +1,26 @@
+import graphene
+from graphene_django import DjangoObjectType
+
+from aleksis.core.schema.base import PermissionsTypeMixin
+
+from ..models import EventRegistration
+
+
+class EventRegistrationType(PermissionsTypeMixin, DjangoObjectType):
+    class Meta:
+        model = EventRegistration
+
+
+class EventRegistrationInputType(graphene.InputObjectType):
+    full_name = graphene.String(required=True)
+    email = graphene.String(required=True)
+    notes = graphene.String(required=False)
+    shipping_option = graphene.ID(required=True)
+    payment_option = graphene.ID(required=True)
+    items = graphene.List(OrderItemInputType, required=True)
+    shipping_full_name = graphene.String(required=False)
+    second_address_row = graphene.String(required=False)
+    street = graphene.String(required=False)
+    housenumber = graphene.String(required=False)
+    plz = graphene.String(required=False)
+    place = graphene.String(required=False)
diff --git a/aleksis/apps/paweljong/schema/terms.py b/aleksis/apps/paweljong/schema/terms.py
new file mode 100644
index 0000000..0db8dc2
--- /dev/null
+++ b/aleksis/apps/paweljong/schema/terms.py
@@ -0,0 +1,10 @@
+from graphene_django import DjangoObjectType
+
+from aleksis.core.schema.base import PermissionsTypeMixin
+
+from ..models import Terms
+
+
+class TermsType(PermissionsTypeMixin, DjangoObjectType):
+    class Meta:
+        model = Terms
-- 
GitLab


From 31f073bc3d841541940691daa2783f08d8c22031 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 11:57:26 +0100
Subject: [PATCH 03/72] Add payment step

---
 .../EventRegistrationForm.vue                 | 60 ++++++++++++++-----
 .../event_registration/helpers.graphql        |  7 +++
 .../apps/paweljong/frontend/messages/en.json  | 52 ++++++----------
 aleksis/apps/paweljong/preferences.py         | 22 +++++++
 aleksis/apps/paweljong/schema/__init__.py     | 19 ++++++
 5 files changed, 113 insertions(+), 47 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 2251168..bb9e703 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -1,6 +1,7 @@
 <script setup>
 import ControlRow from "aleksis.core/components/generic/multi_step/ControlRow.vue";
 import DateField from "aleksis.core/components/generic/forms/DateField.vue";
+import PositiveSmallIntegerField from "aleksis.core/components/generic/forms/PositiveSmallIntegerField.vue";
 import SexSelect from "aleksis.core/components/generic/forms/SexSelect.vue";
 
 import PrimaryActionButton from "aleksis.core/components/generic/buttons/PrimaryActionButton.vue";
@@ -399,33 +400,58 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             <v-stepper-content step="6">
               <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.financial.title") }}</h2>
               <div class="mb-4">
+                <message-box type="info" class="mb-4">
+                  {{ $t("paweljong.event_registration.form.steps.financial.help_text.cost_info") }}
+                </message-box>
+                <v-simple-table class="mb-4">
+                  <template v-slot:default>
+                    <tbody>
+                      <tr>
+                        <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.min_cost") }}</td>
+                        <td>{{ `${event.minCost} €` }}</td>
+                      </tr>
+                      <tr>
+                        <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.cost") }}</td>
+                        <td>{{ `${event.cost} €` }}</td>
+                      </tr>
+                      <tr v-if="event.maxCost">
+                        <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.max_cost") }}</td>
+                        <td>{{ `${event.maxCost} €` }}</td>
+                      </tr>
+                    </tbody>
+                  </template>
+                </v-simple-table>
                 <v-row>
                   <v-col>
-                    <v-text-field
+                    <v-select
+                      :items="paweljongPaymentChoices"
+                      item-text="text"
+                      item-value="variant"
                       outlined
-                      v-model="data.medicalInformation"
-                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      v-model="data.payment.paymentMethod"
+                      :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
+                      persistent-hint
                       required
-                    ></v-text-field>
+                    ></v-select>
                   </v-col>
                 </v-row>
                 <v-row>
                   <v-col>
-                    <v-text-field
+                    <positive-small-integer-field
                       outlined
-                      v-model="data.medicalInformation"
-                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      v-model="data.payment.amount"
+                      :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
+                      :rules="rules.amount"
                       required
-                    ></v-text-field>
+                    />
                   </v-col>
                   <v-col>
                     <v-text-field
                       outlined
-                      v-model="data.medicalInformation"
-                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      v-model="data.payment.voucherCode"
+                      :label="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.label')"
+                      :hint="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.help_text')"
                       required
                     ></v-text-field>
                   </v-col>
@@ -544,6 +570,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   
   <script>
   import { eventBySlug } from "../event/events.graphql";
+  import { gqlPaweljongPaymentChoices } from "./helpers.graphql";
 
   import eventAdditionalFieldMixin from "../event_additional_field/eventAdditionalFieldMixin";
 
@@ -557,7 +584,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             slug: this.slug,
           };
         },
-      }
+      },
+      paweljongPaymentChoices: gqlPaweljongPaymentChoices,
     },
     mixins: [eventAdditionalFieldMixin],
     watch: {
@@ -605,6 +633,10 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           confirmPassword: [
             (v) => this.data.user.password == v || this.$t("paweljong.event_registration.form.rules.confirm_password.no_match"),
           ],
+          amount: [
+            (v) => v > this.event.minCost || this.$t("paweljong.event_registration.form.rules.amount.too_low"),
+            (v) => v < this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
+          ],
           street: [
             (v) => !!v || this.$t("order.rules.street.required"),
             (v) => v.length <= 255 || this.$t("order.rules.street.max"),
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
index 6c0cf30..062a272 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
@@ -17,3 +17,10 @@ query whoAmI {
     }
   }
 }
+
+query gqlPaweljongPaymentChoices {
+  paweljongPaymentChoices {
+    variant
+    text
+  }
+}
diff --git a/aleksis/apps/paweljong/frontend/messages/en.json b/aleksis/apps/paweljong/frontend/messages/en.json
index fcefaff..298588c 100644
--- a/aleksis/apps/paweljong/frontend/messages/en.json
+++ b/aleksis/apps/paweljong/frontend/messages/en.json
@@ -66,10 +66,6 @@
               "continue_aleksis": "Continue using new AlekSIS e-mail address",
               "continue_own": "Continue using own e-mail address"
             },
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "email": {
                 "label": "E-Mail address"
@@ -81,10 +77,6 @@
           },
           "register": {
             "title": "Account",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "first_name": {
                 "label": "First name"
@@ -105,10 +97,6 @@
           },
           "contact_details": {
             "title": "Contact information",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "first_name": {
                 "label": "First name"
@@ -161,10 +149,6 @@
             "title": "Legal guardians",
             "counter": "Guardian {i}",
             "add": "Add guardian",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "first_name": {
                 "label": "Guardian's first name",
@@ -185,10 +169,6 @@
           },
           "additional": {
             "title": "Additional information",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "medical_information": {
                 "label": "Medical information / intolerances",
@@ -198,22 +178,28 @@
           },
           "financial": {
             "title": "Payment",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
+            "help_text": {
+              "cost": "Real costs",
+              "max_cost": "Maximal payable amount",
+              "min_cost": "Minimal payable amount",
+              "cost_info": "Our association would like to offer all children and young people the opportunity to participate in our events. Sometimes, however, families cannot afford the full fee. We therefore have a budget from which we can promote participation after we have carefully examined the necessity and eligibility. We rely on donations for this budget. If you would like to donate a voluntary additional amount for this budget, please enter an amount higher than our costs. If you cannot afford the full fee, please enter an amount that fits your financial capacities."
             },
             "fields": {
-              "": {
-                "": ""
+              "payment_method": {
+                "label": "Payment method",
+                "help_text": "Please choose a payment method. The actual payment will be made after the registration."
+              },
+              "voucher_code": {
+                "label": "Voucher code",
+                "help_text": "If you have a voucher code, type it in here."
+              },
+              "amount": {
+                "label": "Amount paid by you (in €)"
               }
             }
           },
           "consent": {
             "title": "Consent",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "retraction_consent": {
                 "label": "I confirm that the retraction of the registration is not possible anymore after {date}"
@@ -223,10 +209,6 @@
           "confirm": {
             "title": "Confirm",
             "hint": "After this step, you won't be able to edit your registration.",
-            "help_texts": {
-              "participant": "",
-              "guardian": ""
-            },
             "fields": {
               "comment": {
                 "label": "Other remarks",
@@ -244,6 +226,10 @@
           },
           "confirm_password": {
             "no_match": "The passwords do not match"
+          },
+          "amount": {
+            "too_high": "The amount is higher than the maximal payable amount.",
+            "too_low": "The amount is lower than the minimal payable amount."
           }
         }
       }
diff --git a/aleksis/apps/paweljong/preferences.py b/aleksis/apps/paweljong/preferences.py
index a3a9a2c..b0b21a8 100644
--- a/aleksis/apps/paweljong/preferences.py
+++ b/aleksis/apps/paweljong/preferences.py
@@ -20,3 +20,25 @@ class EventNotificationRecipient(StringPreference):
     verbose_name = _("Mail recipient for event notifications")
     field_class = EmailField
     required = True
+
+
+@site_preferences_registry.register
+class EventInvoiceClientName(StringPreference):
+    """Name to use for the client linked to invoices generated for events."""
+
+    section = paweljong
+    name = "event_invoice_client_name"
+    default = "Teckids e.V."
+    verbose_name = _("Name of client linked to invoices for events.")
+    required = True
+
+
+@site_preferences_registry.register
+class EventInvoiceGroupName(StringPreference):
+    """Name to use for the invoice group used for events."""
+
+    section = paweljong
+    name = "event_invoice_group_name"
+    default = "Hack'n'Fun-Veranstaltungen"
+    verbose_name = _("Name of invoice group used for events.")
+    required = True
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
index dd070d2..ec9ca2a 100644
--- a/aleksis/apps/paweljong/schema/__init__.py
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -5,6 +5,10 @@ from graphene_django import DjangoObjectType
 from graphql import GraphQLError
 
 from aleksis.core.schema.base import FilterOrderList
+from aleksis.core.util.core_helpers import get_site_preferences
+from aleksis.apps.tezor.models.base import Client
+from aleksis.apps.tezor.models.invoice import InvoiceGroup
+from aleksis.apps.tezor.schema.client import PaymentVariantChoiceType
 
 from .checkpoint import CheckpointCheckInMutation
 from .event import EventType
@@ -26,6 +30,8 @@ class Query(graphene.ObjectType):
 
     terms = FilterOrderList(TermsType)
 
+    paweljong_payment_choices = graphene.List(PaymentVariantChoiceType)
+
     @staticmethod
     def resolve_event_by_id(root, info, id):
         event = Event.objects.get(pk=id)
@@ -40,6 +46,19 @@ class Query(graphene.ObjectType):
         #     return None
         return event
 
+    @staticmethod
+    def resolve_paweljong_payment_choices(root, info):
+        client, __ = Client.objects.get_or_create(name=get_site_preferences()["paweljong__event_invoice_client_name"])
+        group, __ = InvoiceGroup.objects.get_or_create(
+            name=get_site_preferences()["paweljong__event_invoice_group_name"],
+            client=client,
+            defaults={
+                "template_name": "paweljong/invoice_pdf.html",
+            },
+        )
+
+        return group.get_variant_choices()
+
 
 class Mutation(graphene.ObjectType):
     checkpoint_check_in = CheckpointCheckInMutation.Field()
-- 
GitLab


From ceccd0579c515b4f5fe103560f08a242de46feb7 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 12:26:11 +0100
Subject: [PATCH 04/72] Add optional steps

---
 .../EventRegistrationForm.vue                 | 149 ++++++++----------
 1 file changed, 67 insertions(+), 82 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index bb9e703..a7ae960 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -25,42 +25,16 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
         <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
         <v-stepper v-model="step" class="mb-4">
           <v-stepper-header>
-            <v-stepper-step :complete="step > 1" step="1">
-              {{ $t("paweljong.event_registration.form.steps.email.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 2" step="2">
-              {{ $t("paweljong.event_registration.form.steps.register.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 3" step="3">
-              {{ $t("paweljong.event_registration.form.steps.contact_details.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 4" step="4">
-              {{ $t("paweljong.event_registration.form.steps.guardians.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 5" step="5">
-              {{ $t("paweljong.event_registration.form.steps.additional.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 6" step="6">
-              {{ $t("paweljong.event_registration.form.steps.financial.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 7" step="7">
-              {{ $t("paweljong.event_registration.form.steps.consent.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
-            <v-stepper-step :complete="step > 8" step="8">
-              {{ $t("paweljong.event_registration.form.steps.confirm.title") }}
-            </v-stepper-step>
-            <v-divider></v-divider>
+            <template v-for="(stepChoice, index) in steps">
+              <v-stepper-step :complete="step > index + 1" :step="index + 1" :key="stepChoice.name">
+                {{ $t(stepChoice.titleKey) }}
+              </v-stepper-step>
+              <v-divider></v-divider>
+            </template>
           </v-stepper-header>
           <v-stepper-items>
-            <v-stepper-content step="1">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.email.title") }}</h2>
+            <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
               <div class="mb-4">
                 <v-row v-if="emailMode == 'postbuero'">
                   <v-col>
@@ -125,8 +99,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
 
-            <v-stepper-content step="2">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.register.title") }}</h2>
+            <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
               <div class="mb-4">
                 <v-row>
                   <v-col>
@@ -189,8 +163,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
 
-            <v-stepper-content step="3">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.contact_details.title") }}</h2>
+            <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
               <div class="mb-4">
                 <v-row>
                   <v-col v-if="isFieldVisible('date_of_birth')">
@@ -295,8 +269,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
 
-            <v-stepper-content step="4">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.guardians.title") }}</h2>
+            <v-stepper-content :step="getStepIndex('guardians')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
               <div class="mb-4">
                 <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
                   <v-card-title>
@@ -362,8 +336,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
 
-            <v-stepper-content step="5">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.additional.title") }}</h2>
+            <v-stepper-content :step="getStepIndex('additional')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
               <div class="mb-4">
                 <v-row>
                   <v-col>
@@ -397,8 +371,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
 
-            <v-stepper-content step="6">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.financial.title") }}</h2>
+            <v-stepper-content v-if="isStepEnabled('financial')" :step="getStepIndex('financial')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('financial')) }}</h2>
               <div class="mb-4">
                 <message-box type="info" class="mb-4">
                   {{ $t("paweljong.event_registration.form.steps.financial.help_text.cost_info") }}
@@ -464,8 +438,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
 
-            <v-stepper-content step="7">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.consent.title") }}</h2>
+            <v-stepper-content :step="getStepIndex('consent')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('consent')) }}</h2>
               <div class="mb-4">
                 <v-card v-for="term in event.terms" :key="`term-card-${term.id}`">
                   <v-card-title>
@@ -494,42 +468,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               />
             </v-stepper-content>
   
-            <v-stepper-content step="8">
-              <h2 class="text-h6 mb-4">{{ $t("paweljong.event_registration.form.steps.confirm.title") }}</h2>
-              <!-- <v-list class="mb-4">
-                <v-list-item>
-                  <v-list-item-content>
-                    <v-row>
-                      <v-col class="text-body-1 font-weight-medium">
-                        <v-icon color="primary" left class="mr-4"
-                          >mdi-account-outline</v-icon
-                        >
-                        {{ $t("order.personal_data.label_name") }}
-                      </v-col>
-                      <v-col class="text-body-1">
-                        {{ data.fullName }}
-                      </v-col>
-                    </v-row>
-                  </v-list-item-content>
-                </v-list-item>
-                <v-divider />
-                <v-list-item>
-                  <v-list-item-content>
-                    <v-row>
-                      <v-col class="text-body-1 font-weight-medium">
-                        <v-icon color="primary" left class="mr-4"
-                          >mdi-email-outline</v-icon
-                        >
-                        {{ $t("order.personal_data.label_email") }}
-                      </v-col>
-                      <v-col class="text-body-1">
-                        {{ data.email }}
-                      </v-col>
-                    </v-row>
-                  </v-list-item-content>
-                </v-list-item>
-              </v-list> -->
-
+            <v-stepper-content :step="getStepIndex('confirm')">
+              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('confirm')) }}</h2>
               <v-divider class="my-4" />
   
               <message-box type="info" class="mb-4">
@@ -610,6 +550,15 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             mobileNumber: "",
         });
       },
+      isStepEnabled(stepName) {
+        return this.steps.some((s) => s.name === stepName);
+      },
+      getStepIndex(stepName) {
+        return this.steps.findIndex((s) => s.name === stepName) + 1;
+      },
+      getStepTitleKey(stepName) {
+        return this.steps.find((s) => s.name === stepName)?.titleKey;
+      },
     },
     props: {
       slug: {
@@ -660,6 +609,42 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           ...this.data,
         };
       },
+      steps() {
+        return [
+          {
+            name: "email",
+            titleKey: "paweljong.event_registration.form.steps.email.title",
+          },
+          {
+            name: "register",
+            titleKey: "paweljong.event_registration.form.steps.register.title",
+          },
+          ... (this.event?.contactInformationVisibleFields.length) ? [{
+            name: "contact_details",
+            titleKey: "paweljong.event_registration.form.steps.contact_details.title",
+          }] : [],
+          {
+            name: "guardians",
+            titleKey: "paweljong.event_registration.form.steps.guardians.title",
+          },
+          {
+            name: "additional",
+            titleKey: "paweljong.event_registration.form.steps.additional.title",
+          },
+          ... (this.event.cost !== 0 && this.event.maxCost !== 0) ? [{
+            name: "financial",
+            titleKey: "paweljong.event_registration.form.steps.financial.title",
+          }] : [],
+          {
+            name: "consent",
+            titleKey: "paweljong.event_registration.form.steps.consent.title",
+          },
+          {
+            name: "confirm",
+            titleKey: "paweljong.event_registration.form.steps.confirm.title",
+          },
+        ];
+      }
     },
     data() {
       return {
-- 
GitLab


From 3c3e540240c1e8e3edb472dd0e98ffc807e1db7d Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 12:29:10 +0100
Subject: [PATCH 05/72] Don't show last divider in header

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index a7ae960..114b618 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -29,7 +29,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <v-stepper-step :complete="step > index + 1" :step="index + 1" :key="stepChoice.name">
                 {{ $t(stepChoice.titleKey) }}
               </v-stepper-step>
-              <v-divider></v-divider>
+              <v-divider v-if="index + 1 < steps.length" ></v-divider>
             </template>
           </v-stepper-header>
           <v-stepper-items>
-- 
GitLab


From 0b76f7555e5645d66b7dfc07959573058b646e35 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 12:29:58 +0100
Subject: [PATCH 06/72] Remove reference to main-container

---
 .../components/event_registration/EventRegistrationForm.vue   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 114b618..ab0b547 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -9,7 +9,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
 </script>
 
 <template>
-    <main-container>
+    <div>
       <div v-if="event && eventRegistrationSent">
         <v-card>
           <v-card-title>
@@ -505,7 +505,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       <div v-else-if="$apollo.queries.event.loading">
         <v-skeleton-loader type="heading, text, actions" />
       </div>
-    </main-container>
+    </div>
   </template>
   
   <script>
-- 
GitLab


From 791b62027fefb66dc6211b001d34f0ab2c696732 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 12:34:05 +0100
Subject: [PATCH 07/72] Hide account registration steps when user is
 authenticated

---
 .../event_registration/EventRegistrationForm.vue          | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index ab0b547..e0b8873 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -611,14 +611,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       },
       steps() {
         return [
-          {
+          ... (this.$root.whoAmI.isAnonymous) ? [{
             name: "email",
             titleKey: "paweljong.event_registration.form.steps.email.title",
-          },
-          {
+          }] : [],
+          ... (this.$root.whoAmI.isAnonymous) ? [{
             name: "register",
             titleKey: "paweljong.event_registration.form.steps.register.title",
-          },
+          }] : [],
           ... (this.event?.contactInformationVisibleFields.length) ? [{
             name: "contact_details",
             titleKey: "paweljong.event_registration.form.steps.contact_details.title",
-- 
GitLab


From d56b099ada926bf7010aa393baed8024cdf2b06d Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 12:49:09 +0100
Subject: [PATCH 08/72] Use proper select field for email domains

---
 .../event_registration/EventRegistrationForm.vue | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index e0b8873..e843eb0 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -46,12 +46,18 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     ></v-text-field>
                   </v-col>
                   <v-col>
-                    <v-text-field
+                    <v-autocomplete
                       outlined
+                      hide-no-data
+                      :items="mailDomains"
+                      item-text="domain"
+                      item-value="id"
+                      :loading="$apollo.queries.mailDomains.loading"
+                      prepend-icon="mdi-at"
                       v-model="data.email.domain"
                       :label="$t('postbuero.mail_addresses.data_table.domain')"
                       required
-                    ></v-text-field>
+                    />
                   </v-col>
                 </v-row>
                 <v-row v-else-if="emailMode == 'own'">
@@ -511,6 +517,10 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   <script>
   import { eventBySlug } from "../event/events.graphql";
   import { gqlPaweljongPaymentChoices } from "./helpers.graphql";
+  import {
+    mailDomainsForUser,
+    disallowedLocalParts,
+  } from "aleksis.apps.postbuero/components/mail_addresses/mailAddresses.graphql";
 
   import eventAdditionalFieldMixin from "../event_additional_field/eventAdditionalFieldMixin";
 
@@ -526,6 +536,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
         },
       },
       paweljongPaymentChoices: gqlPaweljongPaymentChoices,
+      mailDomains: mailDomainsForUser,
+      disallowedLocalParts: disallowedLocalParts,
     },
     mixins: [eventAdditionalFieldMixin],
     watch: {
-- 
GitLab


From 210597340d1310723b2b513e6ef99d27a9a3d45b Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 12:51:22 +0100
Subject: [PATCH 09/72] Add email local part rules

---
 .../event_registration/EventRegistrationForm.vue   | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index e843eb0..144f881 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -42,6 +42,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       outlined
                       v-model="data.email.localPart"
                       :label="$t('postbuero.mail_addresses.data_table.local_part')"
+                      :rules="rules.emailLocalPart"
                       required
                     ></v-text-field>
                   </v-col>
@@ -591,6 +592,19 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           confirmEmail: [
             (v) => this.data.user.email == v || this.$t("paweljong.event_registration.form.rules.confirm_email.no_match"),
           ],
+          emailLocalPart: [
+            (v) => !!v || this.$t("forms.errors.required"),
+            (v) =>
+              /^\w+([.!#$%&'*+-\/=?^_`{|}~]?\w+)*$/.test(v) ||
+              this.$t(
+                "postbuero.mail_addresses.data_table.errors.local_part_invalid_characters",
+              ),
+            (v) =>
+              this.disallowedLocalParts.indexOf(v) === -1 ||
+              this.$t(
+                "postbuero.mail_addresses.data_table.errors.local_part_disallowed",
+              ),
+          ],
           confirmPassword: [
             (v) => this.data.user.password == v || this.$t("paweljong.event_registration.form.rules.confirm_password.no_match"),
           ],
-- 
GitLab


From 68be5db20b9a949f5e978b030179236a5d2442cd Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 13:11:22 +0100
Subject: [PATCH 10/72] Refactor data for sending

---
 .../event_registration/EventRegistrationForm.vue       | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 144f881..96ab50a 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -631,8 +631,13 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
         };
       },
       dataForSubmit() {
+        const { confirmEmail, confirmPassword, ...filteredUserData } = this.data.user;
+
         return {
           ...this.data,
+          additionalFields: JSON.stringify(this.data.additionalFields),
+          terms: JSON.stringify(this.data.terms),
+          user: filteredUserData,
         };
       },
       steps() {
@@ -701,7 +706,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                 mobileNumber: "",
               },
             ],
-            email: "",
             mobileNumber: "",
           },
           user: {
@@ -717,13 +721,13 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           payment: {
             paymentMethod: "",
             voucherCode: "",
-            amount: "",
+            amount: null,
           },
           medicalInformation: "",
           comment: "",
           additionalFields: {},
           terms: {},
-          retractionConsent: false,
+          retractionConsent: null,
         },
       };
     },
-- 
GitLab


From 471f707948df47189a413a0ffcd5f0aed495818b Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 13:30:18 +0100
Subject: [PATCH 11/72] Add form field rules

---
 .../EventRegistrationForm.vue                 | 496 ++++++++++--------
 1 file changed, 284 insertions(+), 212 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 96ab50a..7db4e2e 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -38,49 +38,58 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <div class="mb-4">
                 <v-row v-if="emailMode == 'postbuero'">
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.email.localPart"
-                      :label="$t('postbuero.mail_addresses.data_table.local_part')"
-                      :rules="rules.emailLocalPart"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.email.localPart"
+                        :label="$t('postbuero.mail_addresses.data_table.local_part')"
+                        :rules="$rules().required.build([rules.emailLocalPart])"
+                        required
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-autocomplete
-                      outlined
-                      hide-no-data
-                      :items="mailDomains"
-                      item-text="domain"
-                      item-value="id"
-                      :loading="$apollo.queries.mailDomains.loading"
-                      prepend-icon="mdi-at"
-                      v-model="data.email.domain"
-                      :label="$t('postbuero.mail_addresses.data_table.domain')"
-                      required
-                    />
+                    <div aria-required="true">
+                      <v-autocomplete
+                        outlined
+                        hide-no-data
+                        :items="mailDomains"
+                        item-text="domain"
+                        item-value="id"
+                        :loading="$apollo.queries.mailDomains.loading"
+                        prepend-icon="mdi-at"
+                        v-model="data.email.domain"
+                        :label="$t('postbuero.mail_addresses.data_table.domain')"
+                        required
+                        :rules="$rules().required.build()"
+                      />
+                    </div>
                   </v-col>
                 </v-row>
                 <v-row v-else-if="emailMode == 'own'">
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.user.email"
-                      :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
-                      required
-                      :rules="rules.email"
-                      prepend-icon="mdi-email-outline"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.email"
+                        :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
+                        required
+                        :rules="$rules().required.build([rules.email])"
+                        prepend-icon="mdi-email-outline"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.user.confirmEmail"
-                      :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
-                      required
-                      :rules="rules.confirmEmail"
-                      prepend-icon="mdi-email-outline"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.confirmEmail"
+                        :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
+                        required
+                        :rules="$rules().required.build([rules.confirmEmail])"
+                        prepend-icon="mdi-email-outline"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
                 <template v-else>
@@ -111,55 +120,66 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <div class="mb-4">
                 <v-row>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.person.firstName"
-                      :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
-                      required
-                      :rules="rules.name"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.firstName"
+                        :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.person.lastName"
-                      :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
-                      required
-                      :rules="rules.name"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.lastName"
+                        :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
                 <v-row>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.user.username"
-                      :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
-                      required
-                      :rules="rules.name"
-                      prepend-icon="mdi-account-outline"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.username"
+                        :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
+                        required
+                        :rules="$rules().required.build()"
+                        prepend-icon="mdi-account-outline"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.user.password"
-                      :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
-                      required
-                      type="password"
-                      prepend-icon="mdi-form-textbox-password"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.password"
+                        :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
+                        required
+                        :rules="$rules().required.build()"
+                        type="password"
+                        prepend-icon="mdi-form-textbox-password"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.user.confirmPassword"
-                      :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
-                      required
-                      type="password"
-                      :rules="rules.confirmPassword"
-                      prepend-icon="mdi-form-textbox-password"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.confirmPassword"
+                        :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
+                        required
+                        :rules="$rules().required.build([rules.confirmPassword])"
+                        type="password"
+                        prepend-icon="mdi-form-textbox-password"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
               </div>
@@ -175,97 +195,127 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <div class="mb-4">
                 <v-row>
                   <v-col v-if="isFieldVisible('date_of_birth')">
-                    <date-field
-                      outlined
-                      v-model="data.person.dateOfBirth"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
-                      required
-                      prepend-icon="mdi-cake-variant-outline"
-                    />
+                    <div aria-required="true">
+                      <date-field
+                        outlined
+                        v-model="data.person.dateOfBirth"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
+                        required
+                        :rules="$rules().required.build()"
+                        prepend-icon="mdi-cake-variant-outline"
+                      />
+                    </div>
                   </v-col>
                   <v-col v-if="isFieldVisible('sex')">
-                    <sex-select
-                      outlined
-                      v-model="data.person.sex"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
-                      persistent-hint
-                      required
-                    />
+                    <div aria-required="true">
+                      <sex-select
+                        outlined
+                        v-model="data.person.sex"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
+                        persistent-hint
+                        required
+                        :rules="$rules().required.build()"
+                      />
+                    </div>
                   </v-col>
                   <v-col v-if="isFieldVisible('mobile_number')">
-                    <v-text-field
-                      outlined
-                      v-model="data.person.mobileNumber"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
-                      required
-                      prepend-icon="mdi-phone-outline"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.mobileNumber"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
+                        required
+                        :rules="$rules().required.build()"
+                        prepend-icon="mdi-phone-outline"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
                 <v-row>
                   <v-col v-if="isFieldVisible('street')">
-                    <v-text-field
-                      outlined
-                      v-model="data.person.address.street"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.street"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col v-if="isFieldVisible('housenumber')">
-                    <v-text-field
-                      outlined
-                      v-model="data.person.address.housenumber"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.housenumber"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
                 <v-row>
                   <v-col v-if="isFieldVisible('postal_code')">
-                    <v-text-field
-                      outlined
-                      v-model="data.person.address.postalCode"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.postalCode"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col v-if="isFieldVisible('place')">
-                    <v-text-field
-                      outlined
-                      v-model="data.person.address.place"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.place"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
                 <v-row v-if="isFieldVisible('school_details')">
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.school"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.school"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.schoolPlace"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.schoolPlace"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.schoolClass"
-                      :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
-                      required
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.schoolClass"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                 </v-row>
               </div>
@@ -286,41 +336,53 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   <v-card-text>
                     <v-row>
                       <v-col>
-                        <v-text-field
-                          outlined
-                          v-model="guardian.firstName"
-                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
-                          required
-                        ></v-text-field>
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
+                          <v-text-field
+                            outlined
+                            v-model="guardian.firstName"
+                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
+                            required
+                            :rules="index === 0 ? $rules().required.build() : []"
+                          ></v-text-field>
+                        </div>
                       </v-col>
                       <v-col>
-                        <v-text-field
-                          outlined
-                          v-model="guardian.lastName"
-                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
-                          required
-                        ></v-text-field>
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
+                          <v-text-field
+                            outlined
+                            v-model="guardian.lastName"
+                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
+                            required
+                            :rules="index === 0 ? $rules().required.build() : []"
+                          ></v-text-field>
+                        </div>
                       </v-col>
                     </v-row>
                     <v-row>
                       <v-col>
-                        <v-text-field
-                          outlined
-                          v-model="guardian.email"
-                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
-                          required
-                        ></v-text-field>
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
+                          <v-text-field
+                            outlined
+                            v-model="guardian.email"
+                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
+                            required
+                            :rules="index === 0 ? $rules().required.build() : []"
+                          ></v-text-field>
+                        </div>
                       </v-col>
                       <v-col>
-                        <v-text-field
-                          outlined
-                          v-model="guardian.mobileNumber"
-                          :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
-                          required
-                        ></v-text-field>
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
+                          <v-text-field
+                            outlined
+                            v-model="guardian.mobileNumber"
+                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
+                            required
+                            :rules="index === 0 ? $rules().required.build() : []"
+                          ></v-text-field>
+                        </div>
                       </v-col>
                     </v-row>
                   </v-card-text>
@@ -353,21 +415,23 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       v-model="data.medicalInformation"
                       :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
                       :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
-                      required
                     ></v-text-field>
                   </v-col>
                 </v-row>
                 <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
                   <v-col>
-                    <component
-                      :is="fieldComponentForAdditionalField(additionalField)"
-                      v-model="data.additionalFields[additionalField.id]"
-                      outlined
-                      :label="additionalField.title"
-                      :hint="additionalField.helpText"
-                      persistent-hint
-                      :required="additionalField.required"
-                    />
+                    <div :aria-required="additionalField.required ? 'true' : 'false'">
+                      <component
+                        :is="fieldComponentForAdditionalField(additionalField)"
+                        v-model="data.additionalFields[additionalField.id]"
+                        outlined
+                        :label="additionalField.title"
+                        :hint="additionalField.helpText"
+                        persistent-hint
+                        :required="additionalField.required"
+                        :rules="additionalField.required ? $rules().required.build() : []"
+                      />
+                    </div>
                   </v-col>
                 </v-row>
               </div>
@@ -404,28 +468,33 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                 </v-simple-table>
                 <v-row>
                   <v-col>
-                    <v-select
-                      :items="paweljongPaymentChoices"
-                      item-text="text"
-                      item-value="variant"
-                      outlined
-                      v-model="data.payment.paymentMethod"
-                      :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
-                      persistent-hint
-                      required
-                    ></v-select>
+                    <div aria-required="true">
+                      <v-select
+                        :items="paweljongPaymentChoices"
+                        item-text="text"
+                        item-value="variant"
+                        outlined
+                        v-model="data.payment.paymentMethod"
+                        :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
+                        persistent-hint
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-select>
+                    </div>
                   </v-col>
                 </v-row>
                 <v-row>
                   <v-col>
-                    <positive-small-integer-field
-                      outlined
-                      v-model="data.payment.amount"
-                      :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
-                      :rules="rules.amount"
-                      required
-                    />
+                    <div aria-required="true">
+                      <positive-small-integer-field
+                        outlined
+                        v-model="data.payment.amount"
+                        :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
+                        :rules="$rules().required.build([rules.amount])"
+                        required
+                      />
+                    </div>
                   </v-col>
                   <v-col>
                     <v-text-field
@@ -433,7 +502,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       v-model="data.payment.voucherCode"
                       :label="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.label')"
                       :hint="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.help_text')"
-                      required
                     ></v-text-field>
                   </v-col>
                 </v-row>
@@ -454,19 +522,23 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-card-title>
                   <v-card-text v-html="term.term" />
                 </v-card>
-                <v-checkbox
-                  v-for="term in event.terms"
-                  :key="`term-checkbox-${term.id}`"
-                  required
-                  :label="term.confirmationText"
-                  v-model="data.terms[term.id]"
-                />
-                <v-checkbox
-                  v-if="event.dateRetraction"
-                  required
-                  :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
-                  v-model="data.retractionConsent"
-                />
+                <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
+                  <v-checkbox
+                    required
+                    :rules="$rules().required.build()"
+                    :label="term.confirmationText"
+                    v-model="data.terms[term.id]"
+                  />
+                </div>
+                <div aria-required="true">
+                  <v-checkbox
+                    v-if="event.dateRetraction"
+                    required
+                    :rules="$rules().required.build()"
+                    :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
+                    v-model="data.retractionConsent"
+                  />
+                </div>
               </div>
               <v-divider class="mb-4" />
               <control-row
@@ -524,6 +596,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   } from "aleksis.apps.postbuero/components/mail_addresses/mailAddresses.graphql";
 
   import eventAdditionalFieldMixin from "../event_additional_field/eventAdditionalFieldMixin";
+  import formRulesMixin from "aleksis.core/mixins/formRulesMixin";
 
   export default {
     name: "EventRegistrationForm",
@@ -540,7 +613,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       mailDomains: mailDomainsForUser,
       disallowedLocalParts: disallowedLocalParts,
     },
-    mixins: [eventAdditionalFieldMixin],
+    mixins: [eventAdditionalFieldMixin, formRulesMixin],
     watch: {
     },
     methods: {
@@ -593,7 +666,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             (v) => this.data.user.email == v || this.$t("paweljong.event_registration.form.rules.confirm_email.no_match"),
           ],
           emailLocalPart: [
-            (v) => !!v || this.$t("forms.errors.required"),
             (v) =>
               /^\w+([.!#$%&'*+-\/=?^_`{|}~]?\w+)*$/.test(v) ||
               this.$t(
-- 
GitLab


From 1fd9f0b824281d3e25cd73ade365268069051317 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 13:44:20 +0100
Subject: [PATCH 12/72] Add form validation

---
 .../EventRegistrationForm.vue                 | 789 +++++++++---------
 1 file changed, 406 insertions(+), 383 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 7db4e2e..4d687ba 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -36,62 +36,64 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
               <div class="mb-4">
-                <v-row v-if="emailMode == 'postbuero'">
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.email.localPart"
-                        :label="$t('postbuero.mail_addresses.data_table.local_part')"
-                        :rules="$rules().required.build([rules.emailLocalPart])"
-                        required
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-autocomplete
-                        outlined
-                        hide-no-data
-                        :items="mailDomains"
-                        item-text="domain"
-                        item-value="id"
-                        :loading="$apollo.queries.mailDomains.loading"
-                        prepend-icon="mdi-at"
-                        v-model="data.email.domain"
-                        :label="$t('postbuero.mail_addresses.data_table.domain')"
-                        required
-                        :rules="$rules().required.build()"
-                      />
-                    </div>
-                  </v-col>
-                </v-row>
-                <v-row v-else-if="emailMode == 'own'">
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.user.email"
-                        :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
-                        required
-                        :rules="$rules().required.build([rules.email])"
-                        prepend-icon="mdi-email-outline"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.user.confirmEmail"
-                        :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
-                        required
-                        :rules="$rules().required.build([rules.confirmEmail])"
-                        prepend-icon="mdi-email-outline"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
+                <v-form v-if="emailMode" v-model="valid">
+                  <v-row v-if="emailMode == 'postbuero'">
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.email.localPart"
+                          :label="$t('postbuero.mail_addresses.data_table.local_part')"
+                          :rules="$rules().required.build([rules.emailLocalPart])"
+                          required
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-autocomplete
+                          outlined
+                          hide-no-data
+                          :items="mailDomains"
+                          item-text="domain"
+                          item-value="id"
+                          :loading="$apollo.queries.mailDomains.loading"
+                          prepend-icon="mdi-at"
+                          v-model="data.email.domain"
+                          :label="$t('postbuero.mail_addresses.data_table.domain')"
+                          required
+                          :rules="$rules().required.build()"
+                        />
+                      </div>
+                    </v-col>
+                  </v-row>
+                  <v-row v-else-if="emailMode == 'own'">
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.user.email"
+                          :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
+                          required
+                          :rules="$rules().required.build([rules.email])"
+                          prepend-icon="mdi-email-outline"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.user.confirmEmail"
+                          :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
+                          required
+                          :rules="$rules().required.build([rules.confirmEmail])"
+                          prepend-icon="mdi-email-outline"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                </v-form>
                 <template v-else>
                   <v-card-text>
                     {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
@@ -112,281 +114,290 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
 
             <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
               <div class="mb-4">
-                <v-row>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.firstName"
-                        :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.lastName"
-                        :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
-                <v-row>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.user.username"
-                        :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
-                        required
-                        :rules="$rules().required.build()"
-                        prepend-icon="mdi-account-outline"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.user.password"
-                        :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
-                        required
-                        :rules="$rules().required.build()"
-                        type="password"
-                        prepend-icon="mdi-form-textbox-password"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.user.confirmPassword"
-                        :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
-                        required
-                        :rules="$rules().required.build([rules.confirmPassword])"
-                        type="password"
-                        prepend-icon="mdi-form-textbox-password"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
+                <v-form v-model="valid">
+                  <v-row>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.firstName"
+                          :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.lastName"
+                          :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                  <v-row>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.user.username"
+                          :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
+                          required
+                          :rules="$rules().required.build()"
+                          prepend-icon="mdi-account-outline"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.user.password"
+                          :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
+                          required
+                          :rules="$rules().required.build()"
+                          type="password"
+                          prepend-icon="mdi-form-textbox-password"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.user.confirmPassword"
+                          :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
+                          required
+                          :rules="$rules().required.build([rules.confirmPassword])"
+                          type="password"
+                          prepend-icon="mdi-form-textbox-password"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                </v-form>
               </div>
               <v-divider class="mb-4" />
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
 
             <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
               <div class="mb-4">
-                <v-row>
-                  <v-col v-if="isFieldVisible('date_of_birth')">
-                    <div aria-required="true">
-                      <date-field
-                        outlined
-                        v-model="data.person.dateOfBirth"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
-                        required
-                        :rules="$rules().required.build()"
-                        prepend-icon="mdi-cake-variant-outline"
-                      />
-                    </div>
-                  </v-col>
-                  <v-col v-if="isFieldVisible('sex')">
-                    <div aria-required="true">
-                      <sex-select
-                        outlined
-                        v-model="data.person.sex"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
-                        persistent-hint
-                        required
-                        :rules="$rules().required.build()"
-                      />
-                    </div>
-                  </v-col>
-                  <v-col v-if="isFieldVisible('mobile_number')">
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.mobileNumber"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
-                        required
-                        :rules="$rules().required.build()"
-                        prepend-icon="mdi-phone-outline"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
-                <v-row>
-                  <v-col v-if="isFieldVisible('street')">
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.address.street"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col v-if="isFieldVisible('housenumber')">
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.address.housenumber"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
-                <v-row>
-                  <v-col v-if="isFieldVisible('postal_code')">
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.address.postalCode"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col v-if="isFieldVisible('place')">
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.address.place"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
-                <v-row v-if="isFieldVisible('school_details')">
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.school"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.schoolPlace"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-text-field
-                        outlined
-                        v-model="data.schoolClass"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-text-field>
-                    </div>
-                  </v-col>
-                </v-row>
+                <v-form v-model="valid">
+                  <v-row>
+                    <v-col v-if="isFieldVisible('date_of_birth')">
+                      <div aria-required="true">
+                        <date-field
+                          outlined
+                          v-model="data.person.dateOfBirth"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
+                          required
+                          :rules="$rules().required.build()"
+                          prepend-icon="mdi-cake-variant-outline"
+                        />
+                      </div>
+                    </v-col>
+                    <v-col v-if="isFieldVisible('sex')">
+                      <div aria-required="true">
+                        <sex-select
+                          outlined
+                          v-model="data.person.sex"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
+                          persistent-hint
+                          required
+                          :rules="$rules().required.build()"
+                        />
+                      </div>
+                    </v-col>
+                    <v-col v-if="isFieldVisible('mobile_number')">
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.mobileNumber"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
+                          required
+                          :rules="$rules().required.build()"
+                          prepend-icon="mdi-phone-outline"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                  <v-row>
+                    <v-col v-if="isFieldVisible('street')">
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.address.street"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col v-if="isFieldVisible('housenumber')">
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.address.housenumber"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                  <v-row>
+                    <v-col v-if="isFieldVisible('postal_code')">
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.address.postalCode"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col v-if="isFieldVisible('place')">
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.address.place"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                  <v-row v-if="isFieldVisible('school_details')">
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.school"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.schoolPlace"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-text-field
+                          outlined
+                          v-model="data.schoolClass"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-text-field>
+                      </div>
+                    </v-col>
+                  </v-row>
+                </v-form>
               </div>
               <v-divider class="mb-4" />
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
 
             <v-stepper-content :step="getStepIndex('guardians')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
               <div class="mb-4">
-                <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
-                  <v-card-title>
-                    {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
-                  </v-card-title>
-                  <v-card-text>
-                    <v-row>
-                      <v-col>
-                        <div :aria-required="index === 0 ? 'true' : 'false'">
-                          <v-text-field
-                            outlined
-                            v-model="guardian.firstName"
-                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
-                            required
-                            :rules="index === 0 ? $rules().required.build() : []"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                      <v-col>
-                        <div :aria-required="index === 0 ? 'true' : 'false'">
-                          <v-text-field
-                            outlined
-                            v-model="guardian.lastName"
-                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
-                            required
-                            :rules="index === 0 ? $rules().required.build() : []"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                    </v-row>
-                    <v-row>
-                      <v-col>
-                        <div :aria-required="index === 0 ? 'true' : 'false'">
-                          <v-text-field
-                            outlined
-                            v-model="guardian.email"
-                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
-                            required
-                            :rules="index === 0 ? $rules().required.build() : []"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                      <v-col>
-                        <div :aria-required="index === 0 ? 'true' : 'false'">
-                          <v-text-field
-                            outlined
-                            v-model="guardian.mobileNumber"
-                            :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
-                            required
-                            :rules="index === 0 ? $rules().required.build() : []"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                    </v-row>
-                  </v-card-text>
-                </v-card>
+                <v-form v-model="valid">
+                  <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
+                    <v-card-title>
+                      {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
+                    </v-card-title>
+                    <v-card-text>
+                      <v-row>
+                        <v-col>
+                          <div :aria-required="index === 0 ? 'true' : 'false'">
+                            <v-text-field
+                              outlined
+                              v-model="guardian.firstName"
+                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
+                              :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
+                              required
+                              :rules="index === 0 ? $rules().required.build() : []"
+                            ></v-text-field>
+                          </div>
+                        </v-col>
+                        <v-col>
+                          <div :aria-required="index === 0 ? 'true' : 'false'">
+                            <v-text-field
+                              outlined
+                              v-model="guardian.lastName"
+                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
+                              :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
+                              required
+                              :rules="index === 0 ? $rules().required.build() : []"
+                            ></v-text-field>
+                          </div>
+                        </v-col>
+                      </v-row>
+                      <v-row>
+                        <v-col>
+                          <div :aria-required="index === 0 ? 'true' : 'false'">
+                            <v-text-field
+                              outlined
+                              v-model="guardian.email"
+                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
+                              required
+                              :rules="index === 0 ? $rules().required.build() : []"
+                            ></v-text-field>
+                          </div>
+                        </v-col>
+                        <v-col>
+                          <div :aria-required="index === 0 ? 'true' : 'false'">
+                            <v-text-field
+                              outlined
+                              v-model="guardian.mobileNumber"
+                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
+                              :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
+                              required
+                              :rules="index === 0 ? $rules().required.build() : []"
+                            ></v-text-field>
+                          </div>
+                        </v-col>
+                      </v-row>
+                    </v-card-text>
+                  </v-card>
+                </v-form>
                 <v-row class="mb-4">
                   <v-col>
                     <v-spacer />
@@ -402,43 +413,47 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
 
             <v-stepper-content :step="getStepIndex('additional')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
               <div class="mb-4">
-                <v-row>
-                  <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.medicalInformation"
-                      :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
-                    ></v-text-field>
-                  </v-col>
-                </v-row>
-                <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
-                  <v-col>
-                    <div :aria-required="additionalField.required ? 'true' : 'false'">
-                      <component
-                        :is="fieldComponentForAdditionalField(additionalField)"
-                        v-model="data.additionalFields[additionalField.id]"
+                <v-form v-model="valid">
+                  <v-row>
+                    <v-col>
+                      <v-text-field
                         outlined
-                        :label="additionalField.title"
-                        :hint="additionalField.helpText"
-                        persistent-hint
-                        :required="additionalField.required"
-                        :rules="additionalField.required ? $rules().required.build() : []"
-                      />
-                    </div>
-                  </v-col>
-                </v-row>
+                        v-model="data.medicalInformation"
+                        :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
+                      ></v-text-field>
+                    </v-col>
+                  </v-row>
+                  <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
+                    <v-col>
+                      <div :aria-required="additionalField.required ? 'true' : 'false'">
+                        <component
+                          :is="fieldComponentForAdditionalField(additionalField)"
+                          v-model="data.additionalFields[additionalField.id]"
+                          outlined
+                          :label="additionalField.title"
+                          :hint="additionalField.helpText"
+                          persistent-hint
+                          :required="additionalField.required"
+                          :rules="additionalField.required ? $rules().required.build() : []"
+                        />
+                      </div>
+                    </v-col>
+                  </v-row>
+                </v-form>
               </div>
               <v-divider class="mb-4" />
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
 
@@ -466,50 +481,53 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     </tbody>
                   </template>
                 </v-simple-table>
-                <v-row>
-                  <v-col>
-                    <div aria-required="true">
-                      <v-select
-                        :items="paweljongPaymentChoices"
-                        item-text="text"
-                        item-value="variant"
-                        outlined
-                        v-model="data.payment.paymentMethod"
-                        :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
-                        persistent-hint
-                        required
-                        :rules="$rules().required.build()"
-                      ></v-select>
-                    </div>
-                  </v-col>
-                </v-row>
-                <v-row>
-                  <v-col>
-                    <div aria-required="true">
-                      <positive-small-integer-field
+                <v-form v-model="valid">
+                  <v-row>
+                    <v-col>
+                      <div aria-required="true">
+                        <v-select
+                          :items="paweljongPaymentChoices"
+                          item-text="text"
+                          item-value="variant"
+                          outlined
+                          v-model="data.payment.paymentMethod"
+                          :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
+                          persistent-hint
+                          required
+                          :rules="$rules().required.build()"
+                        ></v-select>
+                      </div>
+                    </v-col>
+                  </v-row>
+                  <v-row>
+                    <v-col>
+                      <div aria-required="true">
+                        <positive-small-integer-field
+                          outlined
+                          v-model="data.payment.amount"
+                          :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
+                          :rules="$rules().required.build([rules.amount])"
+                          required
+                        />
+                      </div>
+                    </v-col>
+                    <v-col>
+                      <v-text-field
                         outlined
-                        v-model="data.payment.amount"
-                        :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
-                        :rules="$rules().required.build([rules.amount])"
-                        required
-                      />
-                    </div>
-                  </v-col>
-                  <v-col>
-                    <v-text-field
-                      outlined
-                      v-model="data.payment.voucherCode"
-                      :label="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.label')"
-                      :hint="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.help_text')"
-                    ></v-text-field>
-                  </v-col>
-                </v-row>
+                        v-model="data.payment.voucherCode"
+                        :label="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.label')"
+                        :hint="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.help_text')"
+                      ></v-text-field>
+                    </v-col>
+                  </v-row>
+                </v-form>
               </div>
               <v-divider class="mb-4" />
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
 
@@ -522,28 +540,31 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-card-title>
                   <v-card-text v-html="term.term" />
                 </v-card>
-                <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
-                  <v-checkbox
-                    required
-                    :rules="$rules().required.build()"
-                    :label="term.confirmationText"
-                    v-model="data.terms[term.id]"
-                  />
-                </div>
-                <div aria-required="true">
-                  <v-checkbox
-                    v-if="event.dateRetraction"
-                    required
-                    :rules="$rules().required.build()"
-                    :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
-                    v-model="data.retractionConsent"
-                  />
-                </div>
+                <v-form v-model="valid">
+                  <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
+                    <v-checkbox
+                      required
+                      :rules="$rules().required.build()"
+                      :label="term.confirmationText"
+                      v-model="data.terms[term.id]"
+                    />
+                  </div>
+                  <div aria-required="true">
+                    <v-checkbox
+                      v-if="event.dateRetraction"
+                      required
+                      :rules="$rules().required.build()"
+                      :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
+                      v-model="data.retractionConsent"
+                    />
+                  </div>
+                </v-form>
               </div>
               <v-divider class="mb-4" />
               <control-row
                 :step="step"
                 @set-step="setStep"
+                :next-disabled="!valid"
               />
             </v-stepper-content>
   
@@ -619,6 +640,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
     methods: {
       setStep(step) {
         this.step = step;
+        this.valid = false;
       },
       eventRegistrationDone({ data }) {
         if (data.sendEventRegistration.ok) {
@@ -751,6 +773,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
     },
     data() {
       return {
+        valid: false,
         eventRegistrationSent: false,
         step: 1,
         emailMode: null,
-- 
GitLab


From c76ea69a92b58d0281af88501a8f6f8d780c73e0 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 13:53:29 +0100
Subject: [PATCH 13/72] Add SendEventRegistrationMutation

---
 .../eventRegistrationMutation.graphql         |   2 +-
 aleksis/apps/paweljong/schema/__init__.py     |  11 +-
 .../paweljong/schema/event_registration.py    | 186 ++++++++++++++++--
 3 files changed, 181 insertions(+), 18 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
index 35d25ba..ef1b863 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
@@ -1,4 +1,4 @@
-mutation sendOrder(
+mutation sendEventRegistration(
   $event: ID!
   $eventRegistration: EventRegistrationInputType!
 ) {
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
index ec9ca2a..75a300f 100644
--- a/aleksis/apps/paweljong/schema/__init__.py
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -4,22 +4,23 @@ import graphene
 from graphene_django import DjangoObjectType
 from graphql import GraphQLError
 
-from aleksis.core.schema.base import FilterOrderList
-from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.apps.tezor.models.base import Client
 from aleksis.apps.tezor.models.invoice import InvoiceGroup
 from aleksis.apps.tezor.schema.client import PaymentVariantChoiceType
+from aleksis.core.schema.base import FilterOrderList
+from aleksis.core.util.core_helpers import get_site_preferences
 
+from ..models import Event
 from .checkpoint import CheckpointCheckInMutation
 from .event import EventType
 from .event_additional_field import (
     EventAdditionalFieldBatchCreateMutation,
     EventAdditionalFieldBatchDeleteMutation,
     EventAdditionalFieldBatchPatchMutation,
-    EventAdditionalFieldType
+    EventAdditionalFieldType,
 )
+from .event_registration import SendEventRegistrationMutation
 from .terms import TermsType
-from ..models import Event
 
 
 class Query(graphene.ObjectType):
@@ -66,3 +67,5 @@ class Mutation(graphene.ObjectType):
     create_event_additional_fields = EventAdditionalFieldBatchCreateMutation.Field()
     delete_event_additional_fields = EventAdditionalFieldBatchDeleteMutation.Field()
     update_event_additional_fields = EventAdditionalFieldBatchPatchMutation.Field()
+
+    send_event_registration = SendEventRegistrationMutation.Field()
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index a63bdf9..66ece1a 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -1,9 +1,20 @@
+from django.contrib.auth.models import User
+from django.utils.text import slugify
+from django.utils.translation import gettext as _
+
 import graphene
 from graphene_django import DjangoObjectType
+from templated_email import send_templated_mail
 
+from aleksis.apps.postbuero.models import MailAddress
+from aleksis.apps.postbuero.schema import MailAddressInputType
+from aleksis.core.models import Activity, Person
 from aleksis.core.schema.base import PermissionsTypeMixin
+from aleksis.core.schema.person import PersonInputType
+from aleksis.core.schema.user import UserInputType
+from aleksis.core.util.core_helpers import get_site_preferences
 
-from ..models import EventRegistration
+from ..models import Event, EventRegistration, Terms, Voucher
 
 
 class EventRegistrationType(PermissionsTypeMixin, DjangoObjectType):
@@ -11,16 +22,165 @@ class EventRegistrationType(PermissionsTypeMixin, DjangoObjectType):
         model = EventRegistration
 
 
+class PaymentInputType(graphene.InputObjectType):
+    payment_method = graphene.String(required=True)
+    voucher_code = graphene.String(required=False)
+    amount = graphene.Int(required=True)
+
+
 class EventRegistrationInputType(graphene.InputObjectType):
-    full_name = graphene.String(required=True)
-    email = graphene.String(required=True)
-    notes = graphene.String(required=False)
-    shipping_option = graphene.ID(required=True)
-    payment_option = graphene.ID(required=True)
-    items = graphene.List(OrderItemInputType, required=True)
-    shipping_full_name = graphene.String(required=False)
-    second_address_row = graphene.String(required=False)
-    street = graphene.String(required=False)
-    housenumber = graphene.String(required=False)
-    plz = graphene.String(required=False)
-    place = graphene.String(required=False)
+    email = graphene.Field(MailAddressInputType, required=False)
+    person = graphene.Field(PersonInputType, required=False)
+    user = graphene.Field(UserInputType, required=False)
+    school = graphene.String(required=False)
+    school_place = graphene.String(required=False)
+    school_class = graphene.String(required=False)
+    payment = graphene.Field(PaymentInputType, required=False)
+    medical_information = graphene.String(required=False)
+    comment = graphene.String(required=False)
+    additional_fields = graphene.JSONString(required=False)
+    terms = graphene.JSONString(required=False)
+    retraction_consent = graphene.Boolean(required=True)
+
+
+class SendEventRegistrationMutation(graphene.Mutation):
+    class Arguments:
+        event = graphene.ID(required=True)
+        event_registration = EventRegistrationInputType(required=True)
+
+    ok = graphene.Boolean()
+
+    def mutate(self, info, event: graphene.ID, event_registration: EventRegistrationInputType, **kwargs):
+        event = Event.objects.get(pk=event)
+
+        print(event_registration)
+
+        # Create user
+        if event_registration is not None:
+            user = User.objects.create(
+                username=event_registration["user"]["username"],
+                email=event_registration["user"]["email"],
+            )
+            user.set_password(event_registration["user"]["password"])
+            user.save()
+        else:
+            user = self.request.user
+
+        person, created = Person.objects.get_or_create(
+            user=user,
+            defaults={
+                "email": event_registration["person"]["email"],
+                "first_name": event_registration["person"]["first_name"],
+                "last_name": event_registration["person"]["last_name"],
+            },
+        )
+
+        # Store contact information in database
+        for field_name in ["date_of_birth", "sex", "address", "mobile_number"]:
+            if event_registration["person"] is not None and event_registration["person"][field_name] != "":
+                if field_name == "address":
+                    for addr_field in ["street", "housenumber", "postal_code", "place"]:
+                        setattr(person, addr_field, event_registration["person"]["address"][addr_field])
+                else:
+                    setattr(person, field_name, event_registration["person"][field_name])
+        person.save()
+
+        if event_registration["person"] is not None:
+            for guardian_data in event_registration["person"]["guardians"]:
+                guardian, created = Person.objects.get_or_create(
+                    defaults={
+                        "mobile_number": guardian_data["mobile_number"],
+                    },
+                    first_name=guardian_data["first_name"],
+                    last_name=guardian_data["last_name"],
+                    email=guardian_data["email"],
+                )
+
+                person.guardians.add(guardian)
+            person.save()
+
+        if event_registration["email"] is not None:
+            _mail_address = MailAddress.objects.create(
+                local_part=event_registration["email"]["local_part"],
+                domain=event_registration["email"]["domain"],
+            )
+            _mail_address.person = person
+            _mail_address.save()
+
+        school_details = {}
+        for field_name in ["school", "school_class", "school_place"]:
+            if event_registration[field_name] is not None:
+                school_details[field_name] = event_registration[field_name]
+
+        registration = EventRegistration.objects.create(
+            managed_by_app_label="",
+            event=event,
+            person=person,
+            medical_information=event_registration["medical_information"],
+            **school_details,
+        )
+        for field in event.additional_fields.all():
+            registration.extended_data[
+                slugify(field.title).replace("-", "_")
+            ] = event_registration["additional_fields"][field.title]
+
+        for field in event_registration["terms"]:
+            if not field.startswith("consent_"):
+                continue
+            pk = int(field.split("_")[1])
+            term = Terms.objects.get(id=pk)
+            registration.accepted_terms.add(term)
+
+        registration.cost = event.cost
+
+        if event.max_cost is not None and event.max_cost > 0:
+            amount = event_registration["payment"].amount
+
+            if amount < event.cost:
+                voucher_amount = event.cost - amount
+                discount = voucher_amount / event.cost
+                Voucher.objects.create(
+                    person=person,
+                    event=event,
+                    used=True,
+                    discount=discount,
+                )
+            elif amount > event.cost:
+                registration.donation = amount - event.cost
+
+            # TODO Implement existing voucher handling
+
+            invoice = registration.get_invoice()
+            invoice.variant = event_registration["payment"]["payment_method"]
+            invoice.save()
+
+        registration.save()
+
+        context = {}
+        context["registration"] = registration
+
+#        send_templated_mail(
+#            template_name="event_registered",
+#            from_email=get_site_preferences()["mail__address"],
+#            recipient_list=[get_site_preferences()["paweljong__event_notification_recipient"]],
+#            headers={
+#                "reply_to": [
+#                    person.email,
+#                    person.guardians.first().email,
+#                ],
+#                "X-Zammad-Customer-Email": person.email,
+#            },
+#            context=context,
+#        )
+
+        _act = Activity(
+            title=_("You registered for an event"),
+            description=_("You registered for the event %s" % event.display_name),
+            app="Paweljong",
+            user=person,
+        )
+
+        if event_registration["retraction_consent"]:
+            return SendEventRegistrationMutation(ok=True)
+
+        return SendEventRegistrationMutation(ok=False)
-- 
GitLab


From f5be7af9f9c813c83cbb9ae689463e295ee4ac8d Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 13:56:56 +0100
Subject: [PATCH 14/72] Fix validation mechanism

---
 .../EventRegistrationForm.vue                 | 39 ++++++++++++-------
 1 file changed, 24 insertions(+), 15 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 4d687ba..a7b0667 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -36,7 +36,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
               <div class="mb-4">
-                <v-form v-if="emailMode" v-model="valid">
+                <v-form v-if="emailMode" @input="setValidationStatus('email', $event)">
                   <v-row v-if="emailMode == 'postbuero'">
                     <v-col>
                       <div aria-required="true">
@@ -114,14 +114,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('email')"
               />
             </v-stepper-content>
 
             <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
               <div class="mb-4">
-                <v-form v-model="valid">
+                <v-form @input="setValidationStatus('register', $event)">
                   <v-row>
                     <v-col>
                       <div aria-required="true">
@@ -192,14 +192,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('register')"
               />
             </v-stepper-content>
 
             <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
               <div class="mb-4">
-                <v-form v-model="valid">
+                <v-form  @input="setValidationStatus('contact_details', $event)">
                   <v-row>
                     <v-col v-if="isFieldVisible('date_of_birth')">
                       <div aria-required="true">
@@ -331,14 +331,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('contact_details')"
               />
             </v-stepper-content>
 
             <v-stepper-content :step="getStepIndex('guardians')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
               <div class="mb-4">
-                <v-form v-model="valid">
+                <v-form  @input="setValidationStatus('guardians', $event)">
                   <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
                     <v-card-title>
                       {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
@@ -413,14 +413,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('guardians')"
               />
             </v-stepper-content>
 
             <v-stepper-content :step="getStepIndex('additional')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
               <div class="mb-4">
-                <v-form v-model="valid">
+                <v-form  @input="setValidationStatus('additional', $event)">
                   <v-row>
                     <v-col>
                       <v-text-field
@@ -453,7 +453,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('additional')"
               />
             </v-stepper-content>
 
@@ -481,7 +481,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     </tbody>
                   </template>
                 </v-simple-table>
-                <v-form v-model="valid">
+                <v-form  @input="setValidationStatus('financial', $event)">
                   <v-row>
                     <v-col>
                       <div aria-required="true">
@@ -527,7 +527,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('financial')"
               />
             </v-stepper-content>
 
@@ -540,7 +540,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-card-title>
                   <v-card-text v-html="term.term" />
                 </v-card>
-                <v-form v-model="valid">
+                <v-form  @input="setValidationStatus('consent', $event)">
                   <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
                     <v-checkbox
                       required
@@ -564,7 +564,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!valid"
+                :next-disabled="!getValidationStatus('consent')"
               />
             </v-stepper-content>
   
@@ -667,6 +667,15 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       getStepTitleKey(stepName) {
         return this.steps.find((s) => s.name === stepName)?.titleKey;
       },
+      setValidationStatus(stepName, validationStatus) {
+        this.validationStatuses[stepName] = validationStatus;
+      },
+      getValidationStatus(stepName) {
+        if (this.validationStatuses[stepName]) {
+          return this.validationStatuses[stepName];
+        }
+        return false;
+      },
     },
     props: {
       slug: {
@@ -773,7 +782,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
     },
     data() {
       return {
-        valid: false,
+        validationStatuses: {},
         eventRegistrationSent: false,
         step: 1,
         emailMode: null,
-- 
GitLab


From 155f1bbb0193e2c83258f87d9e1f56ce982c182f Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 14:23:08 +0100
Subject: [PATCH 15/72] Refactor data for sending

---
 .../event_registration/EventRegistrationForm.vue       | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index a7b0667..a6d3660 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -736,12 +736,18 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       dataForSubmit() {
         const { confirmEmail, confirmPassword, ...filteredUserData } = this.data.user;
 
-        return {
+        const data = {
           ...this.data,
           additionalFields: JSON.stringify(this.data.additionalFields),
           terms: JSON.stringify(this.data.terms),
           user: filteredUserData,
         };
+
+        if (this.event.cost == 0 && this.event.maxCost == 0) {
+          const { payment, ...filteredData } = data;
+          return filteredData;
+        }
+        return data;
       },
       steps() {
         return [
@@ -831,7 +837,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           comment: "",
           additionalFields: {},
           terms: {},
-          retractionConsent: null,
+          retractionConsent: false,
         },
       };
     },
-- 
GitLab


From 8e2f097a1754905ef890f061bd656a63229c1d2b Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 14:23:49 +0100
Subject: [PATCH 16/72] also query retraction date

---
 aleksis/apps/paweljong/frontend/components/event/events.graphql | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event/events.graphql b/aleksis/apps/paweljong/frontend/components/event/events.graphql
index 73d7458..7422e6d 100644
--- a/aleksis/apps/paweljong/frontend/components/event/events.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event/events.graphql
@@ -23,6 +23,7 @@ query eventBySlug($slug: String!) {
     cost
     minCost
     maxCost
+    dateRetraction
     terms {
       id
       title
-- 
GitLab


From 86539d9f9f04b6cf133b73e506eaf2c8fc6ae047 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 14:32:17 +0100
Subject: [PATCH 17/72] omit email if not entered

---
 .../event_registration/EventRegistrationForm.vue          | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index a6d3660..68eefd2 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -736,7 +736,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       dataForSubmit() {
         const { confirmEmail, confirmPassword, ...filteredUserData } = this.data.user;
 
-        const data = {
+        let data = {
           ...this.data,
           additionalFields: JSON.stringify(this.data.additionalFields),
           terms: JSON.stringify(this.data.terms),
@@ -745,7 +745,11 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
 
         if (this.event.cost == 0 && this.event.maxCost == 0) {
           const { payment, ...filteredData } = data;
-          return filteredData;
+          data = filteredData;
+        }
+        if (!this.data.email.localPart && !this.data.email.domain) {
+          const { email, ...filteredData } = data;
+          data = filteredData;
         }
         return data;
       },
-- 
GitLab


From ec0fe7ecbd1b4bb29634ae7abdc94ee46c2e89a1 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 14:34:21 +0100
Subject: [PATCH 18/72] Remove unneeded rules & fix validation (again)

---
 .../EventRegistrationForm.vue                 | 26 ++++---------------
 1 file changed, 5 insertions(+), 21 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 68eefd2..6970d01 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -44,7 +44,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           outlined
                           v-model="data.email.localPart"
                           :label="$t('postbuero.mail_addresses.data_table.local_part')"
-                          :rules="$rules().required.build([rules.emailLocalPart])"
+                          :rules="$rules().required.build(rules.emailLocalPart)"
                           required
                         ></v-text-field>
                       </div>
@@ -75,7 +75,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           v-model="data.user.email"
                           :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
                           required
-                          :rules="$rules().required.build([rules.email])"
+                          :rules="$rules().required.build(rules.email)"
                           prepend-icon="mdi-email-outline"
                         ></v-text-field>
                       </div>
@@ -87,7 +87,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           v-model="data.user.confirmEmail"
                           :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
                           required
-                          :rules="$rules().required.build([rules.confirmEmail])"
+                          :rules="$rules().required.build(rules.confirmEmail)"
                           prepend-icon="mdi-email-outline"
                         ></v-text-field>
                       </div>
@@ -179,7 +179,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           v-model="data.user.confirmPassword"
                           :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
                           required
-                          :rules="$rules().required.build([rules.confirmPassword])"
+                          :rules="$rules().required.build(rules.confirmPassword)"
                           type="password"
                           prepend-icon="mdi-form-textbox-password"
                         ></v-text-field>
@@ -507,7 +507,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           outlined
                           v-model="data.payment.amount"
                           :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
-                          :rules="$rules().required.build([rules.amount])"
+                          :rules="$rules().required.build(rules.amount)"
                           required
                         />
                       </div>
@@ -715,22 +715,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             (v) => v > this.event.minCost || this.$t("paweljong.event_registration.form.rules.amount.too_low"),
             (v) => v < this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
           ],
-          street: [
-            (v) => !!v || this.$t("order.rules.street.required"),
-            (v) => v.length <= 255 || this.$t("order.rules.street.max"),
-          ],
-          housenumber: [
-            (v) => !!v || this.$t("order.rules.housenumber.required"),
-            (v) => v.length <= 255 || this.$t("order.rules.housenumber.max"),
-          ],
-          postalCode: [
-            (v) => !!v || this.$t("order.rules.postal_code.required"),
-            (v) => /^\d{5}$/.test(v) || this.$t("order.rules.postal_code.valid"),
-          ],
-          place: [
-            (v) => !!v || this.$t("order.rules.place.required"),
-            (v) => v.length <= 255 || this.$t("order.rules.place.max"),
-          ],
         };
       },
       dataForSubmit() {
-- 
GitLab


From a1b24842e7959ad664840b49ed13da173a74f433 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 14:37:38 +0100
Subject: [PATCH 19/72] Add comment field

---
 .../event_registration/EventRegistrationForm.vue           | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 6970d01..aaf3fa3 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -570,6 +570,13 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   
             <v-stepper-content :step="getStepIndex('confirm')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('confirm')) }}</h2>
+              <div class="mb-4">
+                <v-text-field
+                  outlined
+                  v-model="data.comment"
+                  :label="$t('paweljong.event_registration.form.steps.confirm.fields.comment.label')"
+                ></v-text-field>
+              </div>
               <v-divider class="my-4" />
   
               <message-box type="info" class="mb-4">
-- 
GitLab


From e68ef488864980b3fd709ffe65b90127e5191f7b Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 14:39:48 +0100
Subject: [PATCH 20/72] Fix mail handling

---
 .../apps/paweljong/schema/event_registration.py   | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 66ece1a..041ea7e 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -55,6 +55,17 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         print(event_registration)
 
+        email = None
+
+        if event_registration["email"] is not None:
+            _mail_address = MailAddress.objects.create(
+                local_part=event_registration["email"]["local_part"],
+                domain=event_registration["email"]["domain"],
+            )
+            email = str(_mail_address)
+        elif event_registration["user"] is not None:
+            email = event_registration["user"]["email"]
+
         # Create user
         if event_registration is not None:
             user = User.objects.create(
@@ -69,7 +80,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
         person, created = Person.objects.get_or_create(
             user=user,
             defaults={
-                "email": event_registration["person"]["email"],
+                "email": email,
                 "first_name": event_registration["person"]["first_name"],
                 "last_name": event_registration["person"]["last_name"],
             },
@@ -134,7 +145,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
         registration.cost = event.cost
 
         if event.max_cost is not None and event.max_cost > 0:
-            amount = event_registration["payment"].amount
+            amount = event_registration["payment"]["amount"]
 
             if amount < event.cost:
                 voucher_amount = event.cost - amount
-- 
GitLab


From 073d0e2f77b861883102cbe1185d3828d239eab0 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 14:40:00 +0100
Subject: [PATCH 21/72] Use atomic transaction

---
 aleksis/apps/paweljong/schema/checkpoint.py         | 2 +-
 aleksis/apps/paweljong/schema/event_registration.py | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/schema/checkpoint.py b/aleksis/apps/paweljong/schema/checkpoint.py
index 5c1b03c..7b4c091 100644
--- a/aleksis/apps/paweljong/schema/checkpoint.py
+++ b/aleksis/apps/paweljong/schema/checkpoint.py
@@ -4,9 +4,9 @@ from django.utils import timezone
 import graphene
 from graphene_django import DjangoObjectType
 
+from aleksis.core.models import Person
 from aleksis.core.schema.base import PermissionsTypeMixin
 from aleksis.core.util.core_helpers import has_person
-from aleksis.core.models import Person
 
 from ..models import Checkpoint, Event
 
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 041ea7e..8de0440 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -1,4 +1,6 @@
+from django.db import transaction
 from django.contrib.auth.models import User
+from django.core.exceptions import ValidationError
 from django.utils.text import slugify
 from django.utils.translation import gettext as _
 
@@ -50,6 +52,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
     ok = graphene.Boolean()
 
+    @transaction.atomic
     def mutate(self, info, event: graphene.ID, event_registration: EventRegistrationInputType, **kwargs):
         event = Event.objects.get(pk=event)
 
@@ -194,4 +197,5 @@ class SendEventRegistrationMutation(graphene.Mutation):
         if event_registration["retraction_consent"]:
             return SendEventRegistrationMutation(ok=True)
 
+        raise ValidationError(_("Retraction consent is required"))
         return SendEventRegistrationMutation(ok=False)
-- 
GitLab


From e125ecd336e72738724c3f4d4680c9571e3cc544 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 14:50:12 +0100
Subject: [PATCH 22/72] Fix validation mechanism (again?)

---
 .../EventRegistrationForm.vue                 | 32 ++++++++++---------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index aaf3fa3..a09bd0b 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -36,7 +36,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
               <div class="mb-4">
-                <v-form v-if="emailMode" @input="setValidationStatus('email', $event)">
+                <v-form v-if="emailMode" v-model="validationStatuses['email']">
                   <v-row v-if="emailMode == 'postbuero'">
                     <v-col>
                       <div aria-required="true">
@@ -114,14 +114,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('email')"
+                :next-disabled="!validationStatuses['email']"
               />
             </v-stepper-content>
 
             <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
               <div class="mb-4">
-                <v-form @input="setValidationStatus('register', $event)">
+                <v-form v-model="validationStatuses['register']">
                   <v-row>
                     <v-col>
                       <div aria-required="true">
@@ -192,14 +192,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('register')"
+                :next-disabled="!validationStatuses['register']"
               />
             </v-stepper-content>
 
             <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
               <div class="mb-4">
-                <v-form  @input="setValidationStatus('contact_details', $event)">
+                <v-form v-model="validationStatuses['contact_details']">
                   <v-row>
                     <v-col v-if="isFieldVisible('date_of_birth')">
                       <div aria-required="true">
@@ -331,14 +331,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('contact_details')"
+                :next-disabled="!validationStatuses['contact_details']"
               />
             </v-stepper-content>
 
             <v-stepper-content :step="getStepIndex('guardians')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
               <div class="mb-4">
-                <v-form  @input="setValidationStatus('guardians', $event)">
+                <v-form  v-model="validationStatuses['guardians']">
                   <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
                     <v-card-title>
                       {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
@@ -413,14 +413,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('guardians')"
+                :next-disabled="!validationStatuses['guardians']"
               />
             </v-stepper-content>
 
             <v-stepper-content :step="getStepIndex('additional')">
               <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
               <div class="mb-4">
-                <v-form  @input="setValidationStatus('additional', $event)">
+                <v-form  v-model="validationStatuses['additional']">
                   <v-row>
                     <v-col>
                       <v-text-field
@@ -453,7 +453,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('additional')"
+                :next-disabled="!validationStatuses['additional']"
               />
             </v-stepper-content>
 
@@ -481,7 +481,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     </tbody>
                   </template>
                 </v-simple-table>
-                <v-form  @input="setValidationStatus('financial', $event)">
+                <v-form v-model="validationStatuses['financial']">
                   <v-row>
                     <v-col>
                       <div aria-required="true">
@@ -527,7 +527,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('financial')"
+                :next-disabled="!validationStatuses['financial']"
               />
             </v-stepper-content>
 
@@ -540,7 +540,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-card-title>
                   <v-card-text v-html="term.term" />
                 </v-card>
-                <v-form  @input="setValidationStatus('consent', $event)">
+                <v-form v-model="validationStatuses['consent']">
                   <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
                     <v-checkbox
                       required
@@ -564,7 +564,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <control-row
                 :step="step"
                 @set-step="setStep"
-                :next-disabled="!getValidationStatus('consent')"
+                :next-disabled="!validationStatuses['consent']"
               />
             </v-stepper-content>
   
@@ -578,6 +578,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                 ></v-text-field>
               </div>
               <v-divider class="my-4" />
+
+              <!-- TODO: Add summary -->
   
               <message-box type="info" class="mb-4">
                 {{ $t("paweljong.event_registration.form.steps.confirm.hint") }}
@@ -605,7 +607,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
         </v-stepper>
         <v-card>
           <v-card-text>
-            <div>HELP TEXT</div>
+            <div>TODO: Add configurable help texts</div>
           </v-card-text>
         </v-card>
       </div>
-- 
GitLab


From a757c6062a143e2f4ca0f325cf5f7db88208d44e Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 14:52:56 +0100
Subject: [PATCH 23/72] Fix maxCost validation

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index a09bd0b..37b3e02 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -722,7 +722,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           ],
           amount: [
             (v) => v > this.event.minCost || this.$t("paweljong.event_registration.form.rules.amount.too_low"),
-            (v) => v < this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
+            (v) => this.event.maxCost === null || v < this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
           ],
         };
       },
-- 
GitLab


From dc8161513e67acbf312070febc4b22fed0337102 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 14:55:55 +0100
Subject: [PATCH 24/72] Fix field references

---
 aleksis/apps/paweljong/schema/event_registration.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 8de0440..f4f9164 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -136,12 +136,10 @@ class SendEventRegistrationMutation(graphene.Mutation):
         for field in event.additional_fields.all():
             registration.extended_data[
                 slugify(field.title).replace("-", "_")
-            ] = event_registration["additional_fields"][field.title]
+            ] = event_registration["additional_fields"][field.id]
 
         for field in event_registration["terms"]:
-            if not field.startswith("consent_"):
-                continue
-            pk = int(field.split("_")[1])
+            pk = field
             term = Terms.objects.get(id=pk)
             registration.accepted_terms.add(term)
 
-- 
GitLab


From f2bfe8db32a559bcb3aebffe9ca44ba1d5d0e2a5 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 16:07:02 +0100
Subject: [PATCH 25/72] Fix voucher stuff

---
 aleksis/apps/paweljong/schema/event_registration.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index f4f9164..b617a2a 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -131,6 +131,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
             event=event,
             person=person,
             medical_information=event_registration["medical_information"],
+            donation=0,
             **school_details,
         )
         for field in event.additional_fields.all():
@@ -145,18 +146,19 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         registration.cost = event.cost
 
-        if event.max_cost is not None and event.max_cost > 0:
+        if event.max_cost is None or event.max_cost > 0:
             amount = event_registration["payment"]["amount"]
 
             if amount < event.cost:
                 voucher_amount = event.cost - amount
-                discount = voucher_amount / event.cost
-                Voucher.objects.create(
+                discount = voucher_amount / event.cost * 100
+                voucher = Voucher.objects.create(
                     person=person,
                     event=event,
                     used=True,
                     discount=discount,
                 )
+                registration.voucher = voucher
             elif amount > event.cost:
                 registration.donation = amount - event.cost
 
-- 
GitLab


From 4c4ef82a78cd0febb43b10627711626b38814edc Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 16:07:34 +0100
Subject: [PATCH 26/72] Raise GraphQL errors instead of causing 500

---
 .../paweljong/schema/event_registration.py    | 81 ++++++++++---------
 1 file changed, 44 insertions(+), 37 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index b617a2a..87d5ff5 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -1,4 +1,4 @@
-from django.db import transaction
+from django.db import transaction, IntegrityError
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
 from django.utils.text import slugify
@@ -54,40 +54,52 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
     @transaction.atomic
     def mutate(self, info, event: graphene.ID, event_registration: EventRegistrationInputType, **kwargs):
-        event = Event.objects.get(pk=event)
+        if not event_registration["retraction_consent"]:
+            raise ValidationError(_("Retraction consent is required"))
 
-        print(event_registration)
+        event = Event.objects.get(pk=event)
 
         email = None
 
         if event_registration["email"] is not None:
-            _mail_address = MailAddress.objects.create(
-                local_part=event_registration["email"]["local_part"],
-                domain=event_registration["email"]["domain"],
-            )
+            try:
+                _mail_address = MailAddress.objects.create(
+                    local_part=event_registration["email"]["local_part"],
+                    domain=event_registration["email"]["domain"],
+                )
+            except IntegrityError:
+                raise ValidationError(_("Mail address already in use."))
+
             email = str(_mail_address)
         elif event_registration["user"] is not None:
             email = event_registration["user"]["email"]
 
         # Create user
-        if event_registration is not None:
-            user = User.objects.create(
-                username=event_registration["user"]["username"],
-                email=event_registration["user"]["email"],
-            )
+        if event_registration["user"] is not None:
+            try:
+                user = User.objects.create(
+                    username=event_registration["user"]["username"],
+                    email=event_registration["user"]["email"],
+                )
+            except IntegrityError:
+                raise ValidationError(_("A user with this username or e-mail already exists."))
+
             user.set_password(event_registration["user"]["password"])
             user.save()
         else:
             user = self.request.user
 
-        person, created = Person.objects.get_or_create(
-            user=user,
-            defaults={
-                "email": email,
-                "first_name": event_registration["person"]["first_name"],
-                "last_name": event_registration["person"]["last_name"],
-            },
-        )
+        try:
+            person, created = Person.objects.get_or_create(
+                user=user,
+                defaults={
+                    "email": email,
+                    "first_name": event_registration["person"]["first_name"],
+                    "last_name": event_registration["person"]["last_name"],
+                },
+            )
+        except IntegrityError:
+            raise ValidationError(_("A person with using the e-mail address %s already exists." % email))
 
         # Store contact information in database
         for field_name in ["date_of_birth", "sex", "address", "mobile_number"]:
@@ -101,23 +113,22 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         if event_registration["person"] is not None:
             for guardian_data in event_registration["person"]["guardians"]:
-                guardian, created = Person.objects.get_or_create(
-                    defaults={
-                        "mobile_number": guardian_data["mobile_number"],
-                    },
-                    first_name=guardian_data["first_name"],
-                    last_name=guardian_data["last_name"],
-                    email=guardian_data["email"],
-                )
+                try:
+                    guardian, created = Person.objects.get_or_create(
+                        defaults={
+                            "mobile_number": guardian_data["mobile_number"],
+                        },
+                        first_name=guardian_data["first_name"],
+                        last_name=guardian_data["last_name"],
+                        email=guardian_data["email"],
+                    )
+                except IntegrityError:
+                    raise ValidationError(_("A person with using the e-mail address %s already exists." % guardian_data["email"]))
 
                 person.guardians.add(guardian)
             person.save()
 
         if event_registration["email"] is not None:
-            _mail_address = MailAddress.objects.create(
-                local_part=event_registration["email"]["local_part"],
-                domain=event_registration["email"]["domain"],
-            )
             _mail_address.person = person
             _mail_address.save()
 
@@ -194,8 +205,4 @@ class SendEventRegistrationMutation(graphene.Mutation):
             user=person,
         )
 
-        if event_registration["retraction_consent"]:
-            return SendEventRegistrationMutation(ok=True)
-
-        raise ValidationError(_("Retraction consent is required"))
-        return SendEventRegistrationMutation(ok=False)
+        return SendEventRegistrationMutation(ok=True)
-- 
GitLab


From 448f1588acdc9c855c71806c83ef620549116e4d Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 16:28:28 +0100
Subject: [PATCH 27/72] Remove voucher code field & prefill form with user data

---
 .../EventRegistrationForm.vue                 | 35 ++++++++++++-------
 .../event_registration/helpers.graphql        |  9 +++--
 .../paweljong/schema/event_registration.py    | 35 ++++++++++++-------
 3 files changed, 52 insertions(+), 27 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 37b3e02..6704dc9 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -499,8 +499,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                         ></v-select>
                       </div>
                     </v-col>
-                  </v-row>
-                  <v-row>
                     <v-col>
                       <div aria-required="true">
                         <positive-small-integer-field
@@ -512,14 +510,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                         />
                       </div>
                     </v-col>
-                    <v-col>
-                      <v-text-field
-                        outlined
-                        v-model="data.payment.voucherCode"
-                        :label="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.financial.fields.voucher_code.help_text')"
-                      ></v-text-field>
-                    </v-col>
                   </v-row>
                 </v-form>
               </div>
@@ -619,7 +609,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   
   <script>
   import { eventBySlug } from "../event/events.graphql";
-  import { gqlPaweljongPaymentChoices } from "./helpers.graphql";
+  import { gqlPaweljongPaymentChoices, whoAmI } from "./helpers.graphql";
   import {
     mailDomainsForUser,
     disallowedLocalParts,
@@ -642,6 +632,28 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       paweljongPaymentChoices: gqlPaweljongPaymentChoices,
       mailDomains: mailDomainsForUser,
       disallowedLocalParts: disallowedLocalParts,
+      whoAmI: {
+        query: whoAmI,
+        result({ data }) {
+          if (data && data.whoAmI && data.whoAmI.person) {
+            const { id, __typename, street, housenumber, postalCode, place, guardians, ...filteredPerson } = data.whoAmI.person;
+            this.data.person = filteredPerson;
+            
+            const filteredGuardians = guardians.map(({ __typename, ...filteredGuardian }) => filteredGuardian);
+            this.data.person.guardians = filteredGuardians;
+
+            this.data.person.address = {
+              street: street,
+              housenumber: housenumber,
+              postalCode: postalCode,
+              place: place,
+            };
+          }
+        },
+        skip() {
+          return this.$root.whoAmI?.person?.isDummy
+        },
+      },
     },
     mixins: [eventAdditionalFieldMixin, formRulesMixin],
     watch: {
@@ -827,7 +839,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           schoolClass: "",
           payment: {
             paymentMethod: "",
-            voucherCode: "",
             amount: null,
           },
           medicalInformation: "",
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
index 062a272..b077505 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
@@ -1,7 +1,6 @@
 query whoAmI {
   whoAmI {
     id
-    username
     person {
       id
       firstName
@@ -13,7 +12,13 @@ query whoAmI {
         mobileNumber
         email
       }
-      email
+      mobileNumber
+      sex
+      street
+      housenumber
+      postalCode
+      place
+      dateOfBirth
     }
   }
 }
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 87d5ff5..b6d529b 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -113,19 +113,28 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         if event_registration["person"] is not None:
             for guardian_data in event_registration["person"]["guardians"]:
-                try:
-                    guardian, created = Person.objects.get_or_create(
-                        defaults={
-                            "mobile_number": guardian_data["mobile_number"],
-                        },
-                        first_name=guardian_data["first_name"],
-                        last_name=guardian_data["last_name"],
-                        email=guardian_data["email"],
-                    )
-                except IntegrityError:
-                    raise ValidationError(_("A person with using the e-mail address %s already exists." % guardian_data["email"]))
-
-                person.guardians.add(guardian)
+                if hasattr(guardian_data, "id"):
+                    guardian = Person.objects.get(pk=guardian_data["id"])
+                    guardian.first_name=guardian_data["first_name"]
+                    guardian.last_name=guardian_data["last_name"]
+                    guardian.email=guardian_data["email"]
+                    guardian.mobile_number=guardian_data["mobile_number"]
+                    
+                    guardian.save()
+                else:
+                    try:
+                        guardian, created = Person.objects.get_or_create(
+                            defaults={
+                                "mobile_number": guardian_data["mobile_number"],
+                            },
+                            first_name=guardian_data["first_name"],
+                            last_name=guardian_data["last_name"],
+                            email=guardian_data["email"],
+                        )
+                    except IntegrityError:
+                        raise ValidationError(_("A person with using the e-mail address %s already exists." % guardian_data["email"]))
+
+                    person.guardians.add(guardian)
             person.save()
 
         if event_registration["email"] is not None:
-- 
GitLab


From b06a295980c3685b4fac84a531f7551779b180ff Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 16:31:50 +0100
Subject: [PATCH 28/72] Omit user data if it is empty

---
 .../components/event_registration/EventRegistrationForm.vue   | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 6704dc9..bb1098d 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -756,6 +756,10 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           const { email, ...filteredData } = data;
           data = filteredData;
         }
+        if (!this.data.user.username && !this.data.user.email && !this.data.user.password) {
+          const { user, ...filteredData } = data;
+          data = filteredData;
+        }
         return data;
       },
       steps() {
-- 
GitLab


From 45365f96c6384fdb520abb9f80d564034e72167c Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 16:33:17 +0100
Subject: [PATCH 29/72] Fix reference to request

---
 aleksis/apps/paweljong/schema/event_registration.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index b6d529b..d1e5015 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -87,7 +87,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
             user.set_password(event_registration["user"]["password"])
             user.save()
         else:
-            user = self.request.user
+            user = info.context.user
 
         try:
             person, created = Person.objects.get_or_create(
-- 
GitLab


From 2b525169cd1653195eef5bb776c23caf23b47f38 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 16:44:34 +0100
Subject: [PATCH 30/72] Fix additional field handling

---
 aleksis/apps/paweljong/schema/event_registration.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index d1e5015..fd44bd0 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -59,6 +59,8 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         event = Event.objects.get(pk=event)
 
+        print(event_registration)
+
         email = None
 
         if event_registration["email"] is not None:
@@ -157,7 +159,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
         for field in event.additional_fields.all():
             registration.extended_data[
                 slugify(field.title).replace("-", "_")
-            ] = event_registration["additional_fields"][field.id]
+            ] = event_registration["additional_fields"][str(field.id)]
 
         for field in event_registration["terms"]:
             pk = field
-- 
GitLab


From c65f50d4c4bc879fdb4c6052f0d80d4dedbc1d34 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 16:49:10 +0100
Subject: [PATCH 31/72] Remove legacy urls

---
 .../apps/paweljong/templates/paweljong/event/full.html |  4 +---
 aleksis/apps/paweljong/urls.py                         | 10 ----------
 2 files changed, 1 insertion(+), 13 deletions(-)

diff --git a/aleksis/apps/paweljong/templates/paweljong/event/full.html b/aleksis/apps/paweljong/templates/paweljong/event/full.html
index 051405e..bc5fd3a 100644
--- a/aleksis/apps/paweljong/templates/paweljong/event/full.html
+++ b/aleksis/apps/paweljong/templates/paweljong/event/full.html
@@ -54,9 +54,7 @@
       </div>
     </div>
     <div class="card-action">
-      {% if can_register and not is_authenticated %}
-        <a href="{% url "register_event_by_slug_start" event.slug %}">{% trans "Register now" %}</a>
-      {% elif can_register and is_authenticated %}
+      {% if can_register %}
         <a href="{% url "register_event_by_slug" event.slug %}">{% trans "Register now" %}</a>
       {% else %}
         <a href="#">{% trans "Not available" %}</a>
diff --git a/aleksis/apps/paweljong/urls.py b/aleksis/apps/paweljong/urls.py
index bef72e0..c2743b1 100644
--- a/aleksis/apps/paweljong/urls.py
+++ b/aleksis/apps/paweljong/urls.py
@@ -40,11 +40,6 @@ account_conditions = {
 urlpatterns = [
     path("event/<slug:slug>/edit/", views.EditEventView.as_view(), name="edit_event_by_slug"),
     path("event/<slug:slug>/terms/", views.ViewTerms.as_view(), name="view_event_terms_by_slug"),
-    path(
-        "event/<slug:slug>/register/",
-        views.RegisterEventWizardView.as_view(register_forms, condition_dict=condition_dict),
-        name="register_event_by_slug",
-    ),
     path(
         "group_persons/<int:pk>/add/",
         views.PersonGroupView.as_view(),
@@ -52,11 +47,6 @@ urlpatterns = [
     ),
     path("event/<slug:slug>/", views.EventFullView.as_view(), name="event_by_name"),
     path("event/<slug:slug>/detail/", views.EventDetailView.as_view(), name="event_detail_by_name"),
-    path(
-        "event/<slug:slug>/start/",
-        views.RegisterEventStart.as_view(),
-        name="register_event_by_slug_start",
-    ),
     path("misc/set_email_needed/<slug:slug>/", views.set_email_needed, name="set_email_needed"),
     path("misc/set_email_needed/", views.set_email_needed, name="set_email_needed_no_slug"),
     path(
-- 
GitLab


From 27959b01366f6d1d5f7eb5f50e9f352a4c1f9423 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:05:48 +0100
Subject: [PATCH 32/72] Use configurable tezor data in models

---
 aleksis/apps/paweljong/models.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index bc7b59e..1d1f87d 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -397,9 +397,9 @@ class EventRegistration(ExtensibleModel):
 
     def get_invoice(self):
         # FIXME Maybe do not hard-code this
-        client, __ = Client.objects.get_or_create(name="Teckids e.V.")
+        client, __ = Client.objects.get_or_create(name=get_site_preferences()["paweljong__event_invoice_client_name"])
         group, __ = InvoiceGroup.objects.get_or_create(
-            name="Hack'n'Fun-Veranstaltungen",
+            name=get_site_preferences()["paweljong__event_invoice_group_name"],
             client=client,
             defaults={
                 "template_name": "paweljong/invoice_pdf.html",
-- 
GitLab


From 11354c27f2c51f9780b8b00647c8147536fa0949 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:06:11 +0100
Subject: [PATCH 33/72] Fix additional fields handling (again)

---
 aleksis/apps/paweljong/schema/event_registration.py | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index fd44bd0..a563621 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -59,8 +59,6 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         event = Event.objects.get(pk=event)
 
-        print(event_registration)
-
         email = None
 
         if event_registration["email"] is not None:
@@ -157,9 +155,10 @@ class SendEventRegistrationMutation(graphene.Mutation):
             **school_details,
         )
         for field in event.additional_fields.all():
-            registration.extended_data[
-                slugify(field.title).replace("-", "_")
-            ] = event_registration["additional_fields"][str(field.id)]
+            if hasattr(event_registration["additional_fields"], str(field.id)):
+                registration.extended_data[
+                    slugify(field.title).replace("-", "_")
+                ] = event_registration["additional_fields"][str(field.id)]
 
         for field in event_registration["terms"]:
             pk = field
-- 
GitLab


From 1808e19cd8c9e2664668d25144c9de5c34be331b Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:12:15 +0100
Subject: [PATCH 34/72] Fix registration detail view for registrations without
 invoices

---
 .../templates/paweljong/event_registration/full.html         | 2 ++
 aleksis/apps/paweljong/views.py                              | 5 +++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html b/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html
index e909022..881c3a1 100644
--- a/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html
+++ b/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html
@@ -238,6 +238,7 @@
     </div>
   </div>
 
+  {% if invoice %}
   <h5>{% trans "Invoice details" %}</h5>
   <div class="col s12 m8">
     <div class="row">
@@ -318,6 +319,7 @@
       </div>
     </div>
   </div>
+  {% endif %}
 
   {% if registration.person.guardians.all %}
   <h5>{% trans "Guardians / Parents "%}</h5>
diff --git a/aleksis/apps/paweljong/views.py b/aleksis/apps/paweljong/views.py
index 36b5399..c2bf874 100644
--- a/aleksis/apps/paweljong/views.py
+++ b/aleksis/apps/paweljong/views.py
@@ -219,8 +219,9 @@ class EventRegistrationDetailView(PermissionRequiredMixin, DetailView):
     def get_context_data(self, *args, **kwargs):
         context = super().get_context_data(*args, **kwargs)
 
-        invoice = self.get_object().get_invoice()
-        context["invoice"] = invoice
+        if self.get_object().event.max_cost is None or self.get_object().event.max_cost > 0:
+            invoice = self.get_object().get_invoice()
+            context["invoice"] = invoice
 
         return context
 
-- 
GitLab


From ab69c720881025cefaae940e363eade4cbf8f9d2 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:22:35 +0100
Subject: [PATCH 35/72] Fix mail domain and guardian handling

---
 aleksis/apps/paweljong/schema/event_registration.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index a563621..43aab8a 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -8,7 +8,7 @@ import graphene
 from graphene_django import DjangoObjectType
 from templated_email import send_templated_mail
 
-from aleksis.apps.postbuero.models import MailAddress
+from aleksis.apps.postbuero.models import MailAddress, MailDomain
 from aleksis.apps.postbuero.schema import MailAddressInputType
 from aleksis.core.models import Activity, Person
 from aleksis.core.schema.base import PermissionsTypeMixin
@@ -62,10 +62,14 @@ class SendEventRegistrationMutation(graphene.Mutation):
         email = None
 
         if event_registration["email"] is not None:
+            try:
+                domain = MailDomain.objects.get(pk=event_registration["email"]["domain"])
+            except IntegrityError:
+                raise ValidationError(_("Mail domain does not exist."))
             try:
                 _mail_address = MailAddress.objects.create(
                     local_part=event_registration["email"]["local_part"],
-                    domain=event_registration["email"]["domain"],
+                    domain=domain,
                 )
             except IntegrityError:
                 raise ValidationError(_("Mail address already in use."))
@@ -113,7 +117,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         if event_registration["person"] is not None:
             for guardian_data in event_registration["person"]["guardians"]:
-                if hasattr(guardian_data, "id"):
+                if "id" in guardian_data:
                     guardian = Person.objects.get(pk=guardian_data["id"])
                     guardian.first_name=guardian_data["first_name"]
                     guardian.last_name=guardian_data["last_name"]
-- 
GitLab


From 2a77147e1c89e22a6353015aa25837423a928122 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:22:44 +0100
Subject: [PATCH 36/72] Add fixme

---
 .../components/event_registration/EventRegistrationForm.vue      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index bb1098d..e81f871 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -215,6 +215,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     </v-col>
                     <v-col v-if="isFieldVisible('sex')">
                       <div aria-required="true">
+                        <!-- FIXME: Prefilling data does not work due to upper-/lowercase situation; will be fixed with core person form refactoring -->
                         <sex-select
                           outlined
                           v-model="data.person.sex"
-- 
GitLab


From 18c75190a6053bf4ae416428fe24c94bb620d9d2 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:45:02 +0100
Subject: [PATCH 37/72] Re-introduce url

---
 aleksis/apps/paweljong/urls.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/aleksis/apps/paweljong/urls.py b/aleksis/apps/paweljong/urls.py
index c2743b1..a4c66d7 100644
--- a/aleksis/apps/paweljong/urls.py
+++ b/aleksis/apps/paweljong/urls.py
@@ -1,4 +1,5 @@
 from django.urls import path
+from django.views.generic import TemplateView
 
 from aleksis.apps.postbuero.forms import MailAddForm
 
@@ -40,6 +41,11 @@ account_conditions = {
 urlpatterns = [
     path("event/<slug:slug>/edit/", views.EditEventView.as_view(), name="edit_event_by_slug"),
     path("event/<slug:slug>/terms/", views.ViewTerms.as_view(), name="view_event_terms_by_slug"),
+    path(
+        "event/<slug:slug>/register/",
+        TemplateView.as_view(template_name="core/empty.html"),
+        name="register_event_by_slug",
+    ),
     path(
         "group_persons/<int:pk>/add/",
         views.PersonGroupView.as_view(),
-- 
GitLab


From d1e66e9a9eea00f0de2eb631e02ac2552db79b3a Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:48:28 +0100
Subject: [PATCH 38/72] Refactor email buttoms

---
 .../EventRegistrationForm.vue                 | 22 ++++++++++++-------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index e81f871..2f1d544 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -99,14 +99,20 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
                   </v-card-text>
                   <v-card-actions>
-                    <primary-action-button
-                      @click="emailMode = 'postbuero'"
-                      i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis"
-                    />
-                    <primary-action-button
-                      @click="emailMode = 'own'"
-                      i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_own"
-                    />
+                    <v-row>
+                      <v-col>
+                        <primary-action-button
+                          @click="emailMode = 'postbuero'"
+                          i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis"
+                        />
+                      </v-col>
+                      <v-col>
+                        <primary-action-button
+                          @click="emailMode = 'own'"
+                          i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_own"
+                        />
+                      </v-col>
+                    </v-row>
                   </v-card-actions>
                 </template>
               </div>
-- 
GitLab


From 68e5a8e8eb3979abd11db6da28310f04d48d58d2 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 17:52:29 +0100
Subject: [PATCH 39/72] Fix stepper header wrapping

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 2f1d544..94e6770 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -24,7 +24,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       <div v-else-if="event">
         <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
         <v-stepper v-model="step" class="mb-4">
-          <v-stepper-header>
+          <v-stepper-header class="flex-nowrap">
             <template v-for="(stepChoice, index) in steps">
               <v-stepper-step :complete="step > index + 1" :step="index + 1" :key="stepChoice.name">
                 {{ $t(stepChoice.titleKey) }}
-- 
GitLab


From 1f099921d86d98f815ff2365672644d4a3d8f6a9 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 18:12:44 +0100
Subject: [PATCH 40/72] Remove empty help card

---
 .../components/event_registration/EventRegistrationForm.vue  | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 94e6770..5901ec8 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -602,11 +602,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             </v-stepper-content>
           </v-stepper-items>
         </v-stepper>
-        <v-card>
-          <v-card-text>
-            <div>TODO: Add configurable help texts</div>
-          </v-card-text>
-        </v-card>
       </div>
       <div v-else-if="$apollo.queries.event.loading">
         <v-skeleton-loader type="heading, text, actions" />
-- 
GitLab


From 876bab6c12bfc631eb412a87c327299591c69d66 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Fri, 14 Feb 2025 18:19:15 +0100
Subject: [PATCH 41/72] Add german translations

---
 .../apps/paweljong/frontend/messages/de.json  | 182 ++++++++++++++++++
 1 file changed, 182 insertions(+)

diff --git a/aleksis/apps/paweljong/frontend/messages/de.json b/aleksis/apps/paweljong/frontend/messages/de.json
index 26073ec..40f51ca 100644
--- a/aleksis/apps/paweljong/frontend/messages/de.json
+++ b/aleksis/apps/paweljong/frontend/messages/de.json
@@ -51,6 +51,188 @@
                 "optional": "Optional"
             },
             "help_text": "Hilfe-Text"
+        },
+        "event_registration": {
+            "form": {
+                "submitted": {
+                    "thank_you": "Danke!",
+                    "submitted_successfully": "Deine Anmeldung wurde erfolgreich abgeschickt."
+                },
+                "steps": {
+                    "email": {
+                        "title": "E-Mail-Adresse",
+                        "choose_mode": {
+                        "help_text": "Um fortzufahren, musst du eine E-Mail-Adresse angeben. Du kannst entweder eine neue AlekSIS-E-Mail-Adresse registrieren oder deine eigene, bestehende E-Mail-Adresse verwenden.",
+                        "continue_aleksis": "Mit neuer AlekSIS-E-Mail-Adresse fortfahren",
+                        "continue_own": "Mit eigener E-Mail-Adresse fortfahren"
+                        },
+                        "fields": {
+                        "email": {
+                            "label": "E-Mail-Adresse"
+                        },
+                        "confirm_email": {
+                            "label": "E-Mail-Adresse bestätigen"
+                        }
+                        }
+                    },
+                    "register": {
+                        "title": "Konto",
+                        "fields": {
+                        "first_name": {
+                            "label": "Vorname"
+                        },
+                        "last_name": {
+                            "label": "Nachname"
+                        },
+                        "username": {
+                            "label": "Benutzername"
+                        },
+                        "password": {
+                            "label": "Passwort"
+                        },
+                        "confirm_password": {
+                            "label": "Passwort bestätigen"
+                        }
+                        }
+                    },
+                    "contact_details": {
+                        "title": "Kontaktinformationen",
+                        "fields": {
+                        "first_name": {
+                            "label": "Vorname"
+                        },
+                        "last_name": {
+                            "label": "Nachname"
+                        },
+                        "date_of_birth": {
+                            "label": "Geburtsdatum"
+                        },
+                        "sex": {
+                            "label": "Geschlecht",
+                            "help_text": "Aus verschiedenen Gründen, z. B. weil wir aus rechtlichen Gründen nachts eine geschlechtergetrennte Unterbringung einhalten müssen, müssen wir wissen, ob du ein Junge oder ein Mädchen bist."
+                        },
+                        "street": {
+                            "label": "Straße"
+                        },
+                        "housenumber": {
+                            "label": "Hausnummer"
+                        },
+                        "postal_code": {
+                            "label": "Postleitzahl"
+                        },
+                        "place": {
+                            "label": "Ort"
+                        },
+                        "email": {
+                            "label": "E-Mail-Adresse",
+                            "help_text": "Bitte gib hier deine persönliche E-Mail-Adresse an, die du regelmäßig abrufst. Wichtige Informationen werden immer auch an deine Eltern gesendet. Verwende hier keine E-Mail-Adresse deiner Eltern."
+                        },
+                        "mobile_number": {
+                            "label": "Handynummer",
+                            "help_text": "Deine Handynummer hilft uns, dich im Notfall während der Veranstaltung zu erreichen, z. B. wenn du mit deiner Gruppe auf einer Konferenz unterwegs bist. Falls du kein Handy hast, kannst du das Feld leer lassen."
+                        },
+                        "school": {
+                            "label": "Schule",
+                            "help_text": "Bitte gib den Namen deiner Schule ein."
+                        },
+                        "school_place": {
+                            "label": "Schulort",
+                            "help_text": "Gib den Ort (Stadt) an, in dem sich deine Schule befindet."
+                        },
+                        "school_class": {
+                            "label": "Klasse",
+                            "help_text": "Bitte gib deine aktuelle Klasse an (z. B. 8a)."
+                        }
+                        }
+                    },
+                    "guardians": {
+                        "title": "Erziehungsberechtigte",
+                        "counter": "Erziehungsberechtigter {i}",
+                        "add": "Erziehungsberechtigten hinzufügen",
+                        "fields": {
+                        "first_name": {
+                            "label": "Vorname des Erziehungsberechtigten",
+                            "help_text": "Bitte gib den Vornamen des Erziehungsberechtigten an, der das Anmeldeformular mit dir ausfüllt und während der Veranstaltung in Notfällen erreichbar ist."
+                        },
+                        "last_name": {
+                            "label": "Nachname des Erziehungsberechtigten",
+                            "help_text": "Bitte gib den Nachnamen des Erziehungsberechtigten an, der das Anmeldeformular mit dir ausfüllt und während der Veranstaltung in Notfällen erreichbar ist."
+                        },
+                        "email": {
+                            "label": "E-Mail-Adresse des Erziehungsberechtigten"
+                        },
+                        "mobile_number": {
+                            "label": "Handynummer des Erziehungsberechtigten",
+                            "help_text": "Wir benötigen die Handynummer für Notfälle, falls wir deine Eltern während der Veranstaltung dringend erreichen müssen."
+                        }
+                        }
+                    },
+                    "additional": {
+                        "title": "Zusätzliche Informationen",
+                        "fields": {
+                        "medical_information": {
+                            "label": "Medizinische Informationen / Unverträglichkeiten",
+                            "help_text": "Falls es medizinisch wichtige Dinge gibt, die wir beachten müssen, z. B. bei der Essenszubereitung oder zur Einnahme verschriebener Medikamente, gib diese bitte hier an."
+                        }
+                        }
+                    },
+                    "financial": {
+                        "title": "Bezahlung",
+                        "help_text": {
+                        "cost": "Tatsächliche Kosten",
+                        "max_cost": "Maximal zu zahlender Betrag",
+                        "min_cost": "Minimal zu zahlender Betrag",
+                        "cost_info": "Unser Verein möchte allen Kindern und Jugendlichen die Teilnahme an unseren Veranstaltungen ermöglichen. Manchmal können Familien jedoch nicht die volle Gebühr zahlen. Deshalb haben wir ein Budget, aus dem wir die Teilnahme fördern können, wenn die Notwendigkeit geprüft wurde. Dieses Budget basiert auf Spenden. Falls du freiwillig einen zusätzlichen Betrag für dieses Budget spenden möchtest, gib bitte einen Betrag über den tatsächlichen Kosten an. Falls du die volle Gebühr nicht zahlen kannst, gib bitte einen Betrag an, der zu deinen finanziellen Möglichkeiten passt."
+                        },
+                        "fields": {
+                        "payment_method": {
+                            "label": "Zahlungsmethode",
+                            "help_text": "Bitte wähle eine Zahlungsmethode. Die tatsächliche Zahlung erfolgt nach der Anmeldung."
+                        },
+                        "voucher_code": {
+                            "label": "Gutscheincode",
+                            "help_text": "Falls du einen Gutscheincode hast, gib ihn hier ein."
+                        },
+                        "amount": {
+                            "label": "Von dir gezahlter Betrag (in €)"
+                        }
+                        }
+                    },
+                    "consent": {
+                        "title": "Einverständniserklärung",
+                        "fields": {
+                        "retraction_consent": {
+                            "label": "Ich bestätige, dass die Rücknahme der Anmeldung nach dem {date} nicht mehr möglich ist."
+                        }
+                        }
+                    },
+                    "confirm": {
+                        "title": "Bestätigen",
+                        "hint": "Nach diesem Schritt kannst du deine Anmeldung nicht mehr bearbeiten.",
+                        "fields": {
+                        "comment": {
+                            "label": "Weitere Anmerkungen",
+                            "help_text": "Hier kannst du uns zusätzliche Anmerkungen mitteilen."
+                        }
+                        }
+                    }
+                },
+                "rules": {
+                    "email": {
+                        "valid": "Dies ist keine gültige E-Mail-Adresse"
+                    },
+                    "confirm_email": {
+                        "no_match": "Die E-Mail-Adressen stimmen nicht überein"
+                    },
+                    "confirm_password": {
+                        "no_match": "Die Passwörter stimmen nicht überein"
+                    },
+                    "amount": {
+                        "too_high": "Der Betrag ist höher als der maximal zu zahlende Betrag.",
+                        "too_low": "Der Betrag ist niedriger als der minimal zu zahlende Betrag."
+                    }
+                }
+            }
         }
     }
 }
-- 
GitLab


From 03a763a0aafd71f3e6d3113f16e5c6aa4f63f8d4 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 19:43:03 +0100
Subject: [PATCH 42/72] Do not set null fields

---
 aleksis/apps/paweljong/schema/event_registration.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 43aab8a..dbf4b30 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -107,7 +107,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         # Store contact information in database
         for field_name in ["date_of_birth", "sex", "address", "mobile_number"]:
-            if event_registration["person"] is not None and event_registration["person"][field_name] != "":
+            if event_registration["person"] is not None and event_registration["person"][field_name] is not None and event_registration["person"][field_name] != "":
                 if field_name == "address":
                     for addr_field in ["street", "housenumber", "postal_code", "place"]:
                         setattr(person, addr_field, event_registration["person"]["address"][addr_field])
-- 
GitLab


From 70a703ca94179b1538d20612a0596de90b287e8f Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 20:22:08 +0100
Subject: [PATCH 43/72] Display errors in frontend

---
 .../components/event_registration/EventRegistrationForm.vue      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 5901ec8..61bcdd7 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -597,6 +597,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     @confirm="mutate"
                     :next-loading="loading"
                   />
+                  <v-alert v-if="error" type="error" outlined>{{ error.message }}</v-alert>
                 </template>
               </ApolloMutation>
             </v-stepper-content>
-- 
GitLab


From be3fbc0694f65625cb413b0b16b4de6ea4277fe1 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 21:14:32 +0100
Subject: [PATCH 44/72] Allow 0 as amount

---
 .../event_registration/EventRegistrationForm.vue           | 7 +++----
 aleksis/apps/paweljong/schema/event_registration.py        | 4 +++-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 61bcdd7..1e11e39 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -512,8 +512,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           outlined
                           v-model="data.payment.amount"
                           :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
-                          :rules="$rules().required.build(rules.amount)"
-                          required
+                          :rules="$rules().build(rules.amount)"
                         />
                       </div>
                     </v-col>
@@ -736,8 +735,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             (v) => this.data.user.password == v || this.$t("paweljong.event_registration.form.rules.confirm_password.no_match"),
           ],
           amount: [
-            (v) => v > this.event.minCost || this.$t("paweljong.event_registration.form.rules.amount.too_low"),
-            (v) => this.event.maxCost === null || v < this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
+            (v) => v >= this.event.minCost || this.$t("paweljong.event_registration.form.rules.amount.too_low"),
+            (v) => this.event.maxCost === null || v <= this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
           ],
         };
       },
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index dbf4b30..4990414 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -27,7 +27,7 @@ class EventRegistrationType(PermissionsTypeMixin, DjangoObjectType):
 class PaymentInputType(graphene.InputObjectType):
     payment_method = graphene.String(required=True)
     voucher_code = graphene.String(required=False)
-    amount = graphene.Int(required=True)
+    amount = graphene.Int(required=False) # FIXME: Fix frontend to allow 0, then make field required again
 
 
 class EventRegistrationInputType(graphene.InputObjectType):
@@ -173,6 +173,8 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         if event.max_cost is None or event.max_cost > 0:
             amount = event_registration["payment"]["amount"]
+            if amount is None:
+                raise ValidationError(_("The field 'Amount paid by you' is required."))
 
             if amount < event.cost:
                 voucher_amount = event.cost - amount
-- 
GitLab


From 8a2859b546c7a601184bbcaeb2cf24665cba546b Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 22:36:13 +0100
Subject: [PATCH 45/72] Update translations

---
 .../locale/de_DE/LC_MESSAGES/django.po        | 551 ++++++++++--------
 .../paweljong/schema/event_registration.py    |   4 +-
 2 files changed, 314 insertions(+), 241 deletions(-)

diff --git a/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po
index 9d1c245..8270651 100644
--- a/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-10 19:38+0000\n"
+"POT-Creation-Date: 2025-02-14 21:32+0000\n"
 "PO-Revision-Date: 2023-06-07 13:14+0000\n"
 "Last-Translator: Tom Teichler <tom.teichler@teckids.org>\n"
 "Language-Team: German <https://translate.edugit.org/projects/hacknfun/"
@@ -78,19 +78,20 @@ msgstr ""
 "Creative Commons mit Namensnennung und Weitergabe unter gleichen "
 "Bedingungen, 4.0 oder später"
 
-#: aleksis/apps/paweljong/forms.py:47 aleksis/apps/paweljong/forms.py:448
+#: aleksis/apps/paweljong/forms.py:60 aleksis/apps/paweljong/forms.py:491
+#: aleksis/apps/paweljong/forms.py:615
 msgid "Base data"
 msgstr "Basisdaten"
 
-#: aleksis/apps/paweljong/forms.py:51
+#: aleksis/apps/paweljong/forms.py:64
 msgid "Date data"
 msgstr "Kalenderdaten"
 
-#: aleksis/apps/paweljong/forms.py:52
+#: aleksis/apps/paweljong/forms.py:65
 msgid "Event details"
 msgstr "Veranstaltungsdetails"
 
-#: aleksis/apps/paweljong/forms.py:53 aleksis/apps/paweljong/models.py:171
+#: aleksis/apps/paweljong/forms.py:66 aleksis/apps/paweljong/models.py:174
 #: aleksis/apps/paweljong/templates/paweljong/event/terms.html:4
 #: aleksis/apps/paweljong/templates/paweljong/event/terms.html:5
 #: aleksis/apps/paweljong/templates/paweljong/term/list.html:6
@@ -98,61 +99,105 @@ msgstr "Veranstaltungsdetails"
 msgid "Terms"
 msgstr "Bedingungen"
 
-#: aleksis/apps/paweljong/forms.py:54 aleksis/apps/paweljong/models.py:174
+#: aleksis/apps/paweljong/forms.py:67 aleksis/apps/paweljong/models.py:177
 #: aleksis/apps/paweljong/templates/paweljong/info_mailing/list.html:6
 #: aleksis/apps/paweljong/templates/paweljong/info_mailing/list.html:7
 msgid "Info mailings"
 msgstr "Info-Mailings"
 
-#: aleksis/apps/paweljong/forms.py:114
+#: aleksis/apps/paweljong/forms.py:112 aleksis/apps/paweljong/forms.py:247
+#: aleksis/apps/paweljong/forms.py:628 aleksis/apps/paweljong/models.py:192
+#: aleksis/apps/paweljong/templates/paweljong/print/list_participants.html:22
+msgid "Date of birth"
+msgstr "Geburtsdatum"
+
+#: aleksis/apps/paweljong/forms.py:113 aleksis/apps/paweljong/forms.py:252
+#: aleksis/apps/paweljong/models.py:193
+msgid "Sex"
+msgstr "Geschlecht"
+
+#: aleksis/apps/paweljong/forms.py:114 aleksis/apps/paweljong/forms.py:268
+#: aleksis/apps/paweljong/models.py:194
+msgid "Street"
+msgstr "Straße"
+
+#: aleksis/apps/paweljong/forms.py:115 aleksis/apps/paweljong/models.py:195
+#, fuzzy
+#| msgid "Housenumber"
+msgid "House number"
+msgstr "Hausnummer"
+
+#: aleksis/apps/paweljong/forms.py:116 aleksis/apps/paweljong/forms.py:278
+#: aleksis/apps/paweljong/models.py:196
+msgid "Postal code"
+msgstr "Postleitzahl"
+
+#: aleksis/apps/paweljong/forms.py:117 aleksis/apps/paweljong/forms.py:283
+#: aleksis/apps/paweljong/models.py:197
+msgid "Place"
+msgstr "Stadt"
+
+#: aleksis/apps/paweljong/forms.py:118 aleksis/apps/paweljong/forms.py:298
+#: aleksis/apps/paweljong/models.py:198
+#: aleksis/apps/paweljong/templates/paweljong/print/corona.html:22
+#: aleksis/apps/paweljong/templates/paweljong/print/list_participants.html:23
+msgid "Mobile number"
+msgstr "Handynummer"
+
+#: aleksis/apps/paweljong/forms.py:119 aleksis/apps/paweljong/forms.py:327
+#: aleksis/apps/paweljong/models.py:199
+msgid "School details"
+msgstr "Angaben zur Schule"
+
+#: aleksis/apps/paweljong/forms.py:141
 msgid "Event the voucher is valid for"
 msgstr "Veranstaltungsgutschein ist gültig für"
 
-#: aleksis/apps/paweljong/forms.py:115
+#: aleksis/apps/paweljong/forms.py:142
 msgid "Person the voucher is valid for"
 msgstr "Person, für die der Gutschein gültig ist"
 
-#: aleksis/apps/paweljong/forms.py:116
+#: aleksis/apps/paweljong/forms.py:143
 msgid "Voucher discount"
 msgstr "Rabatt"
 
-#: aleksis/apps/paweljong/forms.py:124 aleksis/apps/paweljong/models.py:155
+#: aleksis/apps/paweljong/forms.py:151 aleksis/apps/paweljong/models.py:156
 msgid "Group"
 msgstr "Gruppe"
 
-#: aleksis/apps/paweljong/forms.py:126
+#: aleksis/apps/paweljong/forms.py:153
 msgid "Select group to generate list"
 msgstr "Gruppe auswählen, für die die Liste generiert werden soll"
 
-#: aleksis/apps/paweljong/forms.py:130
+#: aleksis/apps/paweljong/forms.py:157
 msgid "Template"
 msgstr "Vorlage"
 
-#: aleksis/apps/paweljong/forms.py:132
+#: aleksis/apps/paweljong/forms.py:159
 msgid "Select template to generate list"
 msgstr "Vorlage auswählen, um Liste zu generieren"
 
-#: aleksis/apps/paweljong/forms.py:136
+#: aleksis/apps/paweljong/forms.py:163
 msgid "Landscape"
 msgstr "Querformat"
 
-#: aleksis/apps/paweljong/forms.py:137
+#: aleksis/apps/paweljong/forms.py:164
 msgid "Select if output should be in landscape"
 msgstr "Wähle, ob die Ausgabe im Querformat sein soll"
 
-#: aleksis/apps/paweljong/forms.py:149
+#: aleksis/apps/paweljong/forms.py:176
 msgid "Guardians personal data"
 msgstr "Persönliche Daten der Erziehungsberechtigten"
 
-#: aleksis/apps/paweljong/forms.py:153
+#: aleksis/apps/paweljong/forms.py:180
 msgid "Guardians contact details"
 msgstr "Kontaktdaten der Erziehungsberechtigten"
 
-#: aleksis/apps/paweljong/forms.py:159
+#: aleksis/apps/paweljong/forms.py:186
 msgid "Guardian's first name"
 msgstr "Vorname des Erziehungsberechtigten"
 
-#: aleksis/apps/paweljong/forms.py:161
+#: aleksis/apps/paweljong/forms.py:188
 msgid ""
 "Please enter the first name of the legal guardian who will fill in the "
 "registration with you and who can be reached during the event in an "
@@ -161,11 +206,11 @@ msgstr ""
 "Bitte gib den Vornamen des Erziehungsberechtigten ein, der die Anmeldung mit "
 "dir ausfüllt und in Notfällen während der Veranstaltung erreichbar ist."
 
-#: aleksis/apps/paweljong/forms.py:167
+#: aleksis/apps/paweljong/forms.py:194
 msgid "Guardian's last name"
 msgstr "Nachname des Erziehungsberechtigten"
 
-#: aleksis/apps/paweljong/forms.py:169
+#: aleksis/apps/paweljong/forms.py:196
 msgid ""
 "Please enter the last name of the legal guardian who will fill in the "
 "registration with you and who can be reached during the event in an "
@@ -174,11 +219,11 @@ msgstr ""
 "Bitte gib den Nachnamen des Erziehungsberechtigten ein, der die Anmeldung "
 "mit dir ausfüllt und in Notfällen während der Veranstaltung erreichbar ist."
 
-#: aleksis/apps/paweljong/forms.py:175
+#: aleksis/apps/paweljong/forms.py:202
 msgid "Guardian's mobile number"
 msgstr "Handynummer des Erziehungsberechtigten"
 
-#: aleksis/apps/paweljong/forms.py:177
+#: aleksis/apps/paweljong/forms.py:204
 msgid ""
 "We need the mobile phone number for emergencies if we urgently need to reach "
 "your parents during the event."
@@ -186,28 +231,16 @@ msgstr ""
 "Wir benötigen die Handynummer für Notfälle, wenn wir deine Eltern während "
 "der Veranstaltung dringend erreichen müssen."
 
-#: aleksis/apps/paweljong/forms.py:183
+#: aleksis/apps/paweljong/forms.py:210
 msgid "Guardian's email address"
 msgstr "E-Mail-Adresse des Erziehungsberechtigten"
 
-#: aleksis/apps/paweljong/forms.py:194
-msgid "Personal data"
-msgstr "Persönliche Daten"
-
-#: aleksis/apps/paweljong/forms.py:199
-msgid "Address data"
-msgstr "Addressdaten"
-
-#: aleksis/apps/paweljong/forms.py:204
-#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:72
-msgid "Contact details"
-msgstr "Kontaktdaten"
-
-#: aleksis/apps/paweljong/forms.py:208
-msgid "School details"
-msgstr "Angaben zur Schule"
+#: aleksis/apps/paweljong/forms.py:222 aleksis/apps/paweljong/models.py:42
+msgid "Name"
+msgstr "Name"
 
-#: aleksis/apps/paweljong/forms.py:214 aleksis/apps/paweljong/forms.py:459
+#: aleksis/apps/paweljong/forms.py:233 aleksis/apps/paweljong/forms.py:502
+#: aleksis/apps/paweljong/forms.py:626
 #: aleksis/apps/paweljong/templates/paweljong/print/corona.html:20
 #: aleksis/apps/paweljong/templates/paweljong/print/list_attendance.html:20
 #: aleksis/apps/paweljong/templates/paweljong/print/list_participants.html:21
@@ -215,7 +248,8 @@ msgstr "Angaben zur Schule"
 msgid "First name"
 msgstr "Vorname"
 
-#: aleksis/apps/paweljong/forms.py:219 aleksis/apps/paweljong/forms.py:460
+#: aleksis/apps/paweljong/forms.py:238 aleksis/apps/paweljong/forms.py:503
+#: aleksis/apps/paweljong/forms.py:627
 #: aleksis/apps/paweljong/templates/paweljong/print/corona.html:19
 #: aleksis/apps/paweljong/templates/paweljong/print/list_attendance.html:19
 #: aleksis/apps/paweljong/templates/paweljong/print/list_participants.html:20
@@ -223,48 +257,7 @@ msgstr "Vorname"
 msgid "Last name"
 msgstr "Nachname"
 
-#: aleksis/apps/paweljong/forms.py:224
-msgid "Street"
-msgstr "Straße"
-
-#: aleksis/apps/paweljong/forms.py:228
-msgid "Housenumber"
-msgstr "Hausnummer"
-
-#: aleksis/apps/paweljong/forms.py:232
-msgid "Postal code"
-msgstr "Postleitzahl"
-
-#: aleksis/apps/paweljong/forms.py:236
-msgid "Place"
-msgstr "Stadt"
-
-#: aleksis/apps/paweljong/forms.py:240
-#: aleksis/apps/paweljong/templates/paweljong/print/corona.html:22
-#: aleksis/apps/paweljong/templates/paweljong/print/list_participants.html:23
-msgid "Mobile number"
-msgstr "Handynummer"
-
-#: aleksis/apps/paweljong/forms.py:243
-msgid ""
-"Your mobile number helps us to reach you in an emergency during the event, e."
-"g. if you are alone with your group at a conference or similar. If you don't "
-"have a cell phone, you can leave the field blank."
-msgstr ""
-"deine Handynummer hilft uns, dich bei Notfällen während der Veranstaltung zu "
-"erreichen, z.B. wenn du alleine mit deiner Gruppe auf einer Konferenz "
-"unterwegs bist. Wenn du kein Handy hast, kannst du dieses Feld ignorieren."
-
-#: aleksis/apps/paweljong/forms.py:250 aleksis/apps/paweljong/forms.py:461
-#: aleksis/apps/paweljong/templates/paweljong/print/list_participants.html:22
-msgid "Date of birth"
-msgstr "Geburtsdatum"
-
 #: aleksis/apps/paweljong/forms.py:254
-msgid "Sex"
-msgstr "Geschlecht"
-
-#: aleksis/apps/paweljong/forms.py:256
 msgid ""
 "For various reasons, e.g. because we have to keep gender segregation during "
 "the night for legal reasons, we need to know if you are a boy or a girl."
@@ -273,11 +266,25 @@ msgstr ""
 "Geschlechtertrennung während der Nacht aufrechterhalten müssen, müssen wir "
 "wissen, ob du ein Junge oder ein Mädchen bist."
 
-#: aleksis/apps/paweljong/forms.py:264
+#: aleksis/apps/paweljong/forms.py:260
+#, fuzzy
+#| msgid "Contact information"
+msgid "Personal information"
+msgstr "Kontaktinformationen"
+
+#: aleksis/apps/paweljong/forms.py:273
+msgid "Housenumber"
+msgstr "Hausnummer"
+
+#: aleksis/apps/paweljong/forms.py:285
+msgid "Address data"
+msgstr "Addressdaten"
+
+#: aleksis/apps/paweljong/forms.py:289
 msgid "Email address"
 msgstr "E-Mail-Adresse"
 
-#: aleksis/apps/paweljong/forms.py:266
+#: aleksis/apps/paweljong/forms.py:291
 msgid ""
 "Please use your personal e-mail address here, which you will check "
 "personally. Important information will always be sent to your parents as "
@@ -287,39 +294,54 @@ msgstr ""
 "abrufst. Wichtige Informationen senden wir immer zusätzlich an deine Eltern. "
 "Bitte nutze hier nicht die E-Mail-Adresse deiner Eltern."
 
-#: aleksis/apps/paweljong/forms.py:273
+#: aleksis/apps/paweljong/forms.py:301
+msgid ""
+"Your mobile number helps us to reach you in an emergency during the event, "
+"e.g. if you are alone with your group at a conference or similar. If you "
+"don't have a cell phone, you can leave the field blank."
+msgstr ""
+"deine Handynummer hilft uns, dich bei Notfällen während der Veranstaltung zu "
+"erreichen, z.B. wenn du alleine mit deiner Gruppe auf einer Konferenz "
+"unterwegs bist. Wenn du kein Handy hast, kannst du dieses Feld ignorieren."
+
+#: aleksis/apps/paweljong/forms.py:310
+#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:72
+msgid "Contact details"
+msgstr "Kontaktdaten"
+
+#: aleksis/apps/paweljong/forms.py:315
 msgid "School"
 msgstr "Schule"
 
-#: aleksis/apps/paweljong/forms.py:274
+#: aleksis/apps/paweljong/forms.py:316
 msgid "Please enter the name of your school."
 msgstr "Bitte trage den Namen deiner Schule ein."
 
-#: aleksis/apps/paweljong/forms.py:278
+#: aleksis/apps/paweljong/forms.py:319
 msgid "School place"
 msgstr "Ort der Schule"
 
-#: aleksis/apps/paweljong/forms.py:279
+#: aleksis/apps/paweljong/forms.py:320
 msgid "Enter the place (city) where your school is located."
 msgstr "Bitte trage die Stadt ein, wo sich deine Schule befindet."
 
-#: aleksis/apps/paweljong/forms.py:283 aleksis/apps/paweljong/models.py:318
+#: aleksis/apps/paweljong/forms.py:323 aleksis/apps/paweljong/models.py:343
 msgid "School class"
 msgstr "Schulklasse"
 
-#: aleksis/apps/paweljong/forms.py:284
+#: aleksis/apps/paweljong/forms.py:324
 msgid "Please enter the class you are in (e.g. 8a)."
 msgstr "Bitte trage die Klasse ein, in die du gehst (z.B. 8a)."
 
-#: aleksis/apps/paweljong/forms.py:291 aleksis/apps/paweljong/models.py:323
+#: aleksis/apps/paweljong/forms.py:334 aleksis/apps/paweljong/models.py:348
 msgid "Medical information / intolerances"
 msgstr "Medizinische Informationen / Intoleranzen"
 
-#: aleksis/apps/paweljong/forms.py:295
+#: aleksis/apps/paweljong/forms.py:338
 msgid "Other remarks"
 msgstr "Sonstige Anmerkungen"
 
-#: aleksis/apps/paweljong/forms.py:305
+#: aleksis/apps/paweljong/forms.py:348
 msgid ""
 "If there are any medically important things we need to consider, e.g. when "
 "making food or to make sure you take prescribed medication, please enter it "
@@ -329,27 +351,27 @@ msgstr ""
 "bei der Zubereitung von Speisen oder zur Sicherstellung der Einnahme "
 "verordneter Medikamente, trage diese bitte hier ein."
 
-#: aleksis/apps/paweljong/forms.py:309
+#: aleksis/apps/paweljong/forms.py:352
 msgid "You can write down any remarks you want to tell us here."
 msgstr "Du kannst hier alles rein schreiben, was du uns mitteilen möchtest."
 
-#: aleksis/apps/paweljong/forms.py:334
+#: aleksis/apps/paweljong/forms.py:377
 msgid "Financial data"
 msgstr "Angaben zur Bezahlung"
 
-#: aleksis/apps/paweljong/forms.py:341
+#: aleksis/apps/paweljong/forms.py:384
 msgid "Voucher code"
 msgstr "Gutscheincode"
 
-#: aleksis/apps/paweljong/forms.py:342
+#: aleksis/apps/paweljong/forms.py:385
 msgid "If you have a voucher code, type it in here."
 msgstr "Wenn du einen Gutscheincode hast, gib diesen hier ein."
 
-#: aleksis/apps/paweljong/forms.py:347
+#: aleksis/apps/paweljong/forms.py:390
 msgid "Payment method"
 msgstr "Zahlungsmethode"
 
-#: aleksis/apps/paweljong/forms.py:349
+#: aleksis/apps/paweljong/forms.py:392
 msgid ""
 "Please choose a payment method. The actual payment will be made after the "
 "registration."
@@ -357,7 +379,7 @@ msgstr ""
 "Bitte wähle eine Zahlungsmethode. Die tatsächliche Zahlung wird nach der "
 "Anmeldung gemacht."
 
-#: aleksis/apps/paweljong/forms.py:374
+#: aleksis/apps/paweljong/forms.py:417
 msgid ""
 "Our association would like to offer all children and young people the "
 "opportunity to participate in our events. Sometimes, however, families "
@@ -375,18 +397,18 @@ msgstr ""
 "Spenden angewiesen. Wenn Sie für dieses Budget einen freiwilligen "
 "Zusatzbetrag spenden möchten, geben Sie dies bitte hier an."
 
-#: aleksis/apps/paweljong/forms.py:406
+#: aleksis/apps/paweljong/forms.py:449
 msgid ""
 "I confirm that the retraction of the registration is not possible anymore "
 "after {}"
 msgstr ""
 "Ich bestätige, dass eine Stornierung nach dem {} nicht mehr möglich ist"
 
-#: aleksis/apps/paweljong/forms.py:452
+#: aleksis/apps/paweljong/forms.py:495 aleksis/apps/paweljong/forms.py:619
 msgid "Account data"
 msgstr "Kontodaten"
 
-#: aleksis/apps/paweljong/forms.py:467
+#: aleksis/apps/paweljong/forms.py:509 aleksis/apps/paweljong/forms.py:634
 msgid ""
 "The username must only contain lower case letters and numbers, and must "
 "begin with a letter."
@@ -394,328 +416,342 @@ msgstr ""
 "Der Benutzername darf nur Kleinbuchstaben und Zahlen beinhalten, und muss "
 "mit einem Buchstaben beginnen."
 
-#: aleksis/apps/paweljong/forms.py:512 aleksis/apps/paweljong/forms.py:536
-#: aleksis/apps/paweljong/models.py:286 aleksis/apps/paweljong/models.py:314
+#: aleksis/apps/paweljong/forms.py:554 aleksis/apps/paweljong/forms.py:578
+#: aleksis/apps/paweljong/models.py:311 aleksis/apps/paweljong/models.py:339
 #: aleksis/apps/paweljong/tables.py:46
 msgid "Person"
 msgstr "Person"
 
-#: aleksis/apps/paweljong/forms.py:514 aleksis/apps/paweljong/forms.py:537
+#: aleksis/apps/paweljong/forms.py:556 aleksis/apps/paweljong/forms.py:579
 msgid "Please enter a username."
 msgstr "Bitte wähle einen Benutzernamen."
 
-#: aleksis/apps/paweljong/forms.py:530 aleksis/apps/paweljong/models.py:490
+#: aleksis/apps/paweljong/forms.py:572 aleksis/apps/paweljong/models.py:515
 msgid "Comment"
 msgstr "Kommentar"
 
-#: aleksis/apps/paweljong/forms.py:531
+#: aleksis/apps/paweljong/forms.py:573
 msgid "Please enter a comment describing the checkpoint (e.g. Dinner)."
 msgstr ""
 "Bitte gib einen Kommentar ein, der den Checkpoint beschreibt (z.B. "
 "Abendessen)."
 
-#: aleksis/apps/paweljong/forms.py:543
+#: aleksis/apps/paweljong/forms.py:585
 msgid "Submit geolocation"
 msgstr "Standort speichern"
 
-#: aleksis/apps/paweljong/models.py:26
+#: aleksis/apps/paweljong/models.py:27
 msgid "Boolean (Yes/No)"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:27
+#: aleksis/apps/paweljong/models.py:28
 msgid "Text (one line)"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:28 aleksis/apps/paweljong/tables.py:13
+#: aleksis/apps/paweljong/models.py:29 aleksis/apps/paweljong/tables.py:13
 msgid "Date"
 msgstr "Datum"
 
-#: aleksis/apps/paweljong/models.py:29
+#: aleksis/apps/paweljong/models.py:30
 #, fuzzy
 #| msgid "Date and time of check"
 msgid "Date and time"
 msgstr "Datum und Uhrzeit des Check-Ins"
 
-#: aleksis/apps/paweljong/models.py:30
+#: aleksis/apps/paweljong/models.py:31
 #, fuzzy
 #| msgid "Mobile number"
 msgid "Decimal number"
 msgstr "Handynummer"
 
-#: aleksis/apps/paweljong/models.py:31
+#: aleksis/apps/paweljong/models.py:32
 #, fuzzy
 #| msgid "Email address"
 msgid "E-mail address"
 msgstr "E-Mail-Adresse"
 
-#: aleksis/apps/paweljong/models.py:32
+#: aleksis/apps/paweljong/models.py:33
 msgid "Integer"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:33
+#: aleksis/apps/paweljong/models.py:34
 #, fuzzy
 #| msgid "Address"
 msgid "IP address"
 msgstr "Adresse"
 
-#: aleksis/apps/paweljong/models.py:34
+#: aleksis/apps/paweljong/models.py:35
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:35
+#: aleksis/apps/paweljong/models.py:36
 msgid "Time"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:36
+#: aleksis/apps/paweljong/models.py:37
 msgid "URL / Link"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:41
-msgid "Name"
-msgstr "Name"
-
-#: aleksis/apps/paweljong/models.py:42
+#: aleksis/apps/paweljong/models.py:43
 msgid "Colour"
 msgstr "Farbe"
 
-#: aleksis/apps/paweljong/models.py:49
+#: aleksis/apps/paweljong/models.py:50
 msgid "Title"
 msgstr "Titel"
 
-#: aleksis/apps/paweljong/models.py:50
+#: aleksis/apps/paweljong/models.py:51
 msgid "Term"
 msgstr "Bedingung"
 
-#: aleksis/apps/paweljong/models.py:51
+#: aleksis/apps/paweljong/models.py:52
 msgid "Confirmation text"
 msgstr "Bestätigungstext"
 
-#: aleksis/apps/paweljong/models.py:58
+#: aleksis/apps/paweljong/models.py:59
 msgid "subject"
 msgstr "Betreff"
 
-#: aleksis/apps/paweljong/models.py:59
+#: aleksis/apps/paweljong/models.py:60
 msgid "Text"
 msgstr "Text"
 
-#: aleksis/apps/paweljong/models.py:60
+#: aleksis/apps/paweljong/models.py:61
 msgid "Request replies to"
 msgstr "Antworten an"
 
-#: aleksis/apps/paweljong/models.py:62
+#: aleksis/apps/paweljong/models.py:63
 msgid "Mailing is active"
 msgstr "Mailing ist aktiv"
 
-#: aleksis/apps/paweljong/models.py:64
+#: aleksis/apps/paweljong/models.py:65
 msgid "Sender"
 msgstr "Absender"
 
-#: aleksis/apps/paweljong/models.py:65
+#: aleksis/apps/paweljong/models.py:66
 msgid "Send to registered person"
 msgstr "An angemeldete Personen senden"
 
-#: aleksis/apps/paweljong/models.py:66
+#: aleksis/apps/paweljong/models.py:67
 msgid "Send to guardians"
 msgstr "An Erziehungsberechtigte senden"
 
-#: aleksis/apps/paweljong/models.py:68
+#: aleksis/apps/paweljong/models.py:69
 msgid "Send to participants who retracted"
 msgstr "An Teilnehmende senden, die storniert haben"
 
-#: aleksis/apps/paweljong/models.py:71
+#: aleksis/apps/paweljong/models.py:72
 msgid "Send to participants who did not check in"
 msgstr "An Teilnehmende senden, die nicht eingechecked sind"
 
-#: aleksis/apps/paweljong/models.py:134
+#: aleksis/apps/paweljong/models.py:135
 msgid "Title of field"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:136
+#: aleksis/apps/paweljong/models.py:137
 msgid "Type of field"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:138
+#: aleksis/apps/paweljong/models.py:139
 msgid "Required"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:139
+#: aleksis/apps/paweljong/models.py:140
 msgid "Help text / description"
 msgstr ""
 
-#: aleksis/apps/paweljong/models.py:145
+#: aleksis/apps/paweljong/models.py:146
 #, fuzzy
 #| msgid "Additional fields"
 msgid "Addtitional field for events"
 msgstr "Zusätzliche Felder"
 
-#: aleksis/apps/paweljong/models.py:146
+#: aleksis/apps/paweljong/models.py:147
 #, fuzzy
 #| msgid "Additional fields"
 msgid "Addtitional fields for events"
 msgstr "Zusätzliche Felder"
 
-#: aleksis/apps/paweljong/models.py:153
+#: aleksis/apps/paweljong/models.py:154
 msgid "Display name"
 msgstr "Anzeigename"
 
-#: aleksis/apps/paweljong/models.py:157
+#: aleksis/apps/paweljong/models.py:158
 msgid "Description"
 msgstr "Beschreibung"
 
-#: aleksis/apps/paweljong/models.py:158
+#: aleksis/apps/paweljong/models.py:159
 msgid "Publish"
 msgstr "Veröffentlichen"
 
-#: aleksis/apps/paweljong/models.py:160
+#: aleksis/apps/paweljong/models.py:161
 msgid "Slug"
 msgstr "Slug"
 
-#: aleksis/apps/paweljong/models.py:163
+#: aleksis/apps/paweljong/models.py:164
 msgid "Date of event"
 msgstr "Datum der Veranstaltung"
 
-#: aleksis/apps/paweljong/models.py:164
+#: aleksis/apps/paweljong/models.py:165
 msgid "Registration deadline"
 msgstr "Anmeldungsschluss"
 
-#: aleksis/apps/paweljong/models.py:165
+#: aleksis/apps/paweljong/models.py:166
 msgid "Retraction deadline"
 msgstr "Kündigungsfrist"
 
-#: aleksis/apps/paweljong/models.py:168 aleksis/apps/paweljong/models.py:349
+#: aleksis/apps/paweljong/models.py:169 aleksis/apps/paweljong/models.py:374
 msgid "Cost in €"
 msgstr "Kosten in €"
 
-#: aleksis/apps/paweljong/models.py:169
+#: aleksis/apps/paweljong/models.py:170
+#, fuzzy
+#| msgid "Cost in €"
+msgid "Minimum cost in €"
+msgstr "Kosten in €"
+
+#: aleksis/apps/paweljong/models.py:171
+#, fuzzy
+#| msgid "Cost in €"
+msgid "Maximum cost in €"
+msgstr "Kosten in €"
+
+#: aleksis/apps/paweljong/models.py:172
 msgid "Maximum participants"
 msgstr "Maximale Teilnehmerzahl"
 
-#: aleksis/apps/paweljong/models.py:170
+#: aleksis/apps/paweljong/models.py:173
 msgid "Information about the event"
 msgstr "Informationen über die Veranstaltung"
 
-#: aleksis/apps/paweljong/models.py:181
+#: aleksis/apps/paweljong/models.py:184
 #: aleksis/apps/paweljong/templates/paweljong/event/detail.html:80
 msgid "Additional fields"
 msgstr "Zusätzliche Felder"
 
-#: aleksis/apps/paweljong/models.py:263
+#: aleksis/apps/paweljong/models.py:203
+#, fuzzy
+#| msgid "Contact information"
+msgid "Contact information fields to be shown."
+msgstr "Kontaktinformationen"
+
+#: aleksis/apps/paweljong/models.py:288
 msgid "Sent to persons"
 msgstr "An Personen gesendet"
 
-#: aleksis/apps/paweljong/models.py:272 aleksis/apps/paweljong/models.py:273
+#: aleksis/apps/paweljong/models.py:297 aleksis/apps/paweljong/models.py:298
 #: aleksis/apps/paweljong/templates/paweljong/voucher/list.html:6
 #: aleksis/apps/paweljong/templates/paweljong/voucher/list.html:7
 msgid "Vouchers"
 msgstr "Gutscheine"
 
-#: aleksis/apps/paweljong/models.py:279 aleksis/apps/paweljong/models.py:312
+#: aleksis/apps/paweljong/models.py:304 aleksis/apps/paweljong/models.py:337
 #: aleksis/apps/paweljong/tables.py:12 aleksis/apps/paweljong/tables.py:43
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:4
 msgid "Event"
 msgstr "Veranstaltung"
 
-#: aleksis/apps/paweljong/models.py:295
+#: aleksis/apps/paweljong/models.py:320
 msgid "Used by"
 msgstr "Benutzt von"
 
-#: aleksis/apps/paweljong/models.py:315
+#: aleksis/apps/paweljong/models.py:340
 msgid "Registration date"
 msgstr "Anmeldedatum"
 
-#: aleksis/apps/paweljong/models.py:317
+#: aleksis/apps/paweljong/models.py:342
 msgid "Name of school"
 msgstr "Name der Schule"
 
-#: aleksis/apps/paweljong/models.py:319
+#: aleksis/apps/paweljong/models.py:344
 msgid "Place of the school"
 msgstr "Ort der Schule"
 
-#: aleksis/apps/paweljong/models.py:321
+#: aleksis/apps/paweljong/models.py:346
 msgid "Comment / remarks"
 msgstr "Kommentar / Anmerkungen"
 
-#: aleksis/apps/paweljong/models.py:328
+#: aleksis/apps/paweljong/models.py:353
 msgid "Voucher"
 msgstr "Gutschein"
 
-#: aleksis/apps/paweljong/models.py:332
+#: aleksis/apps/paweljong/models.py:357
 msgid "Donation"
 msgstr "Spende"
 
-#: aleksis/apps/paweljong/models.py:335
+#: aleksis/apps/paweljong/models.py:360
 msgid "Accepted terms"
 msgstr "Akzeptierte Bedingungen"
 
-#: aleksis/apps/paweljong/models.py:340
+#: aleksis/apps/paweljong/models.py:365
 msgid "States"
 msgstr "Status"
 
-#: aleksis/apps/paweljong/models.py:343
+#: aleksis/apps/paweljong/models.py:368
 msgid "Retracted"
 msgstr "Storniert"
 
-#: aleksis/apps/paweljong/models.py:344
+#: aleksis/apps/paweljong/models.py:369
 msgid "Retracted at"
 msgstr "Storniert am"
 
-#: aleksis/apps/paweljong/models.py:346
+#: aleksis/apps/paweljong/models.py:371
 msgid "Checked in"
 msgstr "Eingechecked"
 
-#: aleksis/apps/paweljong/models.py:347
+#: aleksis/apps/paweljong/models.py:372
 msgid "Checked in at"
 msgstr "Eingechecked am"
 
-#: aleksis/apps/paweljong/models.py:357 aleksis/apps/paweljong/views.py:1028
+#: aleksis/apps/paweljong/models.py:382 aleksis/apps/paweljong/views.py:1030
 msgid "Person is already checked in!"
 msgstr "Person ist bereits eingechecked!"
 
-#: aleksis/apps/paweljong/models.py:394
+#: aleksis/apps/paweljong/models.py:419
 msgid "Participation of {} in event {}"
 msgstr "Teilnahme von {} bei Veranstaltung {}"
 
-#: aleksis/apps/paweljong/models.py:423
+#: aleksis/apps/paweljong/models.py:448
 msgid "Social Sponsoring / Extra Donation"
 msgstr "Social-Sponsoring / Spende"
 
-#: aleksis/apps/paweljong/models.py:434
+#: aleksis/apps/paweljong/models.py:459
 msgid "Voucher / Granted discount"
 msgstr "Rabatt"
 
-#: aleksis/apps/paweljong/models.py:464 aleksis/apps/paweljong/views.py:416
+#: aleksis/apps/paweljong/models.py:489 aleksis/apps/paweljong/views.py:428
 msgid "Event registration"
 msgstr "Veranstaltungsanmeldung"
 
-#: aleksis/apps/paweljong/models.py:465
+#: aleksis/apps/paweljong/models.py:490
 msgid "Event registrations"
 msgstr "Veranstaltungsanmeldungen"
 
-#: aleksis/apps/paweljong/models.py:475
+#: aleksis/apps/paweljong/models.py:500
 msgid "Related event"
 msgstr "Zugehörige Veranstaltung"
 
-#: aleksis/apps/paweljong/models.py:479
+#: aleksis/apps/paweljong/models.py:504
 msgid "Checked person"
 msgstr "Eingecheckte Person"
 
-#: aleksis/apps/paweljong/models.py:485
+#: aleksis/apps/paweljong/models.py:510
 msgid "Checked by person"
 msgstr "Eingechecked von person"
 
-#: aleksis/apps/paweljong/models.py:492
+#: aleksis/apps/paweljong/models.py:517
 msgid "Date and time of check"
 msgstr "Datum und Uhrzeit des Check-Ins"
 
-#: aleksis/apps/paweljong/models.py:494
+#: aleksis/apps/paweljong/models.py:519
 msgid "Latitude of check"
 msgstr "Breitengrad des Check-Ins"
 
-#: aleksis/apps/paweljong/models.py:497
+#: aleksis/apps/paweljong/models.py:522
 msgid "Longitude of check"
 msgstr "Längengrad des Check-Ins"
 
-#: aleksis/apps/paweljong/models.py:505
+#: aleksis/apps/paweljong/models.py:530
 msgid "Can generate lists of participants of a group"
 msgstr ""
 
@@ -727,6 +763,51 @@ msgstr "Paweljong"
 msgid "Mail recipient for event notifications"
 msgstr "Mail-Empfänger für Veranstaltungsbenachrichtigungen"
 
+#: aleksis/apps/paweljong/preferences.py:32
+msgid "Name of client linked to invoices for events."
+msgstr ""
+
+#: aleksis/apps/paweljong/preferences.py:43
+msgid "Name of invoice group used for events."
+msgstr ""
+
+#: aleksis/apps/paweljong/schema/event_registration.py:58
+msgid "Retraction consent is required"
+msgstr "Zustimmung zum Rücknahme-Hinweis erforderlich."
+
+#: aleksis/apps/paweljong/schema/event_registration.py:68
+msgid "Mail domain does not exist."
+msgstr "Die gewünschte E-Mail-Domain existiert nicht."
+
+#: aleksis/apps/paweljong/schema/event_registration.py:75
+msgid "Mail address already in use."
+msgstr "Die gewünschte E-Mail-Adresse ist bereits in Benutzung."
+
+#: aleksis/apps/paweljong/schema/event_registration.py:89
+msgid "A user with this username or e-mail already exists."
+msgstr "Ein Account mit diesem Namen oder der E-Mail-Adresse exisiert bereits."
+
+#: aleksis/apps/paweljong/schema/event_registration.py:106
+#: aleksis/apps/paweljong/schema/event_registration.py:139
+#, python-format
+msgid "A person using the e-mail address %s already exists."
+msgstr "Eine Person mit der E-Mail-Adresse %s existiert bereits."
+
+#: aleksis/apps/paweljong/schema/event_registration.py:177
+msgid "The field 'Amount paid by you' is required."
+msgstr "Das Feld 'Von dir gezahlter Betrag' ist verpflichtend."
+
+#: aleksis/apps/paweljong/schema/event_registration.py:218
+#: aleksis/apps/paweljong/views.py:710
+msgid "You registered for an event"
+msgstr "Du hast dich zu einer Veranstaltung angemeldet"
+
+#: aleksis/apps/paweljong/schema/event_registration.py:219
+#: aleksis/apps/paweljong/views.py:711
+#, python-format
+msgid "You registered for the event %s"
+msgstr "Du hast dich zur Veranstaltung %s angemeldet"
+
 #: aleksis/apps/paweljong/tables.py:14
 msgid "Max. participants"
 msgstr "Maximale Teilnehmerzahl"
@@ -871,7 +952,6 @@ msgid "Edit event"
 msgstr "Veranstaltung bearbeiten"
 
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
-#: aleksis/apps/paweljong/templates/paweljong/event/full.html:60
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
 #: aleksis/apps/paweljong/templates/paweljong/register_start.html:57
@@ -879,7 +959,7 @@ msgstr "Veranstaltung bearbeiten"
 msgid "Register now"
 msgstr "Jetzt anmelden"
 
-#: aleksis/apps/paweljong/templates/paweljong/event/full.html:62
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:60
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:85
 msgid "Not available"
 msgstr "Nicht verfügbar"
@@ -890,7 +970,7 @@ msgid "Events"
 msgstr "Veranstaltungen"
 
 #: aleksis/apps/paweljong/templates/paweljong/event/list.html:10
-#: aleksis/apps/paweljong/views.py:788
+#: aleksis/apps/paweljong/views.py:790
 msgid "Upcoming events"
 msgstr "Anstehende Veranstaltungen"
 
@@ -1173,24 +1253,24 @@ msgstr "Akzeptiert"
 msgid "No checked in yet."
 msgstr "Noch nicht eingechecked."
 
-#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:241
+#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:242
 msgid "Invoice details"
 msgstr "Rechnungsinformationen"
 
-#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:247
+#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:248
 msgid "Billing information"
 msgstr "Zahlungsinformationen"
 
-#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:279
-#: aleksis/apps/paweljong/views.py:445
+#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:280
+#: aleksis/apps/paweljong/views.py:457
 msgid "Payment"
 msgstr "Bezahlung"
 
-#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:310
+#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:311
 msgid "Mark payed"
 msgstr "Als bezahlt markieren"
 
-#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:323
+#: aleksis/apps/paweljong/templates/paweljong/event_registration/full.html:325
 msgid "Guardians / Parents "
 msgstr "Erziehungsberechtigte / Eltern "
 
@@ -1407,41 +1487,41 @@ msgstr "Die Veranstaltungsanmeldung wurde gespeichert."
 msgid "The registration has been saved."
 msgstr "Die Anmeldung wurde gespeichert."
 
-#: aleksis/apps/paweljong/views.py:235
+#: aleksis/apps/paweljong/views.py:236
 msgid "The registration has been deleted."
 msgstr "Die Veranstaltung wurde gelöscht."
 
-#: aleksis/apps/paweljong/views.py:247
+#: aleksis/apps/paweljong/views.py:248
 msgid "The voucher has been created."
 msgstr "Der Gutschein wurde erstellt."
 
-#: aleksis/apps/paweljong/views.py:259
+#: aleksis/apps/paweljong/views.py:260
 msgid "The voucher has been saved."
 msgstr "Der Gutschein wurde gespeichert."
 
-#: aleksis/apps/paweljong/views.py:269
+#: aleksis/apps/paweljong/views.py:270
 msgid "The voucher has been deleted."
 msgstr "Der Gutschein wurde gelöscht."
 
-#: aleksis/apps/paweljong/views.py:407
+#: aleksis/apps/paweljong/views.py:419
 msgid "Create e-mail address"
 msgstr "E-Mail-Adresse erstellen"
 
-#: aleksis/apps/paweljong/views.py:409
+#: aleksis/apps/paweljong/views.py:421
 msgid ""
 "All participants need a personal e-mail address, which they check and read "
 "temselves. We offer the possibility to register an e-mail address on our "
 "secure servers, made for young users. For information about receiving mails, "
-"see: <a href='https://wiki.teckids.org/de/Dienste/E-Mail'>https://wiki."
-"teckids.org/de/Dienste/E-Mail</a>."
+"see: <a href='https://wiki.teckids.org/de/Dienste/E-Mail'>https://"
+"wiki.teckids.org/de/Dienste/E-Mail</a>."
 msgstr ""
 "Alle Teilnehmenden brauchen eine persönliche Adresse, die sie selbst abrufen "
 "und lesen. Wir bieten die Möglichkeit an, E-Mail-Adressen auf unseren "
 "sicheren Servern zu registrieren. Informationen, wie Du deine Mails abrufen "
-"kannst, findest Du unter <a href='https://wiki.institute/pages/services."
-"html'>https://wiki.teckids.org/de/Dienste/E-Mail</a>."
+"kannst, findest Du unter <a href='https://wiki.institute/pages/"
+"services.html'>https://wiki.teckids.org/de/Dienste/E-Mail</a>."
 
-#: aleksis/apps/paweljong/views.py:418
+#: aleksis/apps/paweljong/views.py:430
 msgid ""
 "First, please enter some basic information about yourself, and check whether "
 "all information is correct."
@@ -1449,11 +1529,11 @@ msgstr ""
 "Zuerst, gib bitte einige Informationen über dich an und prüfe, dass alle "
 "Informationen korrekt sind."
 
-#: aleksis/apps/paweljong/views.py:422
+#: aleksis/apps/paweljong/views.py:434
 msgid "Contact information"
 msgstr "Kontaktinformationen"
 
-#: aleksis/apps/paweljong/views.py:424
+#: aleksis/apps/paweljong/views.py:436
 msgid ""
 "Tell us how we can contact you. You will receive information about the event "
 "by e-mail. Please use your personal e-mail address where you will read mails "
@@ -1467,11 +1547,11 @@ msgstr ""
 "senden wichtige Informationen immer zusätzlich an deine Eltern. Ihre E-Mail-"
 "Adresse wirst du im nächsten Schritt eingeben."
 
-#: aleksis/apps/paweljong/views.py:431
+#: aleksis/apps/paweljong/views.py:443
 msgid "Legal guardians / parents"
 msgstr "Erziehungsberechtigte / Eltern"
 
-#: aleksis/apps/paweljong/views.py:433
+#: aleksis/apps/paweljong/views.py:445
 msgid ""
 "Tell us how we can reach your parents or other legal guardians. This should "
 "be the person who was present when you registered for the event (which is "
@@ -1483,11 +1563,11 @@ msgstr ""
 "Erziehungsberechtigten angeben möchtest, kannst du uns das später als "
 "Kommentar mitteilen."
 
-#: aleksis/apps/paweljong/views.py:439
+#: aleksis/apps/paweljong/views.py:451
 msgid "Additional registration information"
 msgstr "Zusätzliche Anmeldungsinformationen"
 
-#: aleksis/apps/paweljong/views.py:441
+#: aleksis/apps/paweljong/views.py:453
 msgid ""
 "Please answer the following questions as precisely as you can, so we can "
 "make sure your event attendance will be organised as wel las possible."
@@ -1496,7 +1576,7 @@ msgstr ""
 "sicher gehen können, dass deine Teilnahme bei der Veranstaltung so gut wie "
 "möglich organisiert ist."
 
-#: aleksis/apps/paweljong/views.py:447
+#: aleksis/apps/paweljong/views.py:459
 msgid ""
 "Please decide with your parents how you want to pay. In this step, you only "
 "select a payment method. The real payment will be done in a separate step, "
@@ -1506,11 +1586,11 @@ msgstr ""
 "wird nur die Zahlungsmethode ausgewählt. Die eigentliche Zahlung wird in "
 "einem separaten Schritt nach der Anmeldung durchgeführt."
 
-#: aleksis/apps/paweljong/views.py:452
+#: aleksis/apps/paweljong/views.py:464
 msgid "Consent"
 msgstr "Einverständnis"
 
-#: aleksis/apps/paweljong/views.py:454
+#: aleksis/apps/paweljong/views.py:466
 msgid ""
 "Lastly, please read the terms and conditions carefully, together with your "
 "parents. After that, you will need to confirm that you agree with everything "
@@ -1520,11 +1600,11 @@ msgstr ""
 "durch. Danach musst du bestätigen, dass du und deine Eltern alles gelesen "
 "habt und akzeptiert."
 
-#: aleksis/apps/paweljong/views.py:568 aleksis/apps/paweljong/views.py:678
+#: aleksis/apps/paweljong/views.py:677
 msgid "You entered an invalid voucher code!"
 msgstr "Du hast einen ungütigen Gutscheincode eingegeben!"
 
-#: aleksis/apps/paweljong/views.py:704
+#: aleksis/apps/paweljong/views.py:703
 msgid ""
 "You have successfully registered for the event. Please give us up to two "
 "days to process your registration. You will then receive an email from us."
@@ -1533,60 +1613,53 @@ msgstr ""
 "paar Tage, um deine Anmeldung zu bearbeiten. Du wirst dann eine E-Mail von "
 "uns bekommen."
 
-#: aleksis/apps/paweljong/views.py:711
-msgid "You registered for an event"
-msgstr "Du hast dich zu einer Veranstaltung angemeldet"
-
-#: aleksis/apps/paweljong/views.py:712
-#, python-format
-msgid "You registered for the event %s"
-msgstr "Du hast dich zur Veranstaltung %s angemeldet"
-
-#: aleksis/apps/paweljong/views.py:769 aleksis/apps/paweljong/views.py:917
+#: aleksis/apps/paweljong/views.py:771 aleksis/apps/paweljong/views.py:919
 msgid "The term has been created."
 msgstr "Die Bedingung wurde erfolgreich erstellt."
 
-#: aleksis/apps/paweljong/views.py:781 aleksis/apps/paweljong/views.py:929
+#: aleksis/apps/paweljong/views.py:783 aleksis/apps/paweljong/views.py:931
 msgid "The term has been saved."
 msgstr "Die Bedingung wurde gespeichert."
 
-#: aleksis/apps/paweljong/views.py:797
+#: aleksis/apps/paweljong/views.py:799
 msgid "Announcement feed of all upcoming events"
 msgstr "Ankündigungs-Feed aller bevorstehenden Veranstaltungen"
 
-#: aleksis/apps/paweljong/views.py:837
+#: aleksis/apps/paweljong/views.py:839
 msgid "The info mailing has been created."
 msgstr "Das Info-Mailing wurde erstellt."
 
-#: aleksis/apps/paweljong/views.py:849
+#: aleksis/apps/paweljong/views.py:851
 msgid "The info mailing has been saved."
 msgstr "Das Info-Mailing wurde gespeichert."
 
-#: aleksis/apps/paweljong/views.py:859
+#: aleksis/apps/paweljong/views.py:861
 msgid "The info mailing has been deleted."
 msgstr "Das Info-Mailing wurde gelöscht."
 
-#: aleksis/apps/paweljong/views.py:942
+#: aleksis/apps/paweljong/views.py:944
 msgid "Registration successfully retracted."
 msgstr "Anmeldung erfolgreich storniert."
 
-#: aleksis/apps/paweljong/views.py:997
-#, python-brace-format
-msgid "Person {person} added successfully!"
-msgstr "Person {person} erfolgreich hinzugefügt!"
-
-#: aleksis/apps/paweljong/views.py:999
+#: aleksis/apps/paweljong/views.py:1001
 msgid "Person does not exist!"
 msgstr "Person existiert nicht!"
 
-#: aleksis/apps/paweljong/views.py:1026
+#: aleksis/apps/paweljong/views.py:1028
 msgid "Successfully checked in."
 msgstr "Erfolgreich eingechecked."
 
-#: aleksis/apps/paweljong/views.py:1046
+#: aleksis/apps/paweljong/views.py:1048
 msgid "Successfully marked as payed!"
 msgstr "Erfolgreich als bezahlt markiert!"
 
+#~ msgid "Personal data"
+#~ msgstr "Persönliche Daten"
+
+#, python-brace-format
+#~ msgid "Person {person} added successfully!"
+#~ msgstr "Person {person} erfolgreich hinzugefügt!"
+
 #~ msgid "New account"
 #~ msgstr "Neues Konto"
 
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 4990414..94eed05 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -103,7 +103,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
                 },
             )
         except IntegrityError:
-            raise ValidationError(_("A person with using the e-mail address %s already exists." % email))
+            raise ValidationError(_("A person using the e-mail address %s already exists." % email))
 
         # Store contact information in database
         for field_name in ["date_of_birth", "sex", "address", "mobile_number"]:
@@ -136,7 +136,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
                             email=guardian_data["email"],
                         )
                     except IntegrityError:
-                        raise ValidationError(_("A person with using the e-mail address %s already exists." % guardian_data["email"]))
+                        raise ValidationError(_("A person using the e-mail address %s already exists." % guardian_data["email"]))
 
                     person.guardians.add(guardian)
             person.save()
-- 
GitLab


From 11da31522ed2cb7145c795353f3d810576655084 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 23:41:33 +0100
Subject: [PATCH 46/72] Fix translation usage

---
 aleksis/apps/paweljong/schema/event_registration.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 94eed05..aa8af41 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -103,7 +103,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
                 },
             )
         except IntegrityError:
-            raise ValidationError(_("A person using the e-mail address %s already exists." % email))
+            raise ValidationError(_("A person using the e-mail address %s already exists.") % email)
 
         # Store contact information in database
         for field_name in ["date_of_birth", "sex", "address", "mobile_number"]:
@@ -136,7 +136,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
                             email=guardian_data["email"],
                         )
                     except IntegrityError:
-                        raise ValidationError(_("A person using the e-mail address %s already exists." % guardian_data["email"]))
+                        raise ValidationError(_("A person using the e-mail address %s already exists.") % guardian_data["email"])
 
                     person.guardians.add(guardian)
             person.save()
-- 
GitLab


From 9efd95b2de9fd69c086743a17a87658a67fcd8b1 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 14 Feb 2025 23:49:37 +0100
Subject: [PATCH 47/72] Validate guardian email

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 1e11e39..76de8db 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -385,7 +385,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                               v-model="guardian.email"
                               :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
                               required
-                              :rules="index === 0 ? $rules().required.build() : []"
+                              :rules="index === 0 ? $rules().required.build(rules.email) : []"
                             ></v-text-field>
                           </div>
                         </v-col>
-- 
GitLab


From b1a6c7645bb3294b729f36f4c511e8960e273bdb Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 10:55:29 +0100
Subject: [PATCH 48/72] Add new translations for registrration step

---
 aleksis/apps/paweljong/frontend/messages/de.json | 7 ++++---
 aleksis/apps/paweljong/frontend/messages/en.json | 7 ++++---
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/messages/de.json b/aleksis/apps/paweljong/frontend/messages/de.json
index 40f51ca..c988899 100644
--- a/aleksis/apps/paweljong/frontend/messages/de.json
+++ b/aleksis/apps/paweljong/frontend/messages/de.json
@@ -62,9 +62,10 @@
                     "email": {
                         "title": "E-Mail-Adresse",
                         "choose_mode": {
-                        "help_text": "Um fortzufahren, musst du eine E-Mail-Adresse angeben. Du kannst entweder eine neue AlekSIS-E-Mail-Adresse registrieren oder deine eigene, bestehende E-Mail-Adresse verwenden.",
-                        "continue_aleksis": "Mit neuer AlekSIS-E-Mail-Adresse fortfahren",
-                        "continue_own": "Mit eigener E-Mail-Adresse fortfahren"
+                            "help_text": " Jeder Teilnehmende benötigt seine eigene E-Mail-Adresse. Diese Adresse muss eine Adresse sein, die du selbst abrufst und liest, und die nicht deinen Eltern gehört. Wichtige Informationen senden wir natürlich immer zusätzlich an deine Eltern. Natürlich solltest du mit deinen Eltern immer reden, falls du beim Lesen deiner E-Mails Fragen oder Sorgen hast.  Wenn du bisher keine eigene E-Mail-Adresse hast, und keine bei den \"großen\" Anbietern anlegen kannst oder möchtest (denke daran, dass Google, Outlook etc. Accounts erst ab 16 erlauben, und oft deine Daten sammeln und analysieren), kannst du Dir bei uns eine E-Mail-Adresse registrieren.",
+                            "continue_aleksis": "Ich habe keine persönliche E-Mail-Adresse",
+                            "continue_own": "Ich habe eine persönliche E-Mail-Adresse",
+                            "continue_existing_account": "Ich habe bereits ein Konto"
                         },
                         "fields": {
                         "email": {
diff --git a/aleksis/apps/paweljong/frontend/messages/en.json b/aleksis/apps/paweljong/frontend/messages/en.json
index 298588c..60f85b8 100644
--- a/aleksis/apps/paweljong/frontend/messages/en.json
+++ b/aleksis/apps/paweljong/frontend/messages/en.json
@@ -62,9 +62,10 @@
           "email": {
             "title": "E-Mail address",
             "choose_mode": {
-              "help_text": "To continue, you'll need to provide an e-mail address. You can choose between registering an e-mail address with AlekSIS and using your own, existing e-mail address.",
-              "continue_aleksis": "Continue using new AlekSIS e-mail address",
-              "continue_own": "Continue using own e-mail address"
+              "help_text": "Every person needs a personal e-mail address. This address must be one that you, the participating child, check and read yourself, not one owned by your parents. If you do not have a personal e-mail address yet, and do not want or cannot register one with one of the \"big\" providers (keep in mind that Google, Outlook, etc. are allowed from 16 years only, and often collect and analyse your private mail), you can register an e-mail address with us.",
+              "continue_aleksis": "I don't have a personal email address",
+              "continue_own": "I have a personal email address",
+              "continue_existing_account": "I already have an account"
             },
             "fields": {
               "email": {
-- 
GitLab


From 0efc4d450b93b42693cde9824dd9667a8ac2c3f3 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 11:01:56 +0100
Subject: [PATCH 49/72] Add back button for email mode selection

---
 .../event_registration/EventRegistrationForm.vue      | 11 +++++++++++
 aleksis/apps/paweljong/frontend/messages/de.json      |  3 ++-
 aleksis/apps/paweljong/frontend/messages/en.json      |  3 ++-
 3 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 76de8db..3febf48 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -93,6 +93,17 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </div>
                     </v-col>
                   </v-row>
+                  <v-card-actions>
+                    <v-row>
+                      <v-col>
+                        <secondary-action-button
+                          @click="emailMode = null"
+                          i18n-key="paweljong.event_registration.form.steps.email.choose_mode.back"
+                        />
+                      </v-col>
+                      <v-spacer />
+                    </v-row>
+                  </v-card-actions>
                 </v-form>
                 <template v-else>
                   <v-card-text>
diff --git a/aleksis/apps/paweljong/frontend/messages/de.json b/aleksis/apps/paweljong/frontend/messages/de.json
index c988899..90c35b3 100644
--- a/aleksis/apps/paweljong/frontend/messages/de.json
+++ b/aleksis/apps/paweljong/frontend/messages/de.json
@@ -65,7 +65,8 @@
                             "help_text": " Jeder Teilnehmende benötigt seine eigene E-Mail-Adresse. Diese Adresse muss eine Adresse sein, die du selbst abrufst und liest, und die nicht deinen Eltern gehört. Wichtige Informationen senden wir natürlich immer zusätzlich an deine Eltern. Natürlich solltest du mit deinen Eltern immer reden, falls du beim Lesen deiner E-Mails Fragen oder Sorgen hast.  Wenn du bisher keine eigene E-Mail-Adresse hast, und keine bei den \"großen\" Anbietern anlegen kannst oder möchtest (denke daran, dass Google, Outlook etc. Accounts erst ab 16 erlauben, und oft deine Daten sammeln und analysieren), kannst du Dir bei uns eine E-Mail-Adresse registrieren.",
                             "continue_aleksis": "Ich habe keine persönliche E-Mail-Adresse",
                             "continue_own": "Ich habe eine persönliche E-Mail-Adresse",
-                            "continue_existing_account": "Ich habe bereits ein Konto"
+                            "continue_existing_account": "Ich habe bereits ein Konto",
+                            "back": "Zurück"
                         },
                         "fields": {
                         "email": {
diff --git a/aleksis/apps/paweljong/frontend/messages/en.json b/aleksis/apps/paweljong/frontend/messages/en.json
index 60f85b8..ade9ba5 100644
--- a/aleksis/apps/paweljong/frontend/messages/en.json
+++ b/aleksis/apps/paweljong/frontend/messages/en.json
@@ -65,7 +65,8 @@
               "help_text": "Every person needs a personal e-mail address. This address must be one that you, the participating child, check and read yourself, not one owned by your parents. If you do not have a personal e-mail address yet, and do not want or cannot register one with one of the \"big\" providers (keep in mind that Google, Outlook, etc. are allowed from 16 years only, and often collect and analyse your private mail), you can register an e-mail address with us.",
               "continue_aleksis": "I don't have a personal email address",
               "continue_own": "I have a personal email address",
-              "continue_existing_account": "I already have an account"
+              "continue_existing_account": "I already have an account",
+              "back": "Go back"
             },
             "fields": {
               "email": {
-- 
GitLab


From 2fbc200ea9b752eb1ce3fbc36607956a2ea105e3 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 11:02:56 +0100
Subject: [PATCH 50/72] Align mail mode select buttons to right

---
 .../components/event_registration/EventRegistrationForm.vue      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 3febf48..c8a892f 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -111,6 +111,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-card-text>
                   <v-card-actions>
                     <v-row>
+                      <v-spacer />
                       <v-col>
                         <primary-action-button
                           @click="emailMode = 'postbuero'"
-- 
GitLab


From ea6dc72a96952bba06781b048fd6ea365076be80 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 11:03:52 +0100
Subject: [PATCH 51/72] Make mobile number field optional

---
 .../event_registration/EventRegistrationForm.vue | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index c8a892f..7766ed5 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -246,16 +246,12 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </div>
                     </v-col>
                     <v-col v-if="isFieldVisible('mobile_number')">
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.mobileNumber"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
-                          required
-                          :rules="$rules().required.build()"
-                          prepend-icon="mdi-phone-outline"
-                        ></v-text-field>
-                      </div>
+                      <v-text-field
+                        outlined
+                        v-model="data.person.mobileNumber"
+                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
+                        prepend-icon="mdi-phone-outline"
+                      ></v-text-field>
                     </v-col>
                   </v-row>
                   <v-row>
-- 
GitLab


From 7daaa3db9b4b05767a822a905ceb33b6457eec70 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 11:16:02 +0100
Subject: [PATCH 52/72] Disable payment methods field when 0 is entered as
 amount

---
 .../components/event_registration/EventRegistrationForm.vue    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 7766ed5..0044ec1 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -510,7 +510,8 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
                           persistent-hint
                           required
-                          :rules="$rules().required.build()"
+                          :disabled="data.payment.amount === 0"
+                          :rules="data.payment.amount === 0 ? [] : $rules().required.build()"
                         ></v-select>
                       </div>
                     </v-col>
-- 
GitLab


From 2999ed394498550e8eea939df09f2d1f89775b34 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 12:07:53 +0100
Subject: [PATCH 53/72] Remove unneeded watch

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 --
 1 file changed, 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 0044ec1..febebeb 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -667,8 +667,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
       },
     },
     mixins: [eventAdditionalFieldMixin, formRulesMixin],
-    watch: {
-    },
     methods: {
       setStep(step) {
         this.step = step;
-- 
GitLab


From 2148f26c3efdd7b1502a1f1c1421666e18593a39 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 12:44:48 +0100
Subject: [PATCH 54/72] Add configurable explaination texts

---
 .../EventRegistrationForm.vue                 | 1144 +++++++++--------
 .../event_registration/helpers.graphql        |   22 +
 .../apps/paweljong/frontend/messages/de.json  |    4 +
 .../apps/paweljong/frontend/messages/en.json  |    4 +
 aleksis/apps/paweljong/preferences.py         |  191 ++-
 aleksis/apps/paweljong/schema/__init__.py     |    7 +
 .../apps/paweljong/schema/site_preferences.py |   70 +
 7 files changed, 889 insertions(+), 553 deletions(-)
 create mode 100644 aleksis/apps/paweljong/schema/site_preferences.py

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index febebeb..1014097 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -32,585 +32,602 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <v-divider v-if="index + 1 < steps.length" ></v-divider>
             </template>
           </v-stepper-header>
-          <v-stepper-items>
-            <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
-              <div class="mb-4">
-                <v-form v-if="emailMode" v-model="validationStatuses['email']">
-                  <v-row v-if="emailMode == 'postbuero'">
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.email.localPart"
-                          :label="$t('postbuero.mail_addresses.data_table.local_part')"
-                          :rules="$rules().required.build(rules.emailLocalPart)"
-                          required
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-autocomplete
-                          outlined
-                          hide-no-data
-                          :items="mailDomains"
-                          item-text="domain"
-                          item-value="id"
-                          :loading="$apollo.queries.mailDomains.loading"
-                          prepend-icon="mdi-at"
-                          v-model="data.email.domain"
-                          :label="$t('postbuero.mail_addresses.data_table.domain')"
-                          required
-                          :rules="$rules().required.build()"
-                        />
-                      </div>
-                    </v-col>
-                  </v-row>
-                  <v-row v-else-if="emailMode == 'own'">
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.user.email"
-                          :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
-                          required
-                          :rules="$rules().required.build(rules.email)"
-                          prepend-icon="mdi-email-outline"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.user.confirmEmail"
-                          :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
-                          required
-                          :rules="$rules().required.build(rules.confirmEmail)"
-                          prepend-icon="mdi-email-outline"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                  </v-row>
-                  <v-card-actions>
+            <v-stepper-items>
+              <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
+                <div class="mb-4">
+                  <v-form v-if="emailMode" v-model="validationStatuses['email']">
+                    <v-row v-if="emailMode == 'postbuero'">
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.email.localPart"
+                            :label="$t('postbuero.mail_addresses.data_table.local_part')"
+                            :rules="$rules().required.build(rules.emailLocalPart)"
+                            required
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-autocomplete
+                            outlined
+                            hide-no-data
+                            :items="mailDomains"
+                            item-text="domain"
+                            item-value="id"
+                            :loading="$apollo.queries.mailDomains.loading"
+                            prepend-icon="mdi-at"
+                            v-model="data.email.domain"
+                            :label="$t('postbuero.mail_addresses.data_table.domain')"
+                            required
+                            :rules="$rules().required.build()"
+                          />
+                        </div>
+                      </v-col>
+                    </v-row>
+                    <v-row v-else-if="emailMode == 'own'">
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.user.email"
+                            :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
+                            required
+                            :rules="$rules().required.build(rules.email)"
+                            prepend-icon="mdi-email-outline"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.user.confirmEmail"
+                            :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
+                            required
+                            :rules="$rules().required.build(rules.confirmEmail)"
+                            prepend-icon="mdi-email-outline"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                    </v-row>
+                    <v-card-actions>
+                      <v-row>
+                        <v-col>
+                          <secondary-action-button
+                            @click="emailMode = null"
+                            i18n-key="paweljong.event_registration.form.steps.email.choose_mode.back"
+                          />
+                        </v-col>
+                        <v-spacer />
+                      </v-row>
+                    </v-card-actions>
+                  </v-form>
+                  <template v-else>
+                    <v-card-text>
+                      {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
+                    </v-card-text>
+                    <v-card-actions>
+                      <v-row justify="end">
+                        <v-col>
+                          <primary-action-button
+                            @click="emailMode = 'postbuero'"
+                            i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis"
+                          />
+                        </v-col>
+                        <v-col>
+                          <primary-action-button
+                            @click="emailMode = 'own'"
+                            i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_own"
+                          />
+                        </v-col>
+                      </v-row>
+                    </v-card-actions>
+                  </template>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['email']"
+                />
+              </v-stepper-content>
+
+              <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
+                <div class="mb-4">
+                  <v-form v-model="validationStatuses['register']">
                     <v-row>
                       <v-col>
-                        <secondary-action-button
-                          @click="emailMode = null"
-                          i18n-key="paweljong.event_registration.form.steps.email.choose_mode.back"
-                        />
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.person.firstName"
+                            :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.person.lastName"
+                            :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
                       </v-col>
-                      <v-spacer />
                     </v-row>
-                  </v-card-actions>
-                </v-form>
-                <template v-else>
-                  <v-card-text>
-                    {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
-                  </v-card-text>
-                  <v-card-actions>
                     <v-row>
-                      <v-spacer />
                       <v-col>
-                        <primary-action-button
-                          @click="emailMode = 'postbuero'"
-                          i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis"
-                        />
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.user.username"
+                            :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
+                            required
+                            :rules="$rules().required.build()"
+                            prepend-icon="mdi-account-outline"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.user.password"
+                            :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
+                            required
+                            :rules="$rules().required.build()"
+                            type="password"
+                            prepend-icon="mdi-form-textbox-password"
+                          ></v-text-field>
+                        </div>
                       </v-col>
                       <v-col>
-                        <primary-action-button
-                          @click="emailMode = 'own'"
-                          i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_own"
-                        />
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.user.confirmPassword"
+                            :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
+                            required
+                            :rules="$rules().required.build(rules.confirmPassword)"
+                            type="password"
+                            prepend-icon="mdi-form-textbox-password"
+                          ></v-text-field>
+                        </div>
                       </v-col>
                     </v-row>
-                  </v-card-actions>
-                </template>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['email']"
-              />
-            </v-stepper-content>
+                  </v-form>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['register']"
+                />
+              </v-stepper-content>
 
-            <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
-              <div class="mb-4">
-                <v-form v-model="validationStatuses['register']">
-                  <v-row>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.firstName"
-                          :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.lastName"
-                          :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                  </v-row>
-                  <v-row>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.user.username"
-                          :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
-                          required
-                          :rules="$rules().required.build()"
-                          prepend-icon="mdi-account-outline"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
+              <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
+                <div class="mb-4">
+                  <v-form v-model="validationStatuses['contact_details']">
+                    <v-row>
+                      <v-col v-if="isFieldVisible('date_of_birth')">
+                        <div aria-required="true">
+                          <date-field
+                            outlined
+                            v-model="data.person.dateOfBirth"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
+                            required
+                            :rules="$rules().required.build()"
+                            prepend-icon="mdi-cake-variant-outline"
+                          />
+                        </div>
+                      </v-col>
+                      <v-col v-if="isFieldVisible('sex')">
+                        <div aria-required="true">
+                          <!-- FIXME: Prefilling data does not work due to upper-/lowercase situation; will be fixed with core person form refactoring -->
+                          <sex-select
+                            outlined
+                            v-model="data.person.sex"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
+                            persistent-hint
+                            required
+                            :rules="$rules().required.build()"
+                          />
+                        </div>
+                      </v-col>
+                      <v-col v-if="isFieldVisible('mobile_number')">
                         <v-text-field
                           outlined
-                          v-model="data.user.password"
-                          :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
-                          required
-                          :rules="$rules().required.build()"
-                          type="password"
-                          prepend-icon="mdi-form-textbox-password"
+                          v-model="data.person.mobileNumber"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
+                          prepend-icon="mdi-phone-outline"
                         ></v-text-field>
-                      </div>
-                    </v-col>
+                      </v-col>
+                    </v-row>
+                    <v-row>
+                      <v-col v-if="isFieldVisible('street')">
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.person.address.street"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col v-if="isFieldVisible('housenumber')">
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.person.address.housenumber"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                    </v-row>
+                    <v-row>
+                      <v-col v-if="isFieldVisible('postal_code')">
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.person.address.postalCode"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col v-if="isFieldVisible('place')">
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.person.address.place"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                    </v-row>
+                    <v-row v-if="isFieldVisible('school_details')">
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.school"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.schoolPlace"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-text-field
+                            outlined
+                            v-model="data.schoolClass"
+                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
+                            required
+                            :rules="$rules().required.build()"
+                          ></v-text-field>
+                        </div>
+                      </v-col>
+                    </v-row>
+                  </v-form>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['contact_details']"
+                />
+              </v-stepper-content>
+
+              <v-stepper-content :step="getStepIndex('guardians')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
+                <div class="mb-4">
+                  <v-form  v-model="validationStatuses['guardians']">
+                    <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
+                      <v-card-title>
+                        {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
+                      </v-card-title>
+                      <v-card-text>
+                        <v-row>
+                          <v-col>
+                            <div :aria-required="index === 0 ? 'true' : 'false'">
+                              <v-text-field
+                                outlined
+                                v-model="guardian.firstName"
+                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
+                                :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
+                                required
+                                :rules="index === 0 ? $rules().required.build() : []"
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                          <v-col>
+                            <div :aria-required="index === 0 ? 'true' : 'false'">
+                              <v-text-field
+                                outlined
+                                v-model="guardian.lastName"
+                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
+                                :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
+                                required
+                                :rules="index === 0 ? $rules().required.build() : []"
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                        </v-row>
+                        <v-row>
+                          <v-col>
+                            <div :aria-required="index === 0 ? 'true' : 'false'">
+                              <v-text-field
+                                outlined
+                                v-model="guardian.email"
+                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
+                                required
+                                :rules="index === 0 ? $rules().required.build(rules.email) : []"
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                          <v-col>
+                            <div :aria-required="index === 0 ? 'true' : 'false'">
+                              <v-text-field
+                                outlined
+                                v-model="guardian.mobileNumber"
+                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
+                                :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
+                                required
+                                :rules="index === 0 ? $rules().required.build() : []"
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                        </v-row>
+                      </v-card-text>
+                    </v-card>
+                  </v-form>
+                  <v-row class="mb-4">
                     <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.user.confirmPassword"
-                          :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
-                          required
-                          :rules="$rules().required.build(rules.confirmPassword)"
-                          type="password"
-                          prepend-icon="mdi-form-textbox-password"
-                        ></v-text-field>
-                      </div>
+                      <v-spacer />
+                      <secondary-action-button
+                        @click="addGuardian"
+                        i18n-key="paweljong.event_registration.form.steps.guardians.add"
+                      >
+                      </secondary-action-button>
                     </v-col>
                   </v-row>
-                </v-form>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['register']"
-              />
-            </v-stepper-content>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['guardians']"
+                />
+              </v-stepper-content>
 
-            <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
-              <div class="mb-4">
-                <v-form v-model="validationStatuses['contact_details']">
-                  <v-row>
-                    <v-col v-if="isFieldVisible('date_of_birth')">
-                      <div aria-required="true">
-                        <date-field
-                          outlined
-                          v-model="data.person.dateOfBirth"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
-                          required
-                          :rules="$rules().required.build()"
-                          prepend-icon="mdi-cake-variant-outline"
-                        />
-                      </div>
-                    </v-col>
-                    <v-col v-if="isFieldVisible('sex')">
-                      <div aria-required="true">
-                        <!-- FIXME: Prefilling data does not work due to upper-/lowercase situation; will be fixed with core person form refactoring -->
-                        <sex-select
-                          outlined
-                          v-model="data.person.sex"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
-                          persistent-hint
-                          required
-                          :rules="$rules().required.build()"
-                        />
-                      </div>
-                    </v-col>
-                    <v-col v-if="isFieldVisible('mobile_number')">
-                      <v-text-field
-                        outlined
-                        v-model="data.person.mobileNumber"
-                        :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
-                        prepend-icon="mdi-phone-outline"
-                      ></v-text-field>
-                    </v-col>
-                  </v-row>
-                  <v-row>
-                    <v-col v-if="isFieldVisible('street')">
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.address.street"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col v-if="isFieldVisible('housenumber')">
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.address.housenumber"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                  </v-row>
-                  <v-row>
-                    <v-col v-if="isFieldVisible('postal_code')">
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.address.postalCode"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col v-if="isFieldVisible('place')">
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.address.place"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                  </v-row>
-                  <v-row v-if="isFieldVisible('school_details')">
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.school"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-text-field
-                          outlined
-                          v-model="data.schoolPlace"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
-                          required
-                          :rules="$rules().required.build()"
-                        ></v-text-field>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
+              <v-stepper-content :step="getStepIndex('additional')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
+                <div class="mb-4">
+                  <v-form  v-model="validationStatuses['additional']">
+                    <v-row>
+                      <v-col>
                         <v-text-field
                           outlined
-                          v-model="data.schoolClass"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
-                          required
-                          :rules="$rules().required.build()"
+                          v-model="data.medicalInformation"
+                          :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
+                          :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
                         ></v-text-field>
-                      </div>
-                    </v-col>
-                  </v-row>
-                </v-form>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['contact_details']"
-              />
-            </v-stepper-content>
+                      </v-col>
+                    </v-row>
+                    <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
+                      <v-col>
+                        <div :aria-required="additionalField.required ? 'true' : 'false'">
+                          <component
+                            :is="fieldComponentForAdditionalField(additionalField)"
+                            v-model="data.additionalFields[additionalField.id]"
+                            outlined
+                            :label="additionalField.title"
+                            :hint="additionalField.helpText"
+                            persistent-hint
+                            :required="additionalField.required"
+                            :rules="additionalField.required ? $rules().required.build() : []"
+                          />
+                        </div>
+                      </v-col>
+                    </v-row>
+                  </v-form>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['additional']"
+                />
+              </v-stepper-content>
 
-            <v-stepper-content :step="getStepIndex('guardians')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
-              <div class="mb-4">
-                <v-form  v-model="validationStatuses['guardians']">
-                  <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
+              <v-stepper-content v-if="isStepEnabled('financial')" :step="getStepIndex('financial')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('financial')) }}</h2>
+                <div class="mb-4">
+                  <message-box type="info" class="mb-4">
+                    {{ $t("paweljong.event_registration.form.steps.financial.help_text.cost_info") }}
+                  </message-box>
+                  <v-simple-table class="mb-4">
+                    <template v-slot:default>
+                      <tbody>
+                        <tr>
+                          <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.min_cost") }}</td>
+                          <td>{{ `${event.minCost} €` }}</td>
+                        </tr>
+                        <tr>
+                          <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.cost") }}</td>
+                          <td>{{ `${event.cost} €` }}</td>
+                        </tr>
+                        <tr v-if="event.maxCost">
+                          <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.max_cost") }}</td>
+                          <td>{{ `${event.maxCost} €` }}</td>
+                        </tr>
+                      </tbody>
+                    </template>
+                  </v-simple-table>
+                  <v-form v-model="validationStatuses['financial']">
+                    <v-row>
+                      <v-col>
+                        <div aria-required="true">
+                          <v-select
+                            :items="paweljongPaymentChoices"
+                            item-text="text"
+                            item-value="variant"
+                            outlined
+                            v-model="data.payment.paymentMethod"
+                            :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
+                            :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
+                            persistent-hint
+                            required
+                            :disabled="data.payment.amount === 0"
+                            :rules="data.payment.amount === 0 ? [] : $rules().required.build()"
+                          ></v-select>
+                        </div>
+                      </v-col>
+                      <v-col>
+                        <div aria-required="true">
+                          <positive-small-integer-field
+                            outlined
+                            v-model="data.payment.amount"
+                            :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
+                            :rules="$rules().build(rules.amount)"
+                          />
+                        </div>
+                      </v-col>
+                    </v-row>
+                  </v-form>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['financial']"
+                />
+              </v-stepper-content>
+
+              <v-stepper-content :step="getStepIndex('consent')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('consent')) }}</h2>
+                <div class="mb-4">
+                  <v-card v-for="term in event.terms" :key="`term-card-${term.id}`">
                     <v-card-title>
-                      {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
+                      {{ term.title }}
                     </v-card-title>
-                    <v-card-text>
-                      <v-row>
-                        <v-col>
-                          <div :aria-required="index === 0 ? 'true' : 'false'">
-                            <v-text-field
-                              outlined
-                              v-model="guardian.firstName"
-                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
-                              :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
-                              required
-                              :rules="index === 0 ? $rules().required.build() : []"
-                            ></v-text-field>
-                          </div>
-                        </v-col>
-                        <v-col>
-                          <div :aria-required="index === 0 ? 'true' : 'false'">
-                            <v-text-field
-                              outlined
-                              v-model="guardian.lastName"
-                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
-                              :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
-                              required
-                              :rules="index === 0 ? $rules().required.build() : []"
-                            ></v-text-field>
-                          </div>
-                        </v-col>
-                      </v-row>
-                      <v-row>
-                        <v-col>
-                          <div :aria-required="index === 0 ? 'true' : 'false'">
-                            <v-text-field
-                              outlined
-                              v-model="guardian.email"
-                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
-                              required
-                              :rules="index === 0 ? $rules().required.build(rules.email) : []"
-                            ></v-text-field>
-                          </div>
-                        </v-col>
-                        <v-col>
-                          <div :aria-required="index === 0 ? 'true' : 'false'">
-                            <v-text-field
-                              outlined
-                              v-model="guardian.mobileNumber"
-                              :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
-                              :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
-                              required
-                              :rules="index === 0 ? $rules().required.build() : []"
-                            ></v-text-field>
-                          </div>
-                        </v-col>
-                      </v-row>
-                    </v-card-text>
+                    <v-card-text v-html="term.term" />
                   </v-card>
-                </v-form>
-                <v-row class="mb-4">
-                  <v-col>
-                    <v-spacer />
-                    <secondary-action-button
-                      @click="addGuardian"
-                      i18n-key="paweljong.event_registration.form.steps.guardians.add"
-                    >
-                    </secondary-action-button>
-                  </v-col>
-                </v-row>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['guardians']"
-              />
-            </v-stepper-content>
+                  <v-form v-model="validationStatuses['consent']">
+                    <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
+                      <v-checkbox
+                        required
+                        :rules="$rules().required.build()"
+                        :label="term.confirmationText"
+                        v-model="data.terms[term.id]"
+                      />
+                    </div>
+                    <div aria-required="true">
+                      <v-checkbox
+                        v-if="event.dateRetraction"
+                        required
+                        :rules="$rules().required.build()"
+                        :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
+                        v-model="data.retractionConsent"
+                      />
+                    </div>
+                  </v-form>
+                </div>
+                <v-divider class="mb-4" />
+                <control-row
+                  :step="step"
+                  @set-step="setStep"
+                  :next-disabled="!validationStatuses['consent']"
+                />
+              </v-stepper-content>
+    
+              <v-stepper-content :step="getStepIndex('confirm')">
+                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('confirm')) }}</h2>
+                <div class="mb-4">
+                  <v-text-field
+                    outlined
+                    v-model="data.comment"
+                    :label="$t('paweljong.event_registration.form.steps.confirm.fields.comment.label')"
+                  ></v-text-field>
+                </div>
+                <v-divider class="my-4" />
 
-            <v-stepper-content :step="getStepIndex('additional')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
-              <div class="mb-4">
-                <v-form  v-model="validationStatuses['additional']">
-                  <v-row>
-                    <v-col>
-                      <v-text-field
-                        outlined
-                        v-model="data.medicalInformation"
-                        :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
-                        :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
-                      ></v-text-field>
-                    </v-col>
-                  </v-row>
-                  <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
-                    <v-col>
-                      <div :aria-required="additionalField.required ? 'true' : 'false'">
-                        <component
-                          :is="fieldComponentForAdditionalField(additionalField)"
-                          v-model="data.additionalFields[additionalField.id]"
-                          outlined
-                          :label="additionalField.title"
-                          :hint="additionalField.helpText"
-                          persistent-hint
-                          :required="additionalField.required"
-                          :rules="additionalField.required ? $rules().required.build() : []"
-                        />
-                      </div>
-                    </v-col>
-                  </v-row>
-                </v-form>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['additional']"
-              />
-            </v-stepper-content>
-
-            <v-stepper-content v-if="isStepEnabled('financial')" :step="getStepIndex('financial')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('financial')) }}</h2>
-              <div class="mb-4">
+                <!-- TODO: Add summary -->
+    
                 <message-box type="info" class="mb-4">
-                  {{ $t("paweljong.event_registration.form.steps.financial.help_text.cost_info") }}
+                  {{ $t("paweljong.event_registration.form.steps.confirm.hint") }}
                 </message-box>
-                <v-simple-table class="mb-4">
-                  <template v-slot:default>
-                    <tbody>
-                      <tr>
-                        <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.min_cost") }}</td>
-                        <td>{{ `${event.minCost} €` }}</td>
-                      </tr>
-                      <tr>
-                        <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.cost") }}</td>
-                        <td>{{ `${event.cost} €` }}</td>
-                      </tr>
-                      <tr v-if="event.maxCost">
-                        <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.max_cost") }}</td>
-                        <td>{{ `${event.maxCost} €` }}</td>
-                      </tr>
-                    </tbody>
-                  </template>
-                </v-simple-table>
-                <v-form v-model="validationStatuses['financial']">
-                  <v-row>
-                    <v-col>
-                      <div aria-required="true">
-                        <v-select
-                          :items="paweljongPaymentChoices"
-                          item-text="text"
-                          item-value="variant"
-                          outlined
-                          v-model="data.payment.paymentMethod"
-                          :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
-                          persistent-hint
-                          required
-                          :disabled="data.payment.amount === 0"
-                          :rules="data.payment.amount === 0 ? [] : $rules().required.build()"
-                        ></v-select>
-                      </div>
-                    </v-col>
-                    <v-col>
-                      <div aria-required="true">
-                        <positive-small-integer-field
-                          outlined
-                          v-model="data.payment.amount"
-                          :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
-                          :rules="$rules().build(rules.amount)"
-                        />
-                      </div>
-                    </v-col>
-                  </v-row>
-                </v-form>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['financial']"
-              />
-            </v-stepper-content>
-
-            <v-stepper-content :step="getStepIndex('consent')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('consent')) }}</h2>
-              <div class="mb-4">
-                <v-card v-for="term in event.terms" :key="`term-card-${term.id}`">
-                  <v-card-title>
-                    {{ term.title }}
-                  </v-card-title>
-                  <v-card-text v-html="term.term" />
-                </v-card>
-                <v-form v-model="validationStatuses['consent']">
-                  <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
-                    <v-checkbox
-                      required
-                      :rules="$rules().required.build()"
-                      :label="term.confirmationText"
-                      v-model="data.terms[term.id]"
-                    />
-                  </div>
-                  <div aria-required="true">
-                    <v-checkbox
-                      v-if="event.dateRetraction"
-                      required
-                      :rules="$rules().required.build()"
-                      :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
-                      v-model="data.retractionConsent"
+                <ApolloMutation
+                  :mutation="require('./eventRegistrationMutation.graphql')"
+                  :variables="{
+                    event: event.id,
+                    eventRegistration: dataForSubmit,
+                  }"
+                  @done="eventRegistrationDone"
+                >
+                  <template #default="{ mutate, loading, error }">
+                    <control-row
+                      :step="step"
+                      final-step
+                      @set-step="setStep"
+                      @confirm="mutate"
+                      :next-loading="loading"
                     />
-                  </div>
-                </v-form>
-              </div>
-              <v-divider class="mb-4" />
-              <control-row
-                :step="step"
-                @set-step="setStep"
-                :next-disabled="!validationStatuses['consent']"
-              />
-            </v-stepper-content>
-  
-            <v-stepper-content :step="getStepIndex('confirm')">
-              <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('confirm')) }}</h2>
-              <div class="mb-4">
-                <v-text-field
-                  outlined
-                  v-model="data.comment"
-                  :label="$t('paweljong.event_registration.form.steps.confirm.fields.comment.label')"
-                ></v-text-field>
-              </div>
-              <v-divider class="my-4" />
-
-              <!-- TODO: Add summary -->
-  
-              <message-box type="info" class="mb-4">
-                {{ $t("paweljong.event_registration.form.steps.confirm.hint") }}
-              </message-box>
-              <ApolloMutation
-                :mutation="require('./eventRegistrationMutation.graphql')"
-                :variables="{
-                  event: event.id,
-                  eventRegistration: dataForSubmit,
-                }"
-                @done="eventRegistrationDone"
-              >
-                <template #default="{ mutate, loading, error }">
-                  <control-row
-                    :step="step"
-                    final-step
-                    @set-step="setStep"
-                    @confirm="mutate"
-                    :next-loading="loading"
-                  />
-                  <v-alert v-if="error" type="error" outlined>{{ error.message }}</v-alert>
-                </template>
-              </ApolloMutation>
-            </v-stepper-content>
-          </v-stepper-items>
-        </v-stepper>
+                    <v-alert v-if="error" type="error" outlined>{{ error.message }}</v-alert>
+                  </template>
+                </ApolloMutation>
+              </v-stepper-content>
+            </v-stepper-items>
+          </v-stepper>
+          <v-row v-if="currentParticipantHelpText || currentGuardianHelpText">
+            <v-col v-if="currentParticipantHelpText" cols="12" md="6">
+              <v-card>
+                <v-card-title>
+                  {{ $t("paweljong.event_registration.form.help_text.participant") }}
+                </v-card-title>
+                <v-card-text>{{ currentParticipantHelpText }}</v-card-text>
+              </v-card>
+            </v-col>
+            <v-col v-if="currentGuardianHelpText" cols="12" md="6">
+              <v-card>
+                <v-card-title>
+                  {{ $t("paweljong.event_registration.form.help_text.guardian") }}
+                </v-card-title>
+                <v-card-text>{{ currentGuardianHelpText }}</v-card-text>
+              </v-card>
+            </v-col>
+          </v-row>
       </div>
       <div v-else-if="$apollo.queries.event.loading">
         <v-skeleton-loader type="heading, text, actions" />
@@ -620,7 +637,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   
   <script>
   import { eventBySlug } from "../event/events.graphql";
-  import { gqlPaweljongPaymentChoices, whoAmI } from "./helpers.graphql";
+  import { gqlPaweljongPaymentChoices, gqlPaweljongSitePreferences, whoAmI } from "./helpers.graphql";
   import {
     mailDomainsForUser,
     disallowedLocalParts,
@@ -641,6 +658,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
         },
       },
       paweljongPaymentChoices: gqlPaweljongPaymentChoices,
+      paweljongSitePreferences: gqlPaweljongSitePreferences,
       mailDomains: mailDomainsForUser,
       disallowedLocalParts: disallowedLocalParts,
       whoAmI: {
@@ -776,37 +794,59 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           ... (this.$root.whoAmI.isAnonymous) ? [{
             name: "email",
             titleKey: "paweljong.event_registration.form.steps.email.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationEmailParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationEmailGuardianHelpText,
           }] : [],
           ... (this.$root.whoAmI.isAnonymous) ? [{
             name: "register",
             titleKey: "paweljong.event_registration.form.steps.register.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationRegisterParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationRegisterGuardianHelpText,
           }] : [],
           ... (this.event?.contactInformationVisibleFields.length) ? [{
             name: "contact_details",
             titleKey: "paweljong.event_registration.form.steps.contact_details.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationContactDetailsParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationContactDetailsGuardianHelpText,
           }] : [],
           {
             name: "guardians",
             titleKey: "paweljong.event_registration.form.steps.guardians.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationGuardiansParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationGuardiansGuardianHelpText,
           },
           {
             name: "additional",
             titleKey: "paweljong.event_registration.form.steps.additional.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationAdditionalParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationAdditionalGuardianHelpText,
           },
           ... (this.event.cost !== 0 && this.event.maxCost !== 0) ? [{
             name: "financial",
             titleKey: "paweljong.event_registration.form.steps.financial.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationFinancialParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationFinancialGuardianHelpText,
           }] : [],
           {
             name: "consent",
             titleKey: "paweljong.event_registration.form.steps.consent.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationConsentParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationConsentGuardianHelpText,
           },
           {
             name: "confirm",
             titleKey: "paweljong.event_registration.form.steps.confirm.title",
+            participantHelpText: this.paweljongSitePreferences?.eventRegistrationConfirmParticipantHelpText,
+            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationConfirmGuardianHelpText,
           },
         ];
-      }
+      },
+      currentParticipantHelpText() {
+        return this.steps[this.step - 1].participantHelpText;
+      },
+      currentGuardianHelpText() {
+        return this.steps[this.step - 1].guardianHelpText;
+      },
     },
     data() {
       return {
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
index b077505..a61c153 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
@@ -29,3 +29,25 @@ query gqlPaweljongPaymentChoices {
     text
   }
 }
+
+query gqlPaweljongSitePreferences {
+  paweljongSitePreferences {
+    eventRegistrationEmailParticipantHelpText
+    eventRegistrationRegisterParticipantHelpText
+    eventRegistrationContactDetailsParticipantHelpText
+    eventRegistrationGuardiansParticipantHelpText
+    eventRegistrationAdditionalParticipantHelpText
+    eventRegistrationFinancialParticipantHelpText
+    eventRegistrationConsentParticipantHelpText
+    eventRegistrationConfirmParticipantHelpText
+
+    eventRegistrationEmailGuardianHelpText
+    eventRegistrationRegisterGuardianHelpText
+    eventRegistrationContactDetailsGuardianHelpText
+    eventRegistrationGuardiansGuardianHelpText
+    eventRegistrationAdditionalGuardianHelpText
+    eventRegistrationFinancialGuardianHelpText
+    eventRegistrationConsentGuardianHelpText
+    eventRegistrationConfirmGuardianHelpText 
+  }
+}
diff --git a/aleksis/apps/paweljong/frontend/messages/de.json b/aleksis/apps/paweljong/frontend/messages/de.json
index 90c35b3..de2254f 100644
--- a/aleksis/apps/paweljong/frontend/messages/de.json
+++ b/aleksis/apps/paweljong/frontend/messages/de.json
@@ -233,6 +233,10 @@
                         "too_high": "Der Betrag ist höher als der maximal zu zahlende Betrag.",
                         "too_low": "Der Betrag ist niedriger als der minimal zu zahlende Betrag."
                     }
+                },
+                "help_text": {
+                    "guardian": "Für Erziehungsberechtigte",
+                    "participant": "Für Teilnehmende"
                 }
             }
         }
diff --git a/aleksis/apps/paweljong/frontend/messages/en.json b/aleksis/apps/paweljong/frontend/messages/en.json
index ade9ba5..84c89b9 100644
--- a/aleksis/apps/paweljong/frontend/messages/en.json
+++ b/aleksis/apps/paweljong/frontend/messages/en.json
@@ -233,6 +233,10 @@
             "too_high": "The amount is higher than the maximal payable amount.",
             "too_low": "The amount is lower than the minimal payable amount."
           }
+        },
+        "help_text": {
+          "guardian": "For guardians",
+          "participant": "For participants"
         }
       }
     }
diff --git a/aleksis/apps/paweljong/preferences.py b/aleksis/apps/paweljong/preferences.py
index b0b21a8..f42a0d0 100644
--- a/aleksis/apps/paweljong/preferences.py
+++ b/aleksis/apps/paweljong/preferences.py
@@ -3,7 +3,7 @@ from django.forms import EmailField
 from django.utils.translation import gettext_lazy as _
 
 from dynamic_preferences.preferences import Section
-from dynamic_preferences.types import StringPreference
+from dynamic_preferences.types import LongStringPreference, StringPreference
 
 from aleksis.core.registries import site_preferences_registry
 
@@ -42,3 +42,192 @@ class EventInvoiceGroupName(StringPreference):
     default = "Hack'n'Fun-Veranstaltungen"
     verbose_name = _("Name of invoice group used for events.")
     required = True
+
+
+@site_preferences_registry.register
+class EventRegistrationEmailParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the email step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_email_participant_help_text"
+    verbose_name = _("Participant help text for email step in event registration wizard.")
+    default = ""
+    required = False
+
+
+@site_preferences_registry.register
+class EventRegistrationRegisterParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the account registration step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_register_participant_help_text"
+    verbose_name = _("Participant help text for account registration step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationContactDetailsParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the contact details step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_contact_details_participant_help_text"
+    verbose_name = _("Participant help text for contact details step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationGuardiansParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the guardians step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_guardians_participant_help_text"
+    verbose_name = _("Participant help text for guardians step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationAdditionalParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the additional information step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_additional_participant_help_text"
+    verbose_name = _("Participant help text for additional information step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationFinancialParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the financial step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_financial_participant_help_text"
+    verbose_name = _("Participant help text for financial step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationConsentParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the consent step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_consent_participant_help_text"
+    verbose_name = _("Participant help text for consent step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationConfirmParticipantHelpText(LongStringPreference):
+    """Help text to be displayed to participants during the confirmation step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_confirm_participant_help_text"
+    verbose_name = _("Participant help text for confirmation step in event registration wizard.")
+    default = ""
+    required = False
+
+
+@site_preferences_registry.register
+class EventRegistrationEmailGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the email step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_email_guardian_help_text"
+    verbose_name = _("Guardian help text for email step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationRegisterGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the account registration step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_register_guardian_help_text"
+    verbose_name = _("Guardian help text for account registration step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationContactDetailsGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the contact details step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_contact_details_guardian_help_text"
+    verbose_name = _("Guardian help text for contact details step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationGuardiansGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the guardians step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_guardians_guardian_help_text"
+    verbose_name = _("Guardian help text for guardians step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationAdditionalGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the additional information step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_additional_guardian_help_text"
+    verbose_name = _("Guardian help text for additional information step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationFinancialGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the financial step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_financial_guardian_help_text"
+    verbose_name = _("Guardian help text for financial step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationConsentGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the consent step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_consent_guardian_help_text"
+    verbose_name = _("Guardian help text for consent step in event registration wizard.")
+    default = ""
+    required = False
+
+
+
+@site_preferences_registry.register
+class EventRegistrationConfirmGuardianHelpText(LongStringPreference):
+    """Help text to be displayed to guardians during the confirmation step of the event registration wizard."""
+
+    section = paweljong
+    name = "event_registration_confirm_guardian_help_text"
+    verbose_name = _("Guardian help text for confirmation step in event registration wizard.")
+    default = ""
+    required = False
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
index 75a300f..ae907c9 100644
--- a/aleksis/apps/paweljong/schema/__init__.py
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -20,6 +20,7 @@ from .event_additional_field import (
     EventAdditionalFieldType,
 )
 from .event_registration import SendEventRegistrationMutation
+from .site_preferences import PaweljongSitePreferencesType
 from .terms import TermsType
 
 
@@ -33,6 +34,8 @@ class Query(graphene.ObjectType):
 
     paweljong_payment_choices = graphene.List(PaymentVariantChoiceType)
 
+    paweljong_site_preferences = graphene.Field(PaweljongSitePreferencesType)
+
     @staticmethod
     def resolve_event_by_id(root, info, id):
         event = Event.objects.get(pk=id)
@@ -60,6 +63,10 @@ class Query(graphene.ObjectType):
 
         return group.get_variant_choices()
 
+    @staticmethod
+    def resolve_paweljong_site_preferences(root, info, **kwargs):
+        return get_site_preferences()
+
 
 class Mutation(graphene.ObjectType):
     checkpoint_check_in = CheckpointCheckInMutation.Field()
diff --git a/aleksis/apps/paweljong/schema/site_preferences.py b/aleksis/apps/paweljong/schema/site_preferences.py
new file mode 100644
index 0000000..cddf3d5
--- /dev/null
+++ b/aleksis/apps/paweljong/schema/site_preferences.py
@@ -0,0 +1,70 @@
+import graphene
+
+
+class PaweljongSitePreferencesType(graphene.ObjectType):
+    event_registration_email_participant_help_text = graphene.String()
+    event_registration_register_participant_help_text = graphene.String()
+    event_registration_contact_details_participant_help_text = graphene.String()
+    event_registration_guardians_participant_help_text = graphene.String()
+    event_registration_additional_participant_help_text = graphene.String()
+    event_registration_financial_participant_help_text = graphene.String()
+    event_registration_consent_participant_help_text = graphene.String()
+    event_registration_confirm_participant_help_text = graphene.String()
+
+    event_registration_email_guardian_help_text = graphene.String()
+    event_registration_register_guardian_help_text = graphene.String()
+    event_registration_contact_details_guardian_help_text = graphene.String()
+    event_registration_guardians_guardian_help_text = graphene.String()
+    event_registration_additional_guardian_help_text = graphene.String()
+    event_registration_financial_guardian_help_text = graphene.String()
+    event_registration_consent_guardian_help_text = graphene.String()
+    event_registration_confirm_guardian_help_text = graphene.String()
+
+
+    def resolve_event_registration_email_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_email_participant_help_text"]
+
+    def resolve_event_registration_register_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_register_participant_help_text"]
+
+    def resolve_event_registration_contact_details_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_contact_details_participant_help_text"]
+
+    def resolve_event_registration_guardians_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_guardians_participant_help_text"]
+
+    def resolve_event_registration_additional_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_additional_participant_help_text"]
+
+    def resolve_event_registration_financial_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_financial_participant_help_text"]
+
+    def resolve_event_registration_consent_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_consent_participant_help_text"]
+
+    def resolve_event_registration_confirm_participant_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_confirm_participant_help_text"]
+
+    def resolve_event_registration_email_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_email_guardian_help_text"]
+
+    def resolve_event_registration_register_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_register_guardian_help_text"]
+
+    def resolve_event_registration_contact_details_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_contact_details_guardian_help_text"]
+
+    def resolve_event_registration_guardians_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_guardians_guardian_help_text"]
+
+    def resolve_event_registration_additional_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_additional_guardian_help_text"]
+
+    def resolve_event_registration_financial_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_financial_guardian_help_text"]
+
+    def resolve_event_registration_consent_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_consent_guardian_help_text"]
+
+    def resolve_event_registration_confirm_guardian_help_text(parent, info, **kwargs):
+        return parent["paweljong__event_registration_confirm_guardian_help_text"]
-- 
GitLab


From 49a8d306e2b603f7f98b8177804d9cb9c943b672 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 12:51:03 +0100
Subject: [PATCH 55/72] Use payment pledge as variant when cost is zero

---
 aleksis/apps/paweljong/models.py                    | 7 ++++++-
 aleksis/apps/paweljong/schema/__init__.py           | 7 ++++++-
 aleksis/apps/paweljong/schema/event_registration.py | 7 ++++++-
 3 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 1d1f87d..7d79af0 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -397,7 +397,12 @@ class EventRegistration(ExtensibleModel):
 
     def get_invoice(self):
         # FIXME Maybe do not hard-code this
-        client, __ = Client.objects.get_or_create(name=get_site_preferences()["paweljong__event_invoice_client_name"])
+        client, __ = Client.objects.get_or_create(
+            name=get_site_preferences()["paweljong__event_invoice_client_name"],
+            defaults={
+                "pledge_enabled": True,
+            },
+        )
         group, __ = InvoiceGroup.objects.get_or_create(
             name=get_site_preferences()["paweljong__event_invoice_group_name"],
             client=client,
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
index ae907c9..6cd371e 100644
--- a/aleksis/apps/paweljong/schema/__init__.py
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -52,7 +52,12 @@ class Query(graphene.ObjectType):
 
     @staticmethod
     def resolve_paweljong_payment_choices(root, info):
-        client, __ = Client.objects.get_or_create(name=get_site_preferences()["paweljong__event_invoice_client_name"])
+        client, __ = Client.objects.get_or_create(
+            name=get_site_preferences()["paweljong__event_invoice_client_name"],
+            defaults={
+                "pledge_enabled": True,
+            },
+        )
         group, __ = InvoiceGroup.objects.get_or_create(
             name=get_site_preferences()["paweljong__event_invoice_group_name"],
             client=client,
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index aa8af41..7df9296 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -192,7 +192,12 @@ class SendEventRegistrationMutation(graphene.Mutation):
             # TODO Implement existing voucher handling
 
             invoice = registration.get_invoice()
-            invoice.variant = event_registration["payment"]["payment_method"]
+            if amount == 0:
+                invoice.variant = "pledge"
+            else:
+                invoice.variant = event_registration["payment"]["payment_method"]
+                if invoice.variant == "" or invoice.variant is None:
+                    raise ValidationError(_("The field 'Payment method' is required."))
             invoice.save()
 
         registration.save()
-- 
GitLab


From c23a3c12c0787be3dc156be0c175a24cf91b894f Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:08:59 +0100
Subject: [PATCH 56/72] Use tabs for mail mode selection

---
 .../EventRegistrationForm.vue                 | 165 ++++++++----------
 1 file changed, 75 insertions(+), 90 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 1014097..b13720e 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -36,102 +36,87 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
                 <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
                 <div class="mb-4">
-                  <v-form v-if="emailMode" v-model="validationStatuses['email']">
-                    <v-row v-if="emailMode == 'postbuero'">
-                      <v-col>
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.email.localPart"
-                            :label="$t('postbuero.mail_addresses.data_table.local_part')"
-                            :rules="$rules().required.build(rules.emailLocalPart)"
-                            required
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                      <v-col>
-                        <div aria-required="true">
-                          <v-autocomplete
-                            outlined
-                            hide-no-data
-                            :items="mailDomains"
-                            item-text="domain"
-                            item-value="id"
-                            :loading="$apollo.queries.mailDomains.loading"
-                            prepend-icon="mdi-at"
-                            v-model="data.email.domain"
-                            :label="$t('postbuero.mail_addresses.data_table.domain')"
-                            required
-                            :rules="$rules().required.build()"
-                          />
-                        </div>
-                      </v-col>
-                    </v-row>
-                    <v-row v-else-if="emailMode == 'own'">
-                      <v-col>
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.user.email"
-                            :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
-                            required
-                            :rules="$rules().required.build(rules.email)"
-                            prepend-icon="mdi-email-outline"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                      <v-col>
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.user.confirmEmail"
-                            :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
-                            required
-                            :rules="$rules().required.build(rules.confirmEmail)"
-                            prepend-icon="mdi-email-outline"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                    </v-row>
-                    <v-card-actions>
-                      <v-row>
-                        <v-col>
-                          <secondary-action-button
-                            @click="emailMode = null"
-                            i18n-key="paweljong.event_registration.form.steps.email.choose_mode.back"
-                          />
-                        </v-col>
-                        <v-spacer />
-                      </v-row>
-                    </v-card-actions>
+                  <v-card-text>
+                    {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
+                  </v-card-text>
+                  <v-tabs v-model="emailMode" grow optional>
+                    <v-tab tab-value="postbuero">
+                      {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis") }}
+                    </v-tab>
+                    <v-tab tab-value="own">
+                      {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_own") }}
+                    </v-tab>
+                  </v-tabs>
+                  <v-form v-model="validationStatuses['email']">
+                    <v-tabs-items v-model="emailMode">
+                      <v-tab-item key="postbuero">
+                        <v-row class="mt-4">
+                          <v-col>
+                            <div aria-required="true">
+                              <v-text-field
+                                outlined
+                                v-model="data.email.localPart"
+                                :label="$t('postbuero.mail_addresses.data_table.local_part')"
+                                :rules="emailMode === 0 ? $rules().required.build(rules.emailLocalPart) : []"
+                                required
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                          <v-col>
+                            <div aria-required="true">
+                              <v-autocomplete
+                                outlined
+                                hide-no-data
+                                :items="mailDomains"
+                                item-text="domain"
+                                item-value="id"
+                                :loading="$apollo.queries.mailDomains.loading"
+                                prepend-icon="mdi-at"
+                                v-model="data.email.domain"
+                                :label="$t('postbuero.mail_addresses.data_table.domain')"
+                                required
+                                :rules="emailMode === 0 ? $rules().required.build() : []"
+                              />
+                            </div>
+                          </v-col>
+                        </v-row>
+                      </v-tab-item>
+                      <v-tab-item key="own">
+                        <v-row class="mt-4">
+                          <v-col>
+                            <div aria-required="true">
+                              <v-text-field
+                                outlined
+                                v-model="data.user.email"
+                                :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
+                                required
+                                :rules="emailMode === 1 ? $rules().required.build(rules.email) : []"
+                                prepend-icon="mdi-email-outline"
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                          <v-col>
+                            <div aria-required="true">
+                              <v-text-field
+                                outlined
+                                v-model="data.user.confirmEmail"
+                                :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
+                                required
+                                :rules="emailMode === 1 ? $rules().required.build(rules.confirmEmail) : []"
+                                prepend-icon="mdi-email-outline"
+                              ></v-text-field>
+                            </div>
+                          </v-col>
+                        </v-row>
+                      </v-tab-item>
+                    </v-tabs-items>
                   </v-form>
-                  <template v-else>
-                    <v-card-text>
-                      {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
-                    </v-card-text>
-                    <v-card-actions>
-                      <v-row justify="end">
-                        <v-col>
-                          <primary-action-button
-                            @click="emailMode = 'postbuero'"
-                            i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis"
-                          />
-                        </v-col>
-                        <v-col>
-                          <primary-action-button
-                            @click="emailMode = 'own'"
-                            i18n-key="paweljong.event_registration.form.steps.email.choose_mode.continue_own"
-                          />
-                        </v-col>
-                      </v-row>
-                    </v-card-actions>
-                  </template>
                 </div>
                 <v-divider class="mb-4" />
                 <control-row
                   :step="step"
                   @set-step="setStep"
-                  :next-disabled="!validationStatuses['email']"
+                  :next-disabled="emailMode === undefined || emailMode === null || !validationStatuses['email']"
                 />
               </v-stepper-content>
 
-- 
GitLab


From ea120f72c15984a522ef1312af92624233ca5ba2 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:13:40 +0100
Subject: [PATCH 57/72] Show tab arrows on mobile

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index b13720e..5366362 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -39,7 +39,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   <v-card-text>
                     {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
                   </v-card-text>
-                  <v-tabs v-model="emailMode" grow optional>
+                  <v-tabs v-model="emailMode" grow optional show-arrows>
                     <v-tab tab-value="postbuero">
                       {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis") }}
                     </v-tab>
-- 
GitLab


From b949272100d7860f82220d806073c7f410a2075c Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:38:12 +0100
Subject: [PATCH 58/72] Make design responsive

---
 .../EventRegistrationForm.vue                 | 73 ++++++++++---------
 1 file changed, 40 insertions(+), 33 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 5366362..62a2176 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -39,11 +39,11 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   <v-card-text>
                     {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
                   </v-card-text>
-                  <v-tabs v-model="emailMode" grow optional show-arrows>
-                    <v-tab tab-value="postbuero">
+                  <v-tabs v-model="emailMode" :grow="!$vuetify.breakpoint.mdAndDown" optional show-arrows>
+                    <v-tab tab-value="postbuero" style="max-width: 50vw">
                       {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis") }}
                     </v-tab>
-                    <v-tab tab-value="own">
+                    <v-tab tab-value="own" style="max-width: 50vw">
                       {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_own") }}
                     </v-tab>
                   </v-tabs>
@@ -51,7 +51,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     <v-tabs-items v-model="emailMode">
                       <v-tab-item key="postbuero">
                         <v-row class="mt-4">
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div aria-required="true">
                               <v-text-field
                                 outlined
@@ -62,7 +62,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                               ></v-text-field>
                             </div>
                           </v-col>
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div aria-required="true">
                               <v-autocomplete
                                 outlined
@@ -83,7 +83,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </v-tab-item>
                       <v-tab-item key="own">
                         <v-row class="mt-4">
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div aria-required="true">
                               <v-text-field
                                 outlined
@@ -95,7 +95,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                               ></v-text-field>
                             </div>
                           </v-col>
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div aria-required="true">
                               <v-text-field
                                 outlined
@@ -149,7 +149,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </v-col>
                     </v-row>
                     <v-row>
-                      <v-col>
+                      <v-col cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -161,7 +161,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col>
+                      <v-col cols="12" md="6" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -174,7 +174,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col>
+                      <v-col cols="12" md="6" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -203,7 +203,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                 <div class="mb-4">
                   <v-form v-model="validationStatuses['contact_details']">
                     <v-row>
-                      <v-col v-if="isFieldVisible('date_of_birth')">
+                      <v-col v-if="isFieldVisible('date_of_birth')" cols="12" md="6" lg="4">
                         <div aria-required="true">
                           <date-field
                             outlined
@@ -215,7 +215,15 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           />
                         </div>
                       </v-col>
-                      <v-col v-if="isFieldVisible('sex')">
+                      <v-col v-if="isFieldVisible('mobile_number')" cols="12" md="6" lg="4">
+                        <v-text-field
+                          outlined
+                          v-model="data.person.mobileNumber"
+                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
+                          prepend-icon="mdi-phone-outline"
+                        ></v-text-field>
+                      </v-col>
+                      <v-col v-if="isFieldVisible('sex')" cols="12" lg="4">
                         <div aria-required="true">
                           <!-- FIXME: Prefilling data does not work due to upper-/lowercase situation; will be fixed with core person form refactoring -->
                           <sex-select
@@ -229,17 +237,9 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           />
                         </div>
                       </v-col>
-                      <v-col v-if="isFieldVisible('mobile_number')">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.mobileNumber"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
-                          prepend-icon="mdi-phone-outline"
-                        ></v-text-field>
-                      </v-col>
                     </v-row>
                     <v-row>
-                      <v-col v-if="isFieldVisible('street')">
+                      <v-col v-if="isFieldVisible('street')" cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -250,7 +250,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col v-if="isFieldVisible('housenumber')">
+                      <v-col v-if="isFieldVisible('housenumber')" cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -263,7 +263,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </v-col>
                     </v-row>
                     <v-row>
-                      <v-col v-if="isFieldVisible('postal_code')">
+                      <v-col v-if="isFieldVisible('postal_code')" cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -274,7 +274,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col v-if="isFieldVisible('place')">
+                      <v-col v-if="isFieldVisible('place')" cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -287,7 +287,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </v-col>
                     </v-row>
                     <v-row v-if="isFieldVisible('school_details')">
-                      <v-col>
+                      <v-col cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -299,7 +299,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col>
+                      <v-col cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -311,7 +311,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col>
+                      <v-col cols="12" lg="4">
                         <div aria-required="true">
                           <v-text-field
                             outlined
@@ -344,7 +344,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </v-card-title>
                       <v-card-text>
                         <v-row>
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div :aria-required="index === 0 ? 'true' : 'false'">
                               <v-text-field
                                 outlined
@@ -356,7 +356,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                               ></v-text-field>
                             </div>
                           </v-col>
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div :aria-required="index === 0 ? 'true' : 'false'">
                               <v-text-field
                                 outlined
@@ -370,7 +370,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                           </v-col>
                         </v-row>
                         <v-row>
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div :aria-required="index === 0 ? 'true' : 'false'">
                               <v-text-field
                                 outlined
@@ -381,7 +381,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                               ></v-text-field>
                             </div>
                           </v-col>
-                          <v-col>
+                          <v-col cols="12" md="6">
                             <div :aria-required="index === 0 ? 'true' : 'false'">
                               <v-text-field
                                 outlined
@@ -402,8 +402,11 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       <v-spacer />
                       <secondary-action-button
                         @click="addGuardian"
-                        i18n-key="paweljong.event_registration.form.steps.guardians.add"
+                        class="btn-multiline"
                       >
+                        <span class="text-wrap">
+                          {{ $t("paweljong.event_registration.form.steps.guardians.add") }}
+                        </span>
                       </secondary-action-button>
                     </v-col>
                   </v-row>
@@ -890,5 +893,9 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
   };
   </script>
   
-  <style></style>
+  <style>
+  .btn-multiline > span {
+    width: 100%
+  }
+  </style>
   
\ No newline at end of file
-- 
GitLab


From 237b7f4daedf4d2d6ea6330edebba42bdb428ca8 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:43:24 +0100
Subject: [PATCH 59/72] Fix templated strings

---
 .../components/event_registration/EventRegistrationForm.vue   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 62a2176..18e33ae 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -340,7 +340,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   <v-form  v-model="validationStatuses['guardians']">
                     <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
                       <v-card-title>
-                        {{ $tc("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
+                        {{ $t("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
                       </v-card-title>
                       <v-card-text>
                         <v-row>
@@ -546,7 +546,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                         v-if="event.dateRetraction"
                         required
                         :rules="$rules().required.build()"
-                        :label="$tc('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
+                        :label="$t('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
                         v-model="data.retractionConsent"
                       />
                     </div>
-- 
GitLab


From 0fa84fd00a17117af3e660b9fb5bc45f75199702 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:46:27 +0100
Subject: [PATCH 60/72] Reformat

---
 aleksis/apps/paweljong/data_checks.py         |    4 +-
 aleksis/apps/paweljong/forms.py               |   55 +-
 .../frontend/components/event/Checkpoint.vue  |  200 +-
 .../EventAdditionalFieldList.vue              |    8 +-
 .../eventAdditionalFieldMixin.js              |   20 +-
 .../EventRegistrationForm.vue                 | 1977 ++++++++++-------
 .../event_registration/helpers.graphql        |    2 +-
 aleksis/apps/paweljong/frontend/index.js      |    3 +-
 .../apps/paweljong/frontend/messages/de.json  |  470 ++--
 aleksis/apps/paweljong/models.py              |   16 +-
 aleksis/apps/paweljong/preferences.py         |   29 +-
 aleksis/apps/paweljong/rules.py               |   12 +-
 aleksis/apps/paweljong/schema/__init__.py     |    3 -
 .../paweljong/schema/event_registration.py    |   63 +-
 .../apps/paweljong/schema/site_preferences.py |    1 -
 aleksis/apps/paweljong/views.py               |   30 +-
 16 files changed, 1660 insertions(+), 1233 deletions(-)

diff --git a/aleksis/apps/paweljong/data_checks.py b/aleksis/apps/paweljong/data_checks.py
index 9fd310c..b13878e 100644
--- a/aleksis/apps/paweljong/data_checks.py
+++ b/aleksis/apps/paweljong/data_checks.py
@@ -29,7 +29,9 @@ class EventMembersSyncDataCheck(DataCheck):
 
         async_events = []
         for event in Event.objects.all():
-            if set(event.linked_group.members.values_list("id", flat=True)) != set(event.registrations.filter(retracted=False).values_list("person", flat=True)):
+            if set(event.linked_group.members.values_list("id", flat=True)) != set(
+                event.registrations.filter(retracted=False).values_list("person", flat=True)
+            ):
                 async_events.append(event)
 
         for event in async_events:
diff --git a/aleksis/apps/paweljong/forms.py b/aleksis/apps/paweljong/forms.py
index 3122199..a79004a 100644
--- a/aleksis/apps/paweljong/forms.py
+++ b/aleksis/apps/paweljong/forms.py
@@ -62,7 +62,13 @@ class EditEventForm(ExtensibleForm):
             Row("display_name", "slug", "description"),
             Row("place", "published"),
             Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")),
-            Fieldset(_("Event details"), Row("cost", "min_cost", "max_cost", "max_participants"), "information", "additional_fields", "contact_information_visible_fields"),
+            Fieldset(
+                _("Event details"),
+                Row("cost", "min_cost", "max_cost", "max_participants"),
+                "information",
+                "additional_fields",
+                "contact_information_visible_fields",
+            ),
             Fieldset(_("Terms"), "terms"),
             Fieldset(_("Info mailings"), "info_mailings"),
         ),
@@ -108,16 +114,17 @@ class EditEventForm(ExtensibleForm):
                 attrs={"data-minimum-input-length": 0, "class": "browser-default"},
             ),
             "contact_information_visible_fields": ArrayFieldCheckboxSelectMultiple(
-            choices=[
-                ("date_of_birth", _("Date of birth")),
-                ("sex", _("Sex")),
-                ("street", _("Street")),
-                ("housenumber", _("House number")),
-                ("postal_code", _("Postal code")),
-                ("place", _("Place")),
-                ("mobile_number", _("Mobile number")),
-                ("school_details", _("School details")),
-            ])
+                choices=[
+                    ("date_of_birth", _("Date of birth")),
+                    ("sex", _("Sex")),
+                    ("street", _("Street")),
+                    ("housenumber", _("House number")),
+                    ("postal_code", _("Postal code")),
+                    ("place", _("Place")),
+                    ("mobile_number", _("Mobile number")),
+                    ("school_details", _("School details")),
+                ]
+            ),
         }
 
 
@@ -218,10 +225,12 @@ class RegisterEventContactDetails(ExtensibleForm):
 
     @classmethod
     def clear_layout(cls):
-        cls.base_layout = [Fieldset(
-            _("Name"),
-            Row("first_name", "last_name"),
-        )]
+        cls.base_layout = [
+            Fieldset(
+                _("Name"),
+                Row("first_name", "last_name"),
+            )
+        ]
         cls.layout = Layout(*cls.base_layout)
 
     def __init__(self, event, *args, **kwargs):
@@ -239,7 +248,10 @@ class RegisterEventContactDetails(ExtensibleForm):
             disabled=True,
         )
 
-        if "date_of_birth" in event.contact_information_visible_fields or "sex" in event.contact_information_visible_fields:
+        if (
+            "date_of_birth" in event.contact_information_visible_fields
+            or "sex" in event.contact_information_visible_fields
+        ):
             active_fields = []
             if "date_of_birth" in event.contact_information_visible_fields:
                 active_fields.append("date_of_birth")
@@ -260,7 +272,12 @@ class RegisterEventContactDetails(ExtensibleForm):
             node = Fieldset(_("Personal information"), *active_fields)
             self.add_node_to_layout(node, add_fields=False)
 
-        if "street" in event.contact_information_visible_fields or "housenumber" in event.contact_information_visible_fields or "postal_code" in event.contact_information_visible_fields or "place" in event.contact_information_visible_fields:
+        if (
+            "street" in event.contact_information_visible_fields
+            or "housenumber" in event.contact_information_visible_fields
+            or "postal_code" in event.contact_information_visible_fields
+            or "place" in event.contact_information_visible_fields
+        ):
             active_fields = []
             if "street" in event.contact_information_visible_fields:
                 active_fields.append("street")
@@ -283,7 +300,6 @@ class RegisterEventContactDetails(ExtensibleForm):
                     label=_("Place"),
                 )
             self.add_node_to_layout(Fieldset(_("Address data"), *active_fields), add_fields=False)
-                
 
         self.fields["email"] = forms.EmailField(
             label=_("Email address"),
@@ -306,7 +322,7 @@ class RegisterEventContactDetails(ExtensibleForm):
             contact_details_fields = ["mobile_number", "email"]
         else:
             contact_details_fields = ["email"]
-        
+
         node = Fieldset(_("Contact details"), *contact_details_fields)
         self.add_node_to_layout(node, add_fields=False)
 
@@ -603,6 +619,7 @@ class EventCheckpointForm(forms.Form):
         widget=forms.HiddenInput(),
     )
 
+
 class RegisterAccountForm(SignupForm, ExtensibleForm):
     """Form to register new user accounts."""
 
diff --git a/aleksis/apps/paweljong/frontend/components/event/Checkpoint.vue b/aleksis/apps/paweljong/frontend/components/event/Checkpoint.vue
index 04ca3f8..b8262de 100644
--- a/aleksis/apps/paweljong/frontend/components/event/Checkpoint.vue
+++ b/aleksis/apps/paweljong/frontend/components/event/Checkpoint.vue
@@ -1,116 +1,126 @@
-<template><div>
-  <v-form
-    ref="form"
-  >
-    <v-text-field
-      v-model="comment"
-      label="Comment"
-      required
-    ></v-text-field>
+<template>
+  <div>
+    <v-form ref="form">
+      <v-text-field v-model="comment" label="Comment" required></v-text-field>
 
-    <v-btn
-      color="success"
-      class="mr-4"
-      @click="startScan"
-    >
-      Check in
-    </v-btn>
-  </v-form>
-  <v-card class="mx-auto">
-    <v-alert :color="status.color">
-      {{ status.message }}
-    </v-alert>
-  </v-card>
-  <v-card class="mx-auto">
-    <v-alert v-for="(checkIn, i) in checkIns" :key="i" :color="checkIn.color">
-      {{ checkIn.message }}
-    </v-alert>
-  </v-card>
-</div></template>
+      <v-btn color="success" class="mr-4" @click="startScan"> Check in </v-btn>
+    </v-form>
+    <v-card class="mx-auto">
+      <v-alert :color="status.color">
+        {{ status.message }}
+      </v-alert>
+    </v-card>
+    <v-card class="mx-auto">
+      <v-alert v-for="(checkIn, i) in checkIns" :key="i" :color="checkIn.color">
+        {{ checkIn.message }}
+      </v-alert>
+    </v-card>
+  </div>
+</template>
 
 <script>
-  import gql from "graphql-tag";
+import gql from "graphql-tag";
 
-  export default {
-    data () {
-      return {
-        comment: "",
-        status: {
-          "color": "blue-grey",
-          "message": "Scan not started",
-        },
-        checkIns: new Array(),
-      }
-    },
-    methods: {
-      checkIn (data, statusObject) {
-        this.$apollo.mutate({
-          mutation: gql`mutation ($eventSlug:String!, $personId:Int!, $comment:String!, $lat:Int, $lon:Int) {
-            checkpointCheckIn(eventSlug:$eventSlug, personId:$personId, comment:$comment, lat:$lat, lon:$lon){
-              checkpoint {
-                id
+export default {
+  data() {
+    return {
+      comment: "",
+      status: {
+        color: "blue-grey",
+        message: "Scan not started",
+      },
+      checkIns: new Array(),
+    };
+  },
+  methods: {
+    checkIn(data, statusObject) {
+      this.$apollo
+        .mutate({
+          mutation: gql`
+            mutation (
+              $eventSlug: String!
+              $personId: Int!
+              $comment: String!
+              $lat: Int
+              $lon: Int
+            ) {
+              checkpointCheckIn(
+                eventSlug: $eventSlug
+                personId: $personId
+                comment: $comment
+                lat: $lat
+                lon: $lon
+              ) {
+                checkpoint {
+                  id
+                }
               }
             }
-          }`,
+          `,
           variables: {
-            "eventSlug": this.$route.params.slug,
-            "personId": data.id,
-            "comment": this.comment
-	  }
-	}).then((data) => {
+            eventSlug: this.$route.params.slug,
+            personId: data.id,
+            comment: this.comment,
+          },
+        })
+        .then((data) => {
           statusObject.message = `Checked in ${data.user.username}`;
           statusObject.color = "green";
-	}).catch((error) => {
+        })
+        .catch((error) => {
           statusObject.message = `Error checking in ${data.user.username}`;
           statusObject.color = "red";
-	})
-      },
-      startScan() {
-        try {
-          const ndef = new NDEFReader();
-          ndef.scan().then(() => {
-            this.status.color = "blue-grey";
-            this.status.message = "Scanning...";
-            ndef.addEventListener("readingerror", (err) => {
-              // FIXME use semantic colors/types
-              this.status.color = "red";
-              this.status.message = err;
-            });
-            ndef.addEventListener("reading", (e) => {
-              const message = e.message;
-              const checkInStatus = {
-                "color": "blue-grey",
-                "message": "Decoding...",
-              };
-              this.checkIns.unshift(checkInStatus);
-              for (const record of message.records) {
-                if (record.recordType !== "url") {
-                  checkInStatus.message = "Found non-URL";
-                  continue;
-                }
-                const decoder = new TextDecoder();
-                const url = decoder.decode(record.data);
-                // FIXME use configured base URL here
-                if (!url.startsWith("https://ticdesk.teckids.org/o/")) {
-                  checkInStatus.message = "Found invalid URL";
-                  checkInStatus.color = "red";
-                  break;
-                }
-                fetch(url).then((res) => res.json()).then((data) => {
+        });
+    },
+    startScan() {
+      try {
+        const ndef = new NDEFReader();
+        ndef.scan().then(() => {
+          this.status.color = "blue-grey";
+          this.status.message = "Scanning...";
+          ndef.addEventListener("readingerror", (err) => {
+            // FIXME use semantic colors/types
+            this.status.color = "red";
+            this.status.message = err;
+          });
+          ndef.addEventListener("reading", (e) => {
+            const message = e.message;
+            const checkInStatus = {
+              color: "blue-grey",
+              message: "Decoding...",
+            };
+            this.checkIns.unshift(checkInStatus);
+            for (const record of message.records) {
+              if (record.recordType !== "url") {
+                checkInStatus.message = "Found non-URL";
+                continue;
+              }
+              const decoder = new TextDecoder();
+              const url = decoder.decode(record.data);
+              // FIXME use configured base URL here
+              if (!url.startsWith("https://ticdesk.teckids.org/o/")) {
+                checkInStatus.message = "Found invalid URL";
+                checkInStatus.color = "red";
+                break;
+              }
+              fetch(url)
+                .then((res) => res.json())
+                .then((data) => {
                   checkInStatus.message = `Checking in ${data.user.username}...`;
                   checkInStatus.color = "orange";
                   this.checkIn(data, checkInStatus);
-                }).catch((error) => {
+                })
+                .catch((error) => {
                   checkInStatus.message = "Error retrieving or decoding data";
                   checkInStatus.color = "red";
                 });
-              }
-            });
+            }
           });
-        } catch {
-          console.log("Error");
-        }
+        });
+      } catch {
+        console.log("Error");
       }
     },
-  }
+  },
+};
 </script>
diff --git a/aleksis/apps/paweljong/frontend/components/event_additional_field/EventAdditionalFieldList.vue b/aleksis/apps/paweljong/frontend/components/event_additional_field/EventAdditionalFieldList.vue
index a0e2da9..a43fa9d 100644
--- a/aleksis/apps/paweljong/frontend/components/event_additional_field/EventAdditionalFieldList.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_additional_field/EventAdditionalFieldList.vue
@@ -80,7 +80,9 @@ export default {
           value: "fieldType",
         },
         {
-          text: this.$t("paweljong.event_additional_field.required.form_field_name"),
+          text: this.$t(
+            "paweljong.event_additional_field.required.form_field_name",
+          ),
           value: "required",
         },
         {
@@ -104,7 +106,7 @@ export default {
   },
   methods: {
     getCreateData(item) {
-      let {fieldTitle: _, ...filteredObj} = item;
+      let { fieldTitle: _, ...filteredObj } = item;
       return {
         ...filteredObj,
         required: !!item.required,
@@ -113,7 +115,7 @@ export default {
     },
     getPatchData(item) {
       if ("required" in item) {
-        let {fieldTitle: _, ...filteredObj} = item;
+        let { fieldTitle: _, ...filteredObj } = item;
         return {
           ...filteredObj,
           required: !!item.required,
diff --git a/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js b/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js
index 17e08f3..c63724f 100644
--- a/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js
+++ b/aleksis/apps/paweljong/frontend/components/event_additional_field/eventAdditionalFieldMixin.js
@@ -14,18 +14,29 @@ import PositiveSmallIntegerField from "aleksis.core/components/generic/forms/Pos
 const eventAdditionalFieldMixin = {
   methods: {
     fieldComponentForAdditionalField(additionalField) {
-      if (additionalField.fieldType == "CHARFIELD" || additionalField.fieldType == "EMAILFIELD" || additionalField.fieldType == "URLFIELD" || additionalField.fieldType == "GENERICIPADDRESSFIELD") {
+      if (
+        additionalField.fieldType == "CHARFIELD" ||
+        additionalField.fieldType == "EMAILFIELD" ||
+        additionalField.fieldType == "URLFIELD" ||
+        additionalField.fieldType == "GENERICIPADDRESSFIELD"
+      ) {
         return "v-text-field";
-      } else if (additionalField.fieldType == "BOOLEANFIELD" || additionalField.fieldType == "NULLBOOLEANFIELD") {
+      } else if (
+        additionalField.fieldType == "BOOLEANFIELD" ||
+        additionalField.fieldType == "NULLBOOLEANFIELD"
+      ) {
         // TODO: implement proper null boolean input
-        return "v-checkbox"
+        return "v-checkbox";
       } else if (additionalField.fieldType == "DATEFIELD") {
         return DateField;
       } else if (additionalField.fieldType == "DATETIMEFIELD") {
         return DateTimeField;
       } else if (additionalField.fieldType == "TIMEFIELD") {
         return TimeField;
-      } else if (additionalField.fieldType == "INTEGERFIELD" || additionalField.fieldType == "DECIMALFIELD") {
+      } else if (
+        additionalField.fieldType == "INTEGERFIELD" ||
+        additionalField.fieldType == "DECIMALFIELD"
+      ) {
         // TODO: implement proper decimal input
         return PositiveSmallIntegerField;
       }
@@ -35,4 +46,3 @@ const eventAdditionalFieldMixin = {
 };
 
 export default eventAdditionalFieldMixin;
-  
\ No newline at end of file
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 18e33ae..a830a4b 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -9,893 +9,1266 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
 </script>
 
 <template>
-    <div>
-      <div v-if="event && eventRegistrationSent">
-        <v-card>
-          <v-card-title>
-            <v-icon class="mr-2" color="success">mdi-check-circle-outline</v-icon>
-            {{ $t("paweljong.event_registration.form.submitted.thank_you") }}
-          </v-card-title>
-          <v-card-text class="text-body-1 black--text">
-            {{ $t("paweljong.event_registration.form.submitted.submitted_successfully") }}
-          </v-card-text>
-        </v-card>
-      </div>
-      <div v-else-if="event">
-        <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
-        <v-stepper v-model="step" class="mb-4">
-          <v-stepper-header class="flex-nowrap">
-            <template v-for="(stepChoice, index) in steps">
-              <v-stepper-step :complete="step > index + 1" :step="index + 1" :key="stepChoice.name">
-                {{ $t(stepChoice.titleKey) }}
-              </v-stepper-step>
-              <v-divider v-if="index + 1 < steps.length" ></v-divider>
-            </template>
-          </v-stepper-header>
-            <v-stepper-items>
-              <v-stepper-content v-if="isStepEnabled('email')" :step="getStepIndex('email')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('email')) }}</h2>
-                <div class="mb-4">
-                  <v-card-text>
-                    {{ $t("paweljong.event_registration.form.steps.email.choose_mode.help_text") }}
-                  </v-card-text>
-                  <v-tabs v-model="emailMode" :grow="!$vuetify.breakpoint.mdAndDown" optional show-arrows>
-                    <v-tab tab-value="postbuero" style="max-width: 50vw">
-                      {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis") }}
-                    </v-tab>
-                    <v-tab tab-value="own" style="max-width: 50vw">
-                      {{ $t("paweljong.event_registration.form.steps.email.choose_mode.continue_own") }}
-                    </v-tab>
-                  </v-tabs>
-                  <v-form v-model="validationStatuses['email']">
-                    <v-tabs-items v-model="emailMode">
-                      <v-tab-item key="postbuero">
-                        <v-row class="mt-4">
-                          <v-col cols="12" md="6">
-                            <div aria-required="true">
-                              <v-text-field
-                                outlined
-                                v-model="data.email.localPart"
-                                :label="$t('postbuero.mail_addresses.data_table.local_part')"
-                                :rules="emailMode === 0 ? $rules().required.build(rules.emailLocalPart) : []"
-                                required
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                          <v-col cols="12" md="6">
-                            <div aria-required="true">
-                              <v-autocomplete
-                                outlined
-                                hide-no-data
-                                :items="mailDomains"
-                                item-text="domain"
-                                item-value="id"
-                                :loading="$apollo.queries.mailDomains.loading"
-                                prepend-icon="mdi-at"
-                                v-model="data.email.domain"
-                                :label="$t('postbuero.mail_addresses.data_table.domain')"
-                                required
-                                :rules="emailMode === 0 ? $rules().required.build() : []"
-                              />
-                            </div>
-                          </v-col>
-                        </v-row>
-                      </v-tab-item>
-                      <v-tab-item key="own">
-                        <v-row class="mt-4">
-                          <v-col cols="12" md="6">
-                            <div aria-required="true">
-                              <v-text-field
-                                outlined
-                                v-model="data.user.email"
-                                :label="$t('paweljong.event_registration.form.steps.email.fields.email.label')"
-                                required
-                                :rules="emailMode === 1 ? $rules().required.build(rules.email) : []"
-                                prepend-icon="mdi-email-outline"
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                          <v-col cols="12" md="6">
-                            <div aria-required="true">
-                              <v-text-field
-                                outlined
-                                v-model="data.user.confirmEmail"
-                                :label="$t('paweljong.event_registration.form.steps.email.fields.confirm_email.label')"
-                                required
-                                :rules="emailMode === 1 ? $rules().required.build(rules.confirmEmail) : []"
-                                prepend-icon="mdi-email-outline"
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                        </v-row>
-                      </v-tab-item>
-                    </v-tabs-items>
-                  </v-form>
-                </div>
-                <v-divider class="mb-4" />
-                <control-row
-                  :step="step"
-                  @set-step="setStep"
-                  :next-disabled="emailMode === undefined || emailMode === null || !validationStatuses['email']"
-                />
-              </v-stepper-content>
-
-              <v-stepper-content v-if="isStepEnabled('register')" :step="getStepIndex('register')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
-                <div class="mb-4">
-                  <v-form v-model="validationStatuses['register']">
-                    <v-row>
-                      <v-col>
+  <div>
+    <div v-if="event && eventRegistrationSent">
+      <v-card>
+        <v-card-title>
+          <v-icon class="mr-2" color="success">mdi-check-circle-outline</v-icon>
+          {{ $t("paweljong.event_registration.form.submitted.thank_you") }}
+        </v-card-title>
+        <v-card-text class="text-body-1 black--text">
+          {{
+            $t(
+              "paweljong.event_registration.form.submitted.submitted_successfully",
+            )
+          }}
+        </v-card-text>
+      </v-card>
+    </div>
+    <div v-else-if="event">
+      <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
+      <v-stepper v-model="step" class="mb-4">
+        <v-stepper-header class="flex-nowrap">
+          <template v-for="(stepChoice, index) in steps">
+            <v-stepper-step
+              :complete="step > index + 1"
+              :step="index + 1"
+              :key="stepChoice.name"
+            >
+              {{ $t(stepChoice.titleKey) }}
+            </v-stepper-step>
+            <v-divider v-if="index + 1 < steps.length"></v-divider>
+          </template>
+        </v-stepper-header>
+        <v-stepper-items>
+          <v-stepper-content
+            v-if="isStepEnabled('email')"
+            :step="getStepIndex('email')"
+          >
+            <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("email")) }}</h2>
+            <div class="mb-4">
+              <v-card-text>
+                {{
+                  $t(
+                    "paweljong.event_registration.form.steps.email.choose_mode.help_text",
+                  )
+                }}
+              </v-card-text>
+              <v-tabs
+                v-model="emailMode"
+                :grow="!$vuetify.breakpoint.mdAndDown"
+                optional
+                show-arrows
+              >
+                <v-tab tab-value="postbuero" style="max-width: 50vw">
+                  {{
+                    $t(
+                      "paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis",
+                    )
+                  }}
+                </v-tab>
+                <v-tab tab-value="own" style="max-width: 50vw">
+                  {{
+                    $t(
+                      "paweljong.event_registration.form.steps.email.choose_mode.continue_own",
+                    )
+                  }}
+                </v-tab>
+              </v-tabs>
+              <v-form v-model="validationStatuses['email']">
+                <v-tabs-items v-model="emailMode">
+                  <v-tab-item key="postbuero">
+                    <v-row class="mt-4">
+                      <v-col cols="12" md="6">
                         <div aria-required="true">
                           <v-text-field
                             outlined
-                            v-model="data.person.firstName"
-                            :label="$t('paweljong.event_registration.form.steps.register.fields.first_name.label')"
+                            v-model="data.email.localPart"
+                            :label="
+                              $t(
+                                'postbuero.mail_addresses.data_table.local_part',
+                              )
+                            "
+                            :rules="
+                              emailMode === 0
+                                ? $rules().required.build(rules.emailLocalPart)
+                                : []
+                            "
                             required
-                            :rules="$rules().required.build()"
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col>
+                      <v-col cols="12" md="6">
                         <div aria-required="true">
-                          <v-text-field
+                          <v-autocomplete
                             outlined
-                            v-model="data.person.lastName"
-                            :label="$t('paweljong.event_registration.form.steps.register.fields.last_name.label')"
+                            hide-no-data
+                            :items="mailDomains"
+                            item-text="domain"
+                            item-value="id"
+                            :loading="$apollo.queries.mailDomains.loading"
+                            prepend-icon="mdi-at"
+                            v-model="data.email.domain"
+                            :label="
+                              $t('postbuero.mail_addresses.data_table.domain')
+                            "
                             required
-                            :rules="$rules().required.build()"
-                          ></v-text-field>
+                            :rules="
+                              emailMode === 0 ? $rules().required.build() : []
+                            "
+                          />
                         </div>
                       </v-col>
                     </v-row>
-                    <v-row>
-                      <v-col cols="12" lg="4">
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.user.username"
-                            :label="$t('paweljong.event_registration.form.steps.register.fields.username.label')"
-                            required
-                            :rules="$rules().required.build()"
-                            prepend-icon="mdi-account-outline"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                      <v-col cols="12" md="6" lg="4">
+                  </v-tab-item>
+                  <v-tab-item key="own">
+                    <v-row class="mt-4">
+                      <v-col cols="12" md="6">
                         <div aria-required="true">
                           <v-text-field
                             outlined
-                            v-model="data.user.password"
-                            :label="$t('paweljong.event_registration.form.steps.register.fields.password.label')"
+                            v-model="data.user.email"
+                            :label="
+                              $t(
+                                'paweljong.event_registration.form.steps.email.fields.email.label',
+                              )
+                            "
                             required
-                            :rules="$rules().required.build()"
-                            type="password"
-                            prepend-icon="mdi-form-textbox-password"
+                            :rules="
+                              emailMode === 1
+                                ? $rules().required.build(rules.email)
+                                : []
+                            "
+                            prepend-icon="mdi-email-outline"
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col cols="12" md="6" lg="4">
+                      <v-col cols="12" md="6">
                         <div aria-required="true">
                           <v-text-field
                             outlined
-                            v-model="data.user.confirmPassword"
-                            :label="$t('paweljong.event_registration.form.steps.register.fields.confirm_password.label')"
+                            v-model="data.user.confirmEmail"
+                            :label="
+                              $t(
+                                'paweljong.event_registration.form.steps.email.fields.confirm_email.label',
+                              )
+                            "
                             required
-                            :rules="$rules().required.build(rules.confirmPassword)"
-                            type="password"
-                            prepend-icon="mdi-form-textbox-password"
+                            :rules="
+                              emailMode === 1
+                                ? $rules().required.build(rules.confirmEmail)
+                                : []
+                            "
+                            prepend-icon="mdi-email-outline"
                           ></v-text-field>
                         </div>
                       </v-col>
                     </v-row>
-                  </v-form>
-                </div>
-                <v-divider class="mb-4" />
-                <control-row
-                  :step="step"
-                  @set-step="setStep"
-                  :next-disabled="!validationStatuses['register']"
-                />
-              </v-stepper-content>
+                  </v-tab-item>
+                </v-tabs-items>
+              </v-form>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="
+                emailMode === undefined ||
+                emailMode === null ||
+                !validationStatuses['email']
+              "
+            />
+          </v-stepper-content>
 
-              <v-stepper-content v-if="isStepEnabled('contact_details')" :step="getStepIndex('contact_details')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('contact_details')) }}</h2>
-                <div class="mb-4">
-                  <v-form v-model="validationStatuses['contact_details']">
-                    <v-row>
-                      <v-col v-if="isFieldVisible('date_of_birth')" cols="12" md="6" lg="4">
-                        <div aria-required="true">
-                          <date-field
-                            outlined
-                            v-model="data.person.dateOfBirth"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label')"
-                            required
-                            :rules="$rules().required.build()"
-                            prepend-icon="mdi-cake-variant-outline"
-                          />
-                        </div>
-                      </v-col>
-                      <v-col v-if="isFieldVisible('mobile_number')" cols="12" md="6" lg="4">
-                        <v-text-field
-                          outlined
-                          v-model="data.person.mobileNumber"
-                          :label="$t('paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label')"
-                          prepend-icon="mdi-phone-outline"
-                        ></v-text-field>
-                      </v-col>
-                      <v-col v-if="isFieldVisible('sex')" cols="12" lg="4">
-                        <div aria-required="true">
-                          <!-- FIXME: Prefilling data does not work due to upper-/lowercase situation; will be fixed with core person form refactoring -->
-                          <sex-select
-                            outlined
-                            v-model="data.person.sex"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.sex.help_text')"
-                            persistent-hint
-                            required
-                            :rules="$rules().required.build()"
-                          />
-                        </div>
-                      </v-col>
-                    </v-row>
+          <v-stepper-content
+            v-if="isStepEnabled('register')"
+            :step="getStepIndex('register')"
+          >
+            <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("register")) }}</h2>
+            <div class="mb-4">
+              <v-form v-model="validationStatuses['register']">
+                <v-row>
+                  <v-col>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.firstName"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.register.fields.first_name.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.lastName"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.register.fields.last_name.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.username"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.register.fields.username.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                        prepend-icon="mdi-account-outline"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col cols="12" md="6" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.password"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.register.fields.password.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                        type="password"
+                        prepend-icon="mdi-form-textbox-password"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col cols="12" md="6" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.user.confirmPassword"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.register.fields.confirm_password.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build(rules.confirmPassword)"
+                        type="password"
+                        prepend-icon="mdi-form-textbox-password"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                </v-row>
+              </v-form>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="!validationStatuses['register']"
+            />
+          </v-stepper-content>
+
+          <v-stepper-content
+            v-if="isStepEnabled('contact_details')"
+            :step="getStepIndex('contact_details')"
+          >
+            <h2 class="text-h6 mb-4">
+              {{ $t(getStepTitleKey("contact_details")) }}
+            </h2>
+            <div class="mb-4">
+              <v-form v-model="validationStatuses['contact_details']">
+                <v-row>
+                  <v-col
+                    v-if="isFieldVisible('date_of_birth')"
+                    cols="12"
+                    md="6"
+                    lg="4"
+                  >
+                    <div aria-required="true">
+                      <date-field
+                        outlined
+                        v-model="data.person.dateOfBirth"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.date_of_birth.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                        prepend-icon="mdi-cake-variant-outline"
+                      />
+                    </div>
+                  </v-col>
+                  <v-col
+                    v-if="isFieldVisible('mobile_number')"
+                    cols="12"
+                    md="6"
+                    lg="4"
+                  >
+                    <v-text-field
+                      outlined
+                      v-model="data.person.mobileNumber"
+                      :label="
+                        $t(
+                          'paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label',
+                        )
+                      "
+                      prepend-icon="mdi-phone-outline"
+                    ></v-text-field>
+                  </v-col>
+                  <v-col v-if="isFieldVisible('sex')" cols="12" lg="4">
+                    <div aria-required="true">
+                      <!-- FIXME: Prefilling data does not work due to upper-/lowercase situation; will be fixed with core person form refactoring -->
+                      <sex-select
+                        outlined
+                        v-model="data.person.sex"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.sex.label',
+                          )
+                        "
+                        :hint="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.sex.help_text',
+                          )
+                        "
+                        persistent-hint
+                        required
+                        :rules="$rules().required.build()"
+                      />
+                    </div>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col v-if="isFieldVisible('street')" cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.street"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.street.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col v-if="isFieldVisible('housenumber')" cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.housenumber"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.housenumber.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                </v-row>
+                <v-row>
+                  <v-col v-if="isFieldVisible('postal_code')" cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.postalCode"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.postal_code.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col v-if="isFieldVisible('place')" cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.address.place"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.place.label',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                </v-row>
+                <v-row v-if="isFieldVisible('school_details')">
+                  <v-col cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.school"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.school.label',
+                          )
+                        "
+                        :hint="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.school.help_text',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.schoolPlace"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.school_place.label',
+                          )
+                        "
+                        :hint="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                  <v-col cols="12" lg="4">
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.schoolClass"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.school_class.label',
+                          )
+                        "
+                        :hint="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text',
+                          )
+                        "
+                        required
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
+                  </v-col>
+                </v-row>
+              </v-form>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="!validationStatuses['contact_details']"
+            />
+          </v-stepper-content>
+
+          <v-stepper-content :step="getStepIndex('guardians')">
+            <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("guardians")) }}</h2>
+            <div class="mb-4">
+              <v-form v-model="validationStatuses['guardians']">
+                <v-card
+                  v-for="(guardian, index) in data.person.guardians"
+                  :key="index"
+                  class="mb-4"
+                >
+                  <v-card-title>
+                    {{
+                      $t(
+                        "paweljong.event_registration.form.steps.guardians.counter",
+                        { i: index + 1 },
+                      )
+                    }}
+                  </v-card-title>
+                  <v-card-text>
                     <v-row>
-                      <v-col v-if="isFieldVisible('street')" cols="12" lg="4">
-                        <div aria-required="true">
+                      <v-col cols="12" md="6">
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
                           <v-text-field
                             outlined
-                            v-model="data.person.address.street"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.street.label')"
+                            v-model="guardian.firstName"
+                            :label="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.first_name.label',
+                              )
+                            "
+                            :hint="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.first_name.help_text',
+                              )
+                            "
                             required
-                            :rules="$rules().required.build()"
+                            :rules="
+                              index === 0 ? $rules().required.build() : []
+                            "
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col v-if="isFieldVisible('housenumber')" cols="12" lg="4">
-                        <div aria-required="true">
+                      <v-col cols="12" md="6">
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
                           <v-text-field
                             outlined
-                            v-model="data.person.address.housenumber"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.housenumber.label')"
+                            v-model="guardian.lastName"
+                            :label="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.last_name.label',
+                              )
+                            "
+                            :hint="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.last_name.help_text',
+                              )
+                            "
                             required
-                            :rules="$rules().required.build()"
+                            :rules="
+                              index === 0 ? $rules().required.build() : []
+                            "
                           ></v-text-field>
                         </div>
                       </v-col>
                     </v-row>
                     <v-row>
-                      <v-col v-if="isFieldVisible('postal_code')" cols="12" lg="4">
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.person.address.postalCode"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.postal_code.label')"
-                            required
-                            :rules="$rules().required.build()"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                      <v-col v-if="isFieldVisible('place')" cols="12" lg="4">
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.person.address.place"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.place.label')"
-                            required
-                            :rules="$rules().required.build()"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                    </v-row>
-                    <v-row v-if="isFieldVisible('school_details')">
-                      <v-col cols="12" lg="4">
-                        <div aria-required="true">
+                      <v-col cols="12" md="6">
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
                           <v-text-field
                             outlined
-                            v-model="data.school"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school.help_text')"
+                            v-model="guardian.email"
+                            :label="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.email.label',
+                              )
+                            "
                             required
-                            :rules="$rules().required.build()"
+                            :rules="
+                              index === 0
+                                ? $rules().required.build(rules.email)
+                                : []
+                            "
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col cols="12" lg="4">
-                        <div aria-required="true">
+                      <v-col cols="12" md="6">
+                        <div :aria-required="index === 0 ? 'true' : 'false'">
                           <v-text-field
                             outlined
-                            v-model="data.schoolPlace"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_place.help_text')"
+                            v-model="guardian.mobileNumber"
+                            :label="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.mobile_number.label',
+                              )
+                            "
+                            :hint="
+                              $t(
+                                'paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text',
+                              )
+                            "
                             required
-                            :rules="$rules().required.build()"
+                            :rules="
+                              index === 0 ? $rules().required.build() : []
+                            "
                           ></v-text-field>
                         </div>
                       </v-col>
-                      <v-col cols="12" lg="4">
-                        <div aria-required="true">
-                          <v-text-field
-                            outlined
-                            v-model="data.schoolClass"
-                            :label="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.contact_details.fields.school_class.help_text')"
-                            required
-                            :rules="$rules().required.build()"
-                          ></v-text-field>
-                        </div>
-                      </v-col>
-                    </v-row>
-                  </v-form>
-                </div>
-                <v-divider class="mb-4" />
-                <control-row
-                  :step="step"
-                  @set-step="setStep"
-                  :next-disabled="!validationStatuses['contact_details']"
-                />
-              </v-stepper-content>
-
-              <v-stepper-content :step="getStepIndex('guardians')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('guardians')) }}</h2>
-                <div class="mb-4">
-                  <v-form  v-model="validationStatuses['guardians']">
-                    <v-card v-for="(guardian, index) in data.person.guardians" :key="index" class="mb-4">
-                      <v-card-title>
-                        {{ $t("paweljong.event_registration.form.steps.guardians.counter", { i: index+1 }) }}
-                      </v-card-title>
-                      <v-card-text>
-                        <v-row>
-                          <v-col cols="12" md="6">
-                            <div :aria-required="index === 0 ? 'true' : 'false'">
-                              <v-text-field
-                                outlined
-                                v-model="guardian.firstName"
-                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.label')"
-                                :hint="$t('paweljong.event_registration.form.steps.guardians.fields.first_name.help_text')"
-                                required
-                                :rules="index === 0 ? $rules().required.build() : []"
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                          <v-col cols="12" md="6">
-                            <div :aria-required="index === 0 ? 'true' : 'false'">
-                              <v-text-field
-                                outlined
-                                v-model="guardian.lastName"
-                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.label')"
-                                :hint="$t('paweljong.event_registration.form.steps.guardians.fields.last_name.help_text')"
-                                required
-                                :rules="index === 0 ? $rules().required.build() : []"
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                        </v-row>
-                        <v-row>
-                          <v-col cols="12" md="6">
-                            <div :aria-required="index === 0 ? 'true' : 'false'">
-                              <v-text-field
-                                outlined
-                                v-model="guardian.email"
-                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.email.label')"
-                                required
-                                :rules="index === 0 ? $rules().required.build(rules.email) : []"
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                          <v-col cols="12" md="6">
-                            <div :aria-required="index === 0 ? 'true' : 'false'">
-                              <v-text-field
-                                outlined
-                                v-model="guardian.mobileNumber"
-                                :label="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.label')"
-                                :hint="$t('paweljong.event_registration.form.steps.guardians.fields.mobile_number.help_text')"
-                                required
-                                :rules="index === 0 ? $rules().required.build() : []"
-                              ></v-text-field>
-                            </div>
-                          </v-col>
-                        </v-row>
-                      </v-card-text>
-                    </v-card>
-                  </v-form>
-                  <v-row class="mb-4">
-                    <v-col>
-                      <v-spacer />
-                      <secondary-action-button
-                        @click="addGuardian"
-                        class="btn-multiline"
-                      >
-                        <span class="text-wrap">
-                          {{ $t("paweljong.event_registration.form.steps.guardians.add") }}
-                        </span>
-                      </secondary-action-button>
-                    </v-col>
-                  </v-row>
-                </div>
-                <v-divider class="mb-4" />
-                <control-row
-                  :step="step"
-                  @set-step="setStep"
-                  :next-disabled="!validationStatuses['guardians']"
-                />
-              </v-stepper-content>
-
-              <v-stepper-content :step="getStepIndex('additional')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('additional')) }}</h2>
-                <div class="mb-4">
-                  <v-form  v-model="validationStatuses['additional']">
-                    <v-row>
-                      <v-col>
-                        <v-text-field
-                          outlined
-                          v-model="data.medicalInformation"
-                          :label="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.label')"
-                          :hint="$t('paweljong.event_registration.form.steps.additional.fields.medical_information.help_text')"
-                        ></v-text-field>
-                      </v-col>
-                    </v-row>
-                    <v-row v-for="additionalField in event.additionalFields" :key="`additional-field-${additionalField.id}`">
-                      <v-col>
-                        <div :aria-required="additionalField.required ? 'true' : 'false'">
-                          <component
-                            :is="fieldComponentForAdditionalField(additionalField)"
-                            v-model="data.additionalFields[additionalField.id]"
-                            outlined
-                            :label="additionalField.title"
-                            :hint="additionalField.helpText"
-                            persistent-hint
-                            :required="additionalField.required"
-                            :rules="additionalField.required ? $rules().required.build() : []"
-                          />
-                        </div>
-                      </v-col>
-                    </v-row>
-                  </v-form>
-                </div>
-                <v-divider class="mb-4" />
-                <control-row
-                  :step="step"
-                  @set-step="setStep"
-                  :next-disabled="!validationStatuses['additional']"
-                />
-              </v-stepper-content>
-
-              <v-stepper-content v-if="isStepEnabled('financial')" :step="getStepIndex('financial')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('financial')) }}</h2>
-                <div class="mb-4">
-                  <message-box type="info" class="mb-4">
-                    {{ $t("paweljong.event_registration.form.steps.financial.help_text.cost_info") }}
-                  </message-box>
-                  <v-simple-table class="mb-4">
-                    <template v-slot:default>
-                      <tbody>
-                        <tr>
-                          <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.min_cost") }}</td>
-                          <td>{{ `${event.minCost} €` }}</td>
-                        </tr>
-                        <tr>
-                          <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.cost") }}</td>
-                          <td>{{ `${event.cost} €` }}</td>
-                        </tr>
-                        <tr v-if="event.maxCost">
-                          <td>{{ $t("paweljong.event_registration.form.steps.financial.help_text.max_cost") }}</td>
-                          <td>{{ `${event.maxCost} €` }}</td>
-                        </tr>
-                      </tbody>
-                    </template>
-                  </v-simple-table>
-                  <v-form v-model="validationStatuses['financial']">
-                    <v-row>
-                      <v-col>
-                        <div aria-required="true">
-                          <v-select
-                            :items="paweljongPaymentChoices"
-                            item-text="text"
-                            item-value="variant"
-                            outlined
-                            v-model="data.payment.paymentMethod"
-                            :label="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.label')"
-                            :hint="$t('paweljong.event_registration.form.steps.financial.fields.payment_method.help_text')"
-                            persistent-hint
-                            required
-                            :disabled="data.payment.amount === 0"
-                            :rules="data.payment.amount === 0 ? [] : $rules().required.build()"
-                          ></v-select>
-                        </div>
-                      </v-col>
-                      <v-col>
-                        <div aria-required="true">
-                          <positive-small-integer-field
-                            outlined
-                            v-model="data.payment.amount"
-                            :label="$t('paweljong.event_registration.form.steps.financial.fields.amount.label')"
-                            :rules="$rules().build(rules.amount)"
-                          />
-                        </div>
-                      </v-col>
                     </v-row>
-                  </v-form>
-                </div>
-                <v-divider class="mb-4" />
-                <control-row
-                  :step="step"
-                  @set-step="setStep"
-                  :next-disabled="!validationStatuses['financial']"
-                />
-              </v-stepper-content>
+                  </v-card-text>
+                </v-card>
+              </v-form>
+              <v-row class="mb-4">
+                <v-col>
+                  <v-spacer />
+                  <secondary-action-button
+                    @click="addGuardian"
+                    class="btn-multiline"
+                  >
+                    <span class="text-wrap">
+                      {{
+                        $t(
+                          "paweljong.event_registration.form.steps.guardians.add",
+                        )
+                      }}
+                    </span>
+                  </secondary-action-button>
+                </v-col>
+              </v-row>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="!validationStatuses['guardians']"
+            />
+          </v-stepper-content>
 
-              <v-stepper-content :step="getStepIndex('consent')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('consent')) }}</h2>
-                <div class="mb-4">
-                  <v-card v-for="term in event.terms" :key="`term-card-${term.id}`">
-                    <v-card-title>
-                      {{ term.title }}
-                    </v-card-title>
-                    <v-card-text v-html="term.term" />
-                  </v-card>
-                  <v-form v-model="validationStatuses['consent']">
-                    <div v-for="term in event.terms" :key="`term-checkbox-${term.id}`" aria-required="true">
-                      <v-checkbox
-                        required
-                        :rules="$rules().required.build()"
-                        :label="term.confirmationText"
-                        v-model="data.terms[term.id]"
+          <v-stepper-content :step="getStepIndex('additional')">
+            <h2 class="text-h6 mb-4">
+              {{ $t(getStepTitleKey("additional")) }}
+            </h2>
+            <div class="mb-4">
+              <v-form v-model="validationStatuses['additional']">
+                <v-row>
+                  <v-col>
+                    <v-text-field
+                      outlined
+                      v-model="data.medicalInformation"
+                      :label="
+                        $t(
+                          'paweljong.event_registration.form.steps.additional.fields.medical_information.label',
+                        )
+                      "
+                      :hint="
+                        $t(
+                          'paweljong.event_registration.form.steps.additional.fields.medical_information.help_text',
+                        )
+                      "
+                    ></v-text-field>
+                  </v-col>
+                </v-row>
+                <v-row
+                  v-for="additionalField in event.additionalFields"
+                  :key="`additional-field-${additionalField.id}`"
+                >
+                  <v-col>
+                    <div
+                      :aria-required="
+                        additionalField.required ? 'true' : 'false'
+                      "
+                    >
+                      <component
+                        :is="fieldComponentForAdditionalField(additionalField)"
+                        v-model="data.additionalFields[additionalField.id]"
+                        outlined
+                        :label="additionalField.title"
+                        :hint="additionalField.helpText"
+                        persistent-hint
+                        :required="additionalField.required"
+                        :rules="
+                          additionalField.required
+                            ? $rules().required.build()
+                            : []
+                        "
                       />
                     </div>
+                  </v-col>
+                </v-row>
+              </v-form>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="!validationStatuses['additional']"
+            />
+          </v-stepper-content>
+
+          <v-stepper-content
+            v-if="isStepEnabled('financial')"
+            :step="getStepIndex('financial')"
+          >
+            <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("financial")) }}</h2>
+            <div class="mb-4">
+              <message-box type="info" class="mb-4">
+                {{
+                  $t(
+                    "paweljong.event_registration.form.steps.financial.help_text.cost_info",
+                  )
+                }}
+              </message-box>
+              <v-simple-table class="mb-4">
+                <template v-slot:default>
+                  <tbody>
+                    <tr>
+                      <td>
+                        {{
+                          $t(
+                            "paweljong.event_registration.form.steps.financial.help_text.min_cost",
+                          )
+                        }}
+                      </td>
+                      <td>{{ `${event.minCost} €` }}</td>
+                    </tr>
+                    <tr>
+                      <td>
+                        {{
+                          $t(
+                            "paweljong.event_registration.form.steps.financial.help_text.cost",
+                          )
+                        }}
+                      </td>
+                      <td>{{ `${event.cost} €` }}</td>
+                    </tr>
+                    <tr v-if="event.maxCost">
+                      <td>
+                        {{
+                          $t(
+                            "paweljong.event_registration.form.steps.financial.help_text.max_cost",
+                          )
+                        }}
+                      </td>
+                      <td>{{ `${event.maxCost} €` }}</td>
+                    </tr>
+                  </tbody>
+                </template>
+              </v-simple-table>
+              <v-form v-model="validationStatuses['financial']">
+                <v-row>
+                  <v-col>
                     <div aria-required="true">
-                      <v-checkbox
-                        v-if="event.dateRetraction"
+                      <v-select
+                        :items="paweljongPaymentChoices"
+                        item-text="text"
+                        item-value="variant"
+                        outlined
+                        v-model="data.payment.paymentMethod"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.financial.fields.payment_method.label',
+                          )
+                        "
+                        :hint="
+                          $t(
+                            'paweljong.event_registration.form.steps.financial.fields.payment_method.help_text',
+                          )
+                        "
+                        persistent-hint
                         required
-                        :rules="$rules().required.build()"
-                        :label="$t('paweljong.event_registration.form.steps.consent.fields.retraction_consent.label', { date: $d($parseISODate(event.dateRetraction, 'short')) })"
-                        v-model="data.retractionConsent"
+                        :disabled="data.payment.amount === 0"
+                        :rules="
+                          data.payment.amount === 0
+                            ? []
+                            : $rules().required.build()
+                        "
+                      ></v-select>
+                    </div>
+                  </v-col>
+                  <v-col>
+                    <div aria-required="true">
+                      <positive-small-integer-field
+                        outlined
+                        v-model="data.payment.amount"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.financial.fields.amount.label',
+                          )
+                        "
+                        :rules="$rules().build(rules.amount)"
                       />
                     </div>
-                  </v-form>
+                  </v-col>
+                </v-row>
+              </v-form>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="!validationStatuses['financial']"
+            />
+          </v-stepper-content>
+
+          <v-stepper-content :step="getStepIndex('consent')">
+            <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("consent")) }}</h2>
+            <div class="mb-4">
+              <v-card v-for="term in event.terms" :key="`term-card-${term.id}`">
+                <v-card-title>
+                  {{ term.title }}
+                </v-card-title>
+                <v-card-text v-html="term.term" />
+              </v-card>
+              <v-form v-model="validationStatuses['consent']">
+                <div
+                  v-for="term in event.terms"
+                  :key="`term-checkbox-${term.id}`"
+                  aria-required="true"
+                >
+                  <v-checkbox
+                    required
+                    :rules="$rules().required.build()"
+                    :label="term.confirmationText"
+                    v-model="data.terms[term.id]"
+                  />
                 </div>
-                <v-divider class="mb-4" />
+                <div aria-required="true">
+                  <v-checkbox
+                    v-if="event.dateRetraction"
+                    required
+                    :rules="$rules().required.build()"
+                    :label="
+                      $t(
+                        'paweljong.event_registration.form.steps.consent.fields.retraction_consent.label',
+                        {
+                          date: $d(
+                            $parseISODate(event.dateRetraction, 'short'),
+                          ),
+                        },
+                      )
+                    "
+                    v-model="data.retractionConsent"
+                  />
+                </div>
+              </v-form>
+            </div>
+            <v-divider class="mb-4" />
+            <control-row
+              :step="step"
+              @set-step="setStep"
+              :next-disabled="!validationStatuses['consent']"
+            />
+          </v-stepper-content>
+
+          <v-stepper-content :step="getStepIndex('confirm')">
+            <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey("confirm")) }}</h2>
+            <div class="mb-4">
+              <v-text-field
+                outlined
+                v-model="data.comment"
+                :label="
+                  $t(
+                    'paweljong.event_registration.form.steps.confirm.fields.comment.label',
+                  )
+                "
+              ></v-text-field>
+            </div>
+            <v-divider class="my-4" />
+
+            <!-- TODO: Add summary -->
+
+            <message-box type="info" class="mb-4">
+              {{ $t("paweljong.event_registration.form.steps.confirm.hint") }}
+            </message-box>
+            <ApolloMutation
+              :mutation="require('./eventRegistrationMutation.graphql')"
+              :variables="{
+                event: event.id,
+                eventRegistration: dataForSubmit,
+              }"
+              @done="eventRegistrationDone"
+            >
+              <template #default="{ mutate, loading, error }">
                 <control-row
                   :step="step"
+                  final-step
                   @set-step="setStep"
-                  :next-disabled="!validationStatuses['consent']"
+                  @confirm="mutate"
+                  :next-loading="loading"
                 />
-              </v-stepper-content>
-    
-              <v-stepper-content :step="getStepIndex('confirm')">
-                <h2 class="text-h6 mb-4">{{ $t(getStepTitleKey('confirm')) }}</h2>
-                <div class="mb-4">
-                  <v-text-field
-                    outlined
-                    v-model="data.comment"
-                    :label="$t('paweljong.event_registration.form.steps.confirm.fields.comment.label')"
-                  ></v-text-field>
-                </div>
-                <v-divider class="my-4" />
-
-                <!-- TODO: Add summary -->
-    
-                <message-box type="info" class="mb-4">
-                  {{ $t("paweljong.event_registration.form.steps.confirm.hint") }}
-                </message-box>
-                <ApolloMutation
-                  :mutation="require('./eventRegistrationMutation.graphql')"
-                  :variables="{
-                    event: event.id,
-                    eventRegistration: dataForSubmit,
-                  }"
-                  @done="eventRegistrationDone"
-                >
-                  <template #default="{ mutate, loading, error }">
-                    <control-row
-                      :step="step"
-                      final-step
-                      @set-step="setStep"
-                      @confirm="mutate"
-                      :next-loading="loading"
-                    />
-                    <v-alert v-if="error" type="error" outlined>{{ error.message }}</v-alert>
-                  </template>
-                </ApolloMutation>
-              </v-stepper-content>
-            </v-stepper-items>
-          </v-stepper>
-          <v-row v-if="currentParticipantHelpText || currentGuardianHelpText">
-            <v-col v-if="currentParticipantHelpText" cols="12" md="6">
-              <v-card>
-                <v-card-title>
-                  {{ $t("paweljong.event_registration.form.help_text.participant") }}
-                </v-card-title>
-                <v-card-text>{{ currentParticipantHelpText }}</v-card-text>
-              </v-card>
-            </v-col>
-            <v-col v-if="currentGuardianHelpText" cols="12" md="6">
-              <v-card>
-                <v-card-title>
-                  {{ $t("paweljong.event_registration.form.help_text.guardian") }}
-                </v-card-title>
-                <v-card-text>{{ currentGuardianHelpText }}</v-card-text>
-              </v-card>
-            </v-col>
-          </v-row>
-      </div>
-      <div v-else-if="$apollo.queries.event.loading">
-        <v-skeleton-loader type="heading, text, actions" />
-      </div>
+                <v-alert v-if="error" type="error" outlined>{{
+                  error.message
+                }}</v-alert>
+              </template>
+            </ApolloMutation>
+          </v-stepper-content>
+        </v-stepper-items>
+      </v-stepper>
+      <v-row v-if="currentParticipantHelpText || currentGuardianHelpText">
+        <v-col v-if="currentParticipantHelpText" cols="12" md="6">
+          <v-card>
+            <v-card-title>
+              {{
+                $t("paweljong.event_registration.form.help_text.participant")
+              }}
+            </v-card-title>
+            <v-card-text>{{ currentParticipantHelpText }}</v-card-text>
+          </v-card>
+        </v-col>
+        <v-col v-if="currentGuardianHelpText" cols="12" md="6">
+          <v-card>
+            <v-card-title>
+              {{ $t("paweljong.event_registration.form.help_text.guardian") }}
+            </v-card-title>
+            <v-card-text>{{ currentGuardianHelpText }}</v-card-text>
+          </v-card>
+        </v-col>
+      </v-row>
+    </div>
+    <div v-else-if="$apollo.queries.event.loading">
+      <v-skeleton-loader type="heading, text, actions" />
     </div>
-  </template>
-  
-  <script>
-  import { eventBySlug } from "../event/events.graphql";
-  import { gqlPaweljongPaymentChoices, gqlPaweljongSitePreferences, whoAmI } from "./helpers.graphql";
-  import {
-    mailDomainsForUser,
-    disallowedLocalParts,
-  } from "aleksis.apps.postbuero/components/mail_addresses/mailAddresses.graphql";
+  </div>
+</template>
 
-  import eventAdditionalFieldMixin from "../event_additional_field/eventAdditionalFieldMixin";
-  import formRulesMixin from "aleksis.core/mixins/formRulesMixin";
+<script>
+import { eventBySlug } from "../event/events.graphql";
+import {
+  gqlPaweljongPaymentChoices,
+  gqlPaweljongSitePreferences,
+  whoAmI,
+} from "./helpers.graphql";
+import {
+  mailDomainsForUser,
+  disallowedLocalParts,
+} from "aleksis.apps.postbuero/components/mail_addresses/mailAddresses.graphql";
 
-  export default {
-    name: "EventRegistrationForm",
-    apollo: {
-      event: {
-        query: eventBySlug,
-        variables() {
-          return {
-            slug: this.slug,
-          };
-        },
-      },
-      paweljongPaymentChoices: gqlPaweljongPaymentChoices,
-      paweljongSitePreferences: gqlPaweljongSitePreferences,
-      mailDomains: mailDomainsForUser,
-      disallowedLocalParts: disallowedLocalParts,
-      whoAmI: {
-        query: whoAmI,
-        result({ data }) {
-          if (data && data.whoAmI && data.whoAmI.person) {
-            const { id, __typename, street, housenumber, postalCode, place, guardians, ...filteredPerson } = data.whoAmI.person;
-            this.data.person = filteredPerson;
-            
-            const filteredGuardians = guardians.map(({ __typename, ...filteredGuardian }) => filteredGuardian);
-            this.data.person.guardians = filteredGuardians;
+import eventAdditionalFieldMixin from "../event_additional_field/eventAdditionalFieldMixin";
+import formRulesMixin from "aleksis.core/mixins/formRulesMixin";
 
-            this.data.person.address = {
-              street: street,
-              housenumber: housenumber,
-              postalCode: postalCode,
-              place: place,
-            };
-          }
-        },
-        skip() {
-          return this.$root.whoAmI?.person?.isDummy
-        },
+export default {
+  name: "EventRegistrationForm",
+  apollo: {
+    event: {
+      query: eventBySlug,
+      variables() {
+        return {
+          slug: this.slug,
+        };
       },
     },
-    mixins: [eventAdditionalFieldMixin, formRulesMixin],
-    methods: {
-      setStep(step) {
-        this.step = step;
-        this.valid = false;
-      },
-      eventRegistrationDone({ data }) {
-        if (data.sendEventRegistration.ok) {
-          this.eventRegistrationSent = true;
+    paweljongPaymentChoices: gqlPaweljongPaymentChoices,
+    paweljongSitePreferences: gqlPaweljongSitePreferences,
+    mailDomains: mailDomainsForUser,
+    disallowedLocalParts: disallowedLocalParts,
+    whoAmI: {
+      query: whoAmI,
+      result({ data }) {
+        if (data && data.whoAmI && data.whoAmI.person) {
+          const {
+            id,
+            __typename,
+            street,
+            housenumber,
+            postalCode,
+            place,
+            guardians,
+            ...filteredPerson
+          } = data.whoAmI.person;
+          this.data.person = filteredPerson;
+
+          const filteredGuardians = guardians.map(
+            ({ __typename, ...filteredGuardian }) => filteredGuardian,
+          );
+          this.data.person.guardians = filteredGuardians;
+
+          this.data.person.address = {
+            street: street,
+            housenumber: housenumber,
+            postalCode: postalCode,
+            place: place,
+          };
         }
       },
-      isFieldVisible(fieldName) {
-        return this.event?.contactInformationVisibleFields.includes(fieldName);
-      },
-      addGuardian() {
-        this.data.person.guardians.push({
-            firstName: "",
-            lastName: "",
-            email: "",
-            mobileNumber: "",
-        });
-      },
-      isStepEnabled(stepName) {
-        return this.steps.some((s) => s.name === stepName);
-      },
-      getStepIndex(stepName) {
-        return this.steps.findIndex((s) => s.name === stepName) + 1;
-      },
-      getStepTitleKey(stepName) {
-        return this.steps.find((s) => s.name === stepName)?.titleKey;
-      },
-      setValidationStatus(stepName, validationStatus) {
-        this.validationStatuses[stepName] = validationStatus;
-      },
-      getValidationStatus(stepName) {
-        if (this.validationStatuses[stepName]) {
-          return this.validationStatuses[stepName];
-        }
-        return false;
+      skip() {
+        return this.$root.whoAmI?.person?.isDummy;
       },
     },
-    props: {
-      slug: {
-        type: String,
-        required: true,
+  },
+  mixins: [eventAdditionalFieldMixin, formRulesMixin],
+  methods: {
+    setStep(step) {
+      this.step = step;
+      this.valid = false;
+    },
+    eventRegistrationDone({ data }) {
+      if (data.sendEventRegistration.ok) {
+        this.eventRegistrationSent = true;
       }
     },
-    computed: {
-      rules() {
-        return {
-          name: [
-            (v) => !!v || this.$t("order.rules.name.required"),
-            (v) => v.length <= 255 || this.$t("order.rules.name.max"),
-          ],
-          email: [
-            (v) => /.+@.+\..+/.test(v) || this.$t("paweljong.event_registration.form.rules.email.valid"),
-          ],
-          confirmEmail: [
-            (v) => this.data.user.email == v || this.$t("paweljong.event_registration.form.rules.confirm_email.no_match"),
-          ],
-          emailLocalPart: [
-            (v) =>
-              /^\w+([.!#$%&'*+-\/=?^_`{|}~]?\w+)*$/.test(v) ||
-              this.$t(
-                "postbuero.mail_addresses.data_table.errors.local_part_invalid_characters",
-              ),
-            (v) =>
-              this.disallowedLocalParts.indexOf(v) === -1 ||
-              this.$t(
-                "postbuero.mail_addresses.data_table.errors.local_part_disallowed",
-              ),
-          ],
-          confirmPassword: [
-            (v) => this.data.user.password == v || this.$t("paweljong.event_registration.form.rules.confirm_password.no_match"),
-          ],
-          amount: [
-            (v) => v >= this.event.minCost || this.$t("paweljong.event_registration.form.rules.amount.too_low"),
-            (v) => this.event.maxCost === null || v <= this.event.maxCost || this.$t("paweljong.event_registration.form.rules.amount.too_high"),
-          ],
-        };
-      },
-      dataForSubmit() {
-        const { confirmEmail, confirmPassword, ...filteredUserData } = this.data.user;
+    isFieldVisible(fieldName) {
+      return this.event?.contactInformationVisibleFields.includes(fieldName);
+    },
+    addGuardian() {
+      this.data.person.guardians.push({
+        firstName: "",
+        lastName: "",
+        email: "",
+        mobileNumber: "",
+      });
+    },
+    isStepEnabled(stepName) {
+      return this.steps.some((s) => s.name === stepName);
+    },
+    getStepIndex(stepName) {
+      return this.steps.findIndex((s) => s.name === stepName) + 1;
+    },
+    getStepTitleKey(stepName) {
+      return this.steps.find((s) => s.name === stepName)?.titleKey;
+    },
+    setValidationStatus(stepName, validationStatus) {
+      this.validationStatuses[stepName] = validationStatus;
+    },
+    getValidationStatus(stepName) {
+      if (this.validationStatuses[stepName]) {
+        return this.validationStatuses[stepName];
+      }
+      return false;
+    },
+  },
+  props: {
+    slug: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    rules() {
+      return {
+        name: [
+          (v) => !!v || this.$t("order.rules.name.required"),
+          (v) => v.length <= 255 || this.$t("order.rules.name.max"),
+        ],
+        email: [
+          (v) =>
+            /.+@.+\..+/.test(v) ||
+            this.$t("paweljong.event_registration.form.rules.email.valid"),
+        ],
+        confirmEmail: [
+          (v) =>
+            this.data.user.email == v ||
+            this.$t(
+              "paweljong.event_registration.form.rules.confirm_email.no_match",
+            ),
+        ],
+        emailLocalPart: [
+          (v) =>
+            /^\w+([.!#$%&'*+-\/=?^_`{|}~]?\w+)*$/.test(v) ||
+            this.$t(
+              "postbuero.mail_addresses.data_table.errors.local_part_invalid_characters",
+            ),
+          (v) =>
+            this.disallowedLocalParts.indexOf(v) === -1 ||
+            this.$t(
+              "postbuero.mail_addresses.data_table.errors.local_part_disallowed",
+            ),
+        ],
+        confirmPassword: [
+          (v) =>
+            this.data.user.password == v ||
+            this.$t(
+              "paweljong.event_registration.form.rules.confirm_password.no_match",
+            ),
+        ],
+        amount: [
+          (v) =>
+            v >= this.event.minCost ||
+            this.$t("paweljong.event_registration.form.rules.amount.too_low"),
+          (v) =>
+            this.event.maxCost === null ||
+            v <= this.event.maxCost ||
+            this.$t("paweljong.event_registration.form.rules.amount.too_high"),
+        ],
+      };
+    },
+    dataForSubmit() {
+      const { confirmEmail, confirmPassword, ...filteredUserData } =
+        this.data.user;
 
-        let data = {
-          ...this.data,
-          additionalFields: JSON.stringify(this.data.additionalFields),
-          terms: JSON.stringify(this.data.terms),
-          user: filteredUserData,
-        };
+      let data = {
+        ...this.data,
+        additionalFields: JSON.stringify(this.data.additionalFields),
+        terms: JSON.stringify(this.data.terms),
+        user: filteredUserData,
+      };
 
-        if (this.event.cost == 0 && this.event.maxCost == 0) {
-          const { payment, ...filteredData } = data;
-          data = filteredData;
-        }
-        if (!this.data.email.localPart && !this.data.email.domain) {
-          const { email, ...filteredData } = data;
-          data = filteredData;
-        }
-        if (!this.data.user.username && !this.data.user.email && !this.data.user.password) {
-          const { user, ...filteredData } = data;
-          data = filteredData;
-        }
-        return data;
-      },
-      steps() {
-        return [
-          ... (this.$root.whoAmI.isAnonymous) ? [{
-            name: "email",
-            titleKey: "paweljong.event_registration.form.steps.email.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationEmailParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationEmailGuardianHelpText,
-          }] : [],
-          ... (this.$root.whoAmI.isAnonymous) ? [{
-            name: "register",
-            titleKey: "paweljong.event_registration.form.steps.register.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationRegisterParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationRegisterGuardianHelpText,
-          }] : [],
-          ... (this.event?.contactInformationVisibleFields.length) ? [{
-            name: "contact_details",
-            titleKey: "paweljong.event_registration.form.steps.contact_details.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationContactDetailsParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationContactDetailsGuardianHelpText,
-          }] : [],
-          {
-            name: "guardians",
-            titleKey: "paweljong.event_registration.form.steps.guardians.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationGuardiansParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationGuardiansGuardianHelpText,
-          },
-          {
-            name: "additional",
-            titleKey: "paweljong.event_registration.form.steps.additional.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationAdditionalParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationAdditionalGuardianHelpText,
-          },
-          ... (this.event.cost !== 0 && this.event.maxCost !== 0) ? [{
-            name: "financial",
-            titleKey: "paweljong.event_registration.form.steps.financial.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationFinancialParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationFinancialGuardianHelpText,
-          }] : [],
-          {
-            name: "consent",
-            titleKey: "paweljong.event_registration.form.steps.consent.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationConsentParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationConsentGuardianHelpText,
-          },
-          {
-            name: "confirm",
-            titleKey: "paweljong.event_registration.form.steps.confirm.title",
-            participantHelpText: this.paweljongSitePreferences?.eventRegistrationConfirmParticipantHelpText,
-            guardianHelpText: this.paweljongSitePreferences?.eventRegistrationConfirmGuardianHelpText,
-          },
-        ];
-      },
-      currentParticipantHelpText() {
-        return this.steps[this.step - 1].participantHelpText;
-      },
-      currentGuardianHelpText() {
-        return this.steps[this.step - 1].guardianHelpText;
-      },
+      if (this.event.cost == 0 && this.event.maxCost == 0) {
+        const { payment, ...filteredData } = data;
+        data = filteredData;
+      }
+      if (!this.data.email.localPart && !this.data.email.domain) {
+        const { email, ...filteredData } = data;
+        data = filteredData;
+      }
+      if (
+        !this.data.user.username &&
+        !this.data.user.email &&
+        !this.data.user.password
+      ) {
+        const { user, ...filteredData } = data;
+        data = filteredData;
+      }
+      return data;
     },
-    data() {
-      return {
-        validationStatuses: {},
-        eventRegistrationSent: false,
-        step: 1,
-        emailMode: null,
-        data: {
-          email: {
-            localPart: "",
-            domain: "",
-          },
-          person: {
-            firstName: "",
-            lastName: "",
-            dateOfBirth: "",
-            sex: "",
-            address: {
-              street: "",
-              housenumber: "",
-              postalCode: "",
-              place: "",
-            },
-            guardians: [
+    steps() {
+      return [
+        ...(this.$root.whoAmI.isAnonymous
+          ? [
               {
-                firstName: "",
-                lastName: "",
-                email: "",
-                mobileNumber: "",
+                name: "email",
+                titleKey: "paweljong.event_registration.form.steps.email.title",
+                participantHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationEmailParticipantHelpText,
+                guardianHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationEmailGuardianHelpText,
               },
-            ],
-            mobileNumber: "",
-          },
-          user: {
-            username: "",
-            email: "",
-            confirmEmail: "",
-            password: "",
-            confirmPassword: "",
-          },
-          school: "",
-          schoolPlace: "",
-          schoolClass: "",
-          payment: {
-            paymentMethod: "",
-            amount: null,
-          },
-          medicalInformation: "",
-          comment: "",
-          additionalFields: {},
-          terms: {},
-          retractionConsent: false,
+            ]
+          : []),
+        ...(this.$root.whoAmI.isAnonymous
+          ? [
+              {
+                name: "register",
+                titleKey:
+                  "paweljong.event_registration.form.steps.register.title",
+                participantHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationRegisterParticipantHelpText,
+                guardianHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationRegisterGuardianHelpText,
+              },
+            ]
+          : []),
+        ...(this.event?.contactInformationVisibleFields.length
+          ? [
+              {
+                name: "contact_details",
+                titleKey:
+                  "paweljong.event_registration.form.steps.contact_details.title",
+                participantHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationContactDetailsParticipantHelpText,
+                guardianHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationContactDetailsGuardianHelpText,
+              },
+            ]
+          : []),
+        {
+          name: "guardians",
+          titleKey: "paweljong.event_registration.form.steps.guardians.title",
+          participantHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationGuardiansParticipantHelpText,
+          guardianHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationGuardiansGuardianHelpText,
         },
-      };
+        {
+          name: "additional",
+          titleKey: "paweljong.event_registration.form.steps.additional.title",
+          participantHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationAdditionalParticipantHelpText,
+          guardianHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationAdditionalGuardianHelpText,
+        },
+        ...(this.event.cost !== 0 && this.event.maxCost !== 0
+          ? [
+              {
+                name: "financial",
+                titleKey:
+                  "paweljong.event_registration.form.steps.financial.title",
+                participantHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationFinancialParticipantHelpText,
+                guardianHelpText:
+                  this.paweljongSitePreferences
+                    ?.eventRegistrationFinancialGuardianHelpText,
+              },
+            ]
+          : []),
+        {
+          name: "consent",
+          titleKey: "paweljong.event_registration.form.steps.consent.title",
+          participantHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationConsentParticipantHelpText,
+          guardianHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationConsentGuardianHelpText,
+        },
+        {
+          name: "confirm",
+          titleKey: "paweljong.event_registration.form.steps.confirm.title",
+          participantHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationConfirmParticipantHelpText,
+          guardianHelpText:
+            this.paweljongSitePreferences
+              ?.eventRegistrationConfirmGuardianHelpText,
+        },
+      ];
     },
-  };
-  </script>
-  
-  <style>
-  .btn-multiline > span {
-    width: 100%
-  }
-  </style>
-  
\ No newline at end of file
+    currentParticipantHelpText() {
+      return this.steps[this.step - 1].participantHelpText;
+    },
+    currentGuardianHelpText() {
+      return this.steps[this.step - 1].guardianHelpText;
+    },
+  },
+  data() {
+    return {
+      validationStatuses: {},
+      eventRegistrationSent: false,
+      step: 1,
+      emailMode: null,
+      data: {
+        email: {
+          localPart: "",
+          domain: "",
+        },
+        person: {
+          firstName: "",
+          lastName: "",
+          dateOfBirth: "",
+          sex: "",
+          address: {
+            street: "",
+            housenumber: "",
+            postalCode: "",
+            place: "",
+          },
+          guardians: [
+            {
+              firstName: "",
+              lastName: "",
+              email: "",
+              mobileNumber: "",
+            },
+          ],
+          mobileNumber: "",
+        },
+        user: {
+          username: "",
+          email: "",
+          confirmEmail: "",
+          password: "",
+          confirmPassword: "",
+        },
+        school: "",
+        schoolPlace: "",
+        schoolClass: "",
+        payment: {
+          paymentMethod: "",
+          amount: null,
+        },
+        medicalInformation: "",
+        comment: "",
+        additionalFields: {},
+        terms: {},
+        retractionConsent: false,
+      },
+    };
+  },
+};
+</script>
+
+<style>
+.btn-multiline > span {
+  width: 100%;
+}
+</style>
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
index a61c153..83d5dd5 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/helpers.graphql
@@ -48,6 +48,6 @@ query gqlPaweljongSitePreferences {
     eventRegistrationAdditionalGuardianHelpText
     eventRegistrationFinancialGuardianHelpText
     eventRegistrationConsentGuardianHelpText
-    eventRegistrationConfirmGuardianHelpText 
+    eventRegistrationConfirmGuardianHelpText
   }
 }
diff --git a/aleksis/apps/paweljong/frontend/index.js b/aleksis/apps/paweljong/frontend/index.js
index a3229cc..c1451f3 100644
--- a/aleksis/apps/paweljong/frontend/index.js
+++ b/aleksis/apps/paweljong/frontend/index.js
@@ -30,7 +30,8 @@ export default {
     },
     {
       path: "event/:slug/register/",
-      component: () => import("./components/event_registration/EventRegistrationForm.vue"),
+      component: () =>
+        import("./components/event_registration/EventRegistrationForm.vue"),
       name: "paweljong.registerEventBySlug",
       props: true,
     },
diff --git a/aleksis/apps/paweljong/frontend/messages/de.json b/aleksis/apps/paweljong/frontend/messages/de.json
index de2254f..f1928c4 100644
--- a/aleksis/apps/paweljong/frontend/messages/de.json
+++ b/aleksis/apps/paweljong/frontend/messages/de.json
@@ -1,244 +1,244 @@
 {
-    "paweljong": {
-        "events": {
-            "menu_title": "Veranstaltungen",
-            "vouchers": {
-                "menu_title": "Gutscheine"
-            },
-            "terms": {
-                "menu_title": "Bedingungen"
-            },
-            "registration_states": {
-                "menu_title": "Registrierungsstatus"
-            },
-            "info_mailings": {
-                "menu_title": "Info-Mailings"
+  "paweljong": {
+    "events": {
+      "menu_title": "Veranstaltungen",
+      "vouchers": {
+        "menu_title": "Gutscheine"
+      },
+      "terms": {
+        "menu_title": "Bedingungen"
+      },
+      "registration_states": {
+        "menu_title": "Registrierungsstatus"
+      },
+      "info_mailings": {
+        "menu_title": "Info-Mailings"
+      },
+      "generate_lists": {
+        "menu_title": "Teilnehmendenliste generieren"
+      },
+      "manage_events": {
+        "menu_title": "Veranstaltungen verwalten"
+      }
+    },
+    "register": {
+      "menu_title": "Registrieren"
+    },
+    "event_additional_field": {
+      "create": "Zusätzliche Felder für Veranstaltungen erstellen",
+      "title": "Zusätzliches Feld für Veranstaltung",
+      "field_title": "Titel",
+      "title_plural": "Zusätzliche Felder für Veranstaltungen",
+      "menu_title": "Zusätzliche Felder für Veranstaltungen",
+      "field_type": {
+        "form_field_name": "Feld-Typ",
+        "BOOLEANFIELD": "Boolescher Wert (Ja/Nein)",
+        "CHARFIELD": "Text (einzeilig)",
+        "DATEFIELD": "Datum",
+        "DATETIMEFIELD": "Datum und Uhrzeit",
+        "DECIMALFIELD": "Dezimalzahl",
+        "EMAILFIELD": "E-Mail-Adresse",
+        "INTEGERFIELD": "Zahl",
+        "GENERICIPADDRESSFIELD": "IP-Adresse",
+        "NULLBOOLEANFIELD": "Boolescher Wert oder leer (Ja/Nein/Leer)",
+        "TEXTFIELD": "Text (mehrzeilig)",
+        "TIMEFIELD": "Uhrzeit",
+        "URLFIELD": "URL / Link"
+      },
+      "required": {
+        "form_field_name": "Verpflichtend",
+        "required": "Verpflichtend",
+        "optional": "Optional"
+      },
+      "help_text": "Hilfe-Text"
+    },
+    "event_registration": {
+      "form": {
+        "submitted": {
+          "thank_you": "Danke!",
+          "submitted_successfully": "Deine Anmeldung wurde erfolgreich abgeschickt."
+        },
+        "steps": {
+          "email": {
+            "title": "E-Mail-Adresse",
+            "choose_mode": {
+              "help_text": " Jeder Teilnehmende benötigt seine eigene E-Mail-Adresse. Diese Adresse muss eine Adresse sein, die du selbst abrufst und liest, und die nicht deinen Eltern gehört. Wichtige Informationen senden wir natürlich immer zusätzlich an deine Eltern. Natürlich solltest du mit deinen Eltern immer reden, falls du beim Lesen deiner E-Mails Fragen oder Sorgen hast.  Wenn du bisher keine eigene E-Mail-Adresse hast, und keine bei den \"großen\" Anbietern anlegen kannst oder möchtest (denke daran, dass Google, Outlook etc. Accounts erst ab 16 erlauben, und oft deine Daten sammeln und analysieren), kannst du Dir bei uns eine E-Mail-Adresse registrieren.",
+              "continue_aleksis": "Ich habe keine persönliche E-Mail-Adresse",
+              "continue_own": "Ich habe eine persönliche E-Mail-Adresse",
+              "continue_existing_account": "Ich habe bereits ein Konto",
+              "back": "Zurück"
             },
-            "generate_lists": {
-                "menu_title": "Teilnehmendenliste generieren"
+            "fields": {
+              "email": {
+                "label": "E-Mail-Adresse"
+              },
+              "confirm_email": {
+                "label": "E-Mail-Adresse bestätigen"
+              }
+            }
+          },
+          "register": {
+            "title": "Konto",
+            "fields": {
+              "first_name": {
+                "label": "Vorname"
+              },
+              "last_name": {
+                "label": "Nachname"
+              },
+              "username": {
+                "label": "Benutzername"
+              },
+              "password": {
+                "label": "Passwort"
+              },
+              "confirm_password": {
+                "label": "Passwort bestätigen"
+              }
+            }
+          },
+          "contact_details": {
+            "title": "Kontaktinformationen",
+            "fields": {
+              "first_name": {
+                "label": "Vorname"
+              },
+              "last_name": {
+                "label": "Nachname"
+              },
+              "date_of_birth": {
+                "label": "Geburtsdatum"
+              },
+              "sex": {
+                "label": "Geschlecht",
+                "help_text": "Aus verschiedenen Gründen, z. B. weil wir aus rechtlichen Gründen nachts eine geschlechtergetrennte Unterbringung einhalten müssen, müssen wir wissen, ob du ein Junge oder ein Mädchen bist."
+              },
+              "street": {
+                "label": "Straße"
+              },
+              "housenumber": {
+                "label": "Hausnummer"
+              },
+              "postal_code": {
+                "label": "Postleitzahl"
+              },
+              "place": {
+                "label": "Ort"
+              },
+              "email": {
+                "label": "E-Mail-Adresse",
+                "help_text": "Bitte gib hier deine persönliche E-Mail-Adresse an, die du regelmäßig abrufst. Wichtige Informationen werden immer auch an deine Eltern gesendet. Verwende hier keine E-Mail-Adresse deiner Eltern."
+              },
+              "mobile_number": {
+                "label": "Handynummer",
+                "help_text": "Deine Handynummer hilft uns, dich im Notfall während der Veranstaltung zu erreichen, z. B. wenn du mit deiner Gruppe auf einer Konferenz unterwegs bist. Falls du kein Handy hast, kannst du das Feld leer lassen."
+              },
+              "school": {
+                "label": "Schule",
+                "help_text": "Bitte gib den Namen deiner Schule ein."
+              },
+              "school_place": {
+                "label": "Schulort",
+                "help_text": "Gib den Ort (Stadt) an, in dem sich deine Schule befindet."
+              },
+              "school_class": {
+                "label": "Klasse",
+                "help_text": "Bitte gib deine aktuelle Klasse an (z. B. 8a)."
+              }
+            }
+          },
+          "guardians": {
+            "title": "Erziehungsberechtigte",
+            "counter": "Erziehungsberechtigter {i}",
+            "add": "Erziehungsberechtigten hinzufügen",
+            "fields": {
+              "first_name": {
+                "label": "Vorname des Erziehungsberechtigten",
+                "help_text": "Bitte gib den Vornamen des Erziehungsberechtigten an, der das Anmeldeformular mit dir ausfüllt und während der Veranstaltung in Notfällen erreichbar ist."
+              },
+              "last_name": {
+                "label": "Nachname des Erziehungsberechtigten",
+                "help_text": "Bitte gib den Nachnamen des Erziehungsberechtigten an, der das Anmeldeformular mit dir ausfüllt und während der Veranstaltung in Notfällen erreichbar ist."
+              },
+              "email": {
+                "label": "E-Mail-Adresse des Erziehungsberechtigten"
+              },
+              "mobile_number": {
+                "label": "Handynummer des Erziehungsberechtigten",
+                "help_text": "Wir benötigen die Handynummer für Notfälle, falls wir deine Eltern während der Veranstaltung dringend erreichen müssen."
+              }
+            }
+          },
+          "additional": {
+            "title": "Zusätzliche Informationen",
+            "fields": {
+              "medical_information": {
+                "label": "Medizinische Informationen / Unverträglichkeiten",
+                "help_text": "Falls es medizinisch wichtige Dinge gibt, die wir beachten müssen, z. B. bei der Essenszubereitung oder zur Einnahme verschriebener Medikamente, gib diese bitte hier an."
+              }
+            }
+          },
+          "financial": {
+            "title": "Bezahlung",
+            "help_text": {
+              "cost": "Tatsächliche Kosten",
+              "max_cost": "Maximal zu zahlender Betrag",
+              "min_cost": "Minimal zu zahlender Betrag",
+              "cost_info": "Unser Verein möchte allen Kindern und Jugendlichen die Teilnahme an unseren Veranstaltungen ermöglichen. Manchmal können Familien jedoch nicht die volle Gebühr zahlen. Deshalb haben wir ein Budget, aus dem wir die Teilnahme fördern können, wenn die Notwendigkeit geprüft wurde. Dieses Budget basiert auf Spenden. Falls du freiwillig einen zusätzlichen Betrag für dieses Budget spenden möchtest, gib bitte einen Betrag über den tatsächlichen Kosten an. Falls du die volle Gebühr nicht zahlen kannst, gib bitte einen Betrag an, der zu deinen finanziellen Möglichkeiten passt."
             },
-            "manage_events": {
-                "menu_title": "Veranstaltungen verwalten"
+            "fields": {
+              "payment_method": {
+                "label": "Zahlungsmethode",
+                "help_text": "Bitte wähle eine Zahlungsmethode. Die tatsächliche Zahlung erfolgt nach der Anmeldung."
+              },
+              "voucher_code": {
+                "label": "Gutscheincode",
+                "help_text": "Falls du einen Gutscheincode hast, gib ihn hier ein."
+              },
+              "amount": {
+                "label": "Von dir gezahlter Betrag (in €)"
+              }
+            }
+          },
+          "consent": {
+            "title": "Einverständniserklärung",
+            "fields": {
+              "retraction_consent": {
+                "label": "Ich bestätige, dass die Rücknahme der Anmeldung nach dem {date} nicht mehr möglich ist."
+              }
             }
+          },
+          "confirm": {
+            "title": "Bestätigen",
+            "hint": "Nach diesem Schritt kannst du deine Anmeldung nicht mehr bearbeiten.",
+            "fields": {
+              "comment": {
+                "label": "Weitere Anmerkungen",
+                "help_text": "Hier kannst du uns zusätzliche Anmerkungen mitteilen."
+              }
+            }
+          }
         },
-        "register": {
-            "menu_title": "Registrieren"
+        "rules": {
+          "email": {
+            "valid": "Dies ist keine gültige E-Mail-Adresse"
+          },
+          "confirm_email": {
+            "no_match": "Die E-Mail-Adressen stimmen nicht überein"
+          },
+          "confirm_password": {
+            "no_match": "Die Passwörter stimmen nicht überein"
+          },
+          "amount": {
+            "too_high": "Der Betrag ist höher als der maximal zu zahlende Betrag.",
+            "too_low": "Der Betrag ist niedriger als der minimal zu zahlende Betrag."
+          }
         },
-        "event_additional_field": {
-            "create": "Zusätzliche Felder für Veranstaltungen erstellen",
-            "title": "Zusätzliches Feld für Veranstaltung",
-            "field_title": "Titel",
-            "title_plural": "Zusätzliche Felder für Veranstaltungen",
-            "menu_title": "Zusätzliche Felder für Veranstaltungen",
-            "field_type": {
-                "form_field_name": "Feld-Typ",
-                "BOOLEANFIELD": "Boolescher Wert (Ja/Nein)",
-                "CHARFIELD": "Text (einzeilig)",
-                "DATEFIELD": "Datum",
-                "DATETIMEFIELD": "Datum und Uhrzeit",
-                "DECIMALFIELD": "Dezimalzahl",
-                "EMAILFIELD": "E-Mail-Adresse",
-                "INTEGERFIELD": "Zahl",
-                "GENERICIPADDRESSFIELD": "IP-Adresse",
-                "NULLBOOLEANFIELD": "Boolescher Wert oder leer (Ja/Nein/Leer)",
-                "TEXTFIELD": "Text (mehrzeilig)",
-                "TIMEFIELD": "Uhrzeit",
-                "URLFIELD": "URL / Link"
-            },
-            "required": {
-                "form_field_name": "Verpflichtend",
-                "required": "Verpflichtend",
-                "optional": "Optional"
-            },
-            "help_text": "Hilfe-Text"
-        },
-        "event_registration": {
-            "form": {
-                "submitted": {
-                    "thank_you": "Danke!",
-                    "submitted_successfully": "Deine Anmeldung wurde erfolgreich abgeschickt."
-                },
-                "steps": {
-                    "email": {
-                        "title": "E-Mail-Adresse",
-                        "choose_mode": {
-                            "help_text": " Jeder Teilnehmende benötigt seine eigene E-Mail-Adresse. Diese Adresse muss eine Adresse sein, die du selbst abrufst und liest, und die nicht deinen Eltern gehört. Wichtige Informationen senden wir natürlich immer zusätzlich an deine Eltern. Natürlich solltest du mit deinen Eltern immer reden, falls du beim Lesen deiner E-Mails Fragen oder Sorgen hast.  Wenn du bisher keine eigene E-Mail-Adresse hast, und keine bei den \"großen\" Anbietern anlegen kannst oder möchtest (denke daran, dass Google, Outlook etc. Accounts erst ab 16 erlauben, und oft deine Daten sammeln und analysieren), kannst du Dir bei uns eine E-Mail-Adresse registrieren.",
-                            "continue_aleksis": "Ich habe keine persönliche E-Mail-Adresse",
-                            "continue_own": "Ich habe eine persönliche E-Mail-Adresse",
-                            "continue_existing_account": "Ich habe bereits ein Konto",
-                            "back": "Zurück"
-                        },
-                        "fields": {
-                        "email": {
-                            "label": "E-Mail-Adresse"
-                        },
-                        "confirm_email": {
-                            "label": "E-Mail-Adresse bestätigen"
-                        }
-                        }
-                    },
-                    "register": {
-                        "title": "Konto",
-                        "fields": {
-                        "first_name": {
-                            "label": "Vorname"
-                        },
-                        "last_name": {
-                            "label": "Nachname"
-                        },
-                        "username": {
-                            "label": "Benutzername"
-                        },
-                        "password": {
-                            "label": "Passwort"
-                        },
-                        "confirm_password": {
-                            "label": "Passwort bestätigen"
-                        }
-                        }
-                    },
-                    "contact_details": {
-                        "title": "Kontaktinformationen",
-                        "fields": {
-                        "first_name": {
-                            "label": "Vorname"
-                        },
-                        "last_name": {
-                            "label": "Nachname"
-                        },
-                        "date_of_birth": {
-                            "label": "Geburtsdatum"
-                        },
-                        "sex": {
-                            "label": "Geschlecht",
-                            "help_text": "Aus verschiedenen Gründen, z. B. weil wir aus rechtlichen Gründen nachts eine geschlechtergetrennte Unterbringung einhalten müssen, müssen wir wissen, ob du ein Junge oder ein Mädchen bist."
-                        },
-                        "street": {
-                            "label": "Straße"
-                        },
-                        "housenumber": {
-                            "label": "Hausnummer"
-                        },
-                        "postal_code": {
-                            "label": "Postleitzahl"
-                        },
-                        "place": {
-                            "label": "Ort"
-                        },
-                        "email": {
-                            "label": "E-Mail-Adresse",
-                            "help_text": "Bitte gib hier deine persönliche E-Mail-Adresse an, die du regelmäßig abrufst. Wichtige Informationen werden immer auch an deine Eltern gesendet. Verwende hier keine E-Mail-Adresse deiner Eltern."
-                        },
-                        "mobile_number": {
-                            "label": "Handynummer",
-                            "help_text": "Deine Handynummer hilft uns, dich im Notfall während der Veranstaltung zu erreichen, z. B. wenn du mit deiner Gruppe auf einer Konferenz unterwegs bist. Falls du kein Handy hast, kannst du das Feld leer lassen."
-                        },
-                        "school": {
-                            "label": "Schule",
-                            "help_text": "Bitte gib den Namen deiner Schule ein."
-                        },
-                        "school_place": {
-                            "label": "Schulort",
-                            "help_text": "Gib den Ort (Stadt) an, in dem sich deine Schule befindet."
-                        },
-                        "school_class": {
-                            "label": "Klasse",
-                            "help_text": "Bitte gib deine aktuelle Klasse an (z. B. 8a)."
-                        }
-                        }
-                    },
-                    "guardians": {
-                        "title": "Erziehungsberechtigte",
-                        "counter": "Erziehungsberechtigter {i}",
-                        "add": "Erziehungsberechtigten hinzufügen",
-                        "fields": {
-                        "first_name": {
-                            "label": "Vorname des Erziehungsberechtigten",
-                            "help_text": "Bitte gib den Vornamen des Erziehungsberechtigten an, der das Anmeldeformular mit dir ausfüllt und während der Veranstaltung in Notfällen erreichbar ist."
-                        },
-                        "last_name": {
-                            "label": "Nachname des Erziehungsberechtigten",
-                            "help_text": "Bitte gib den Nachnamen des Erziehungsberechtigten an, der das Anmeldeformular mit dir ausfüllt und während der Veranstaltung in Notfällen erreichbar ist."
-                        },
-                        "email": {
-                            "label": "E-Mail-Adresse des Erziehungsberechtigten"
-                        },
-                        "mobile_number": {
-                            "label": "Handynummer des Erziehungsberechtigten",
-                            "help_text": "Wir benötigen die Handynummer für Notfälle, falls wir deine Eltern während der Veranstaltung dringend erreichen müssen."
-                        }
-                        }
-                    },
-                    "additional": {
-                        "title": "Zusätzliche Informationen",
-                        "fields": {
-                        "medical_information": {
-                            "label": "Medizinische Informationen / Unverträglichkeiten",
-                            "help_text": "Falls es medizinisch wichtige Dinge gibt, die wir beachten müssen, z. B. bei der Essenszubereitung oder zur Einnahme verschriebener Medikamente, gib diese bitte hier an."
-                        }
-                        }
-                    },
-                    "financial": {
-                        "title": "Bezahlung",
-                        "help_text": {
-                        "cost": "Tatsächliche Kosten",
-                        "max_cost": "Maximal zu zahlender Betrag",
-                        "min_cost": "Minimal zu zahlender Betrag",
-                        "cost_info": "Unser Verein möchte allen Kindern und Jugendlichen die Teilnahme an unseren Veranstaltungen ermöglichen. Manchmal können Familien jedoch nicht die volle Gebühr zahlen. Deshalb haben wir ein Budget, aus dem wir die Teilnahme fördern können, wenn die Notwendigkeit geprüft wurde. Dieses Budget basiert auf Spenden. Falls du freiwillig einen zusätzlichen Betrag für dieses Budget spenden möchtest, gib bitte einen Betrag über den tatsächlichen Kosten an. Falls du die volle Gebühr nicht zahlen kannst, gib bitte einen Betrag an, der zu deinen finanziellen Möglichkeiten passt."
-                        },
-                        "fields": {
-                        "payment_method": {
-                            "label": "Zahlungsmethode",
-                            "help_text": "Bitte wähle eine Zahlungsmethode. Die tatsächliche Zahlung erfolgt nach der Anmeldung."
-                        },
-                        "voucher_code": {
-                            "label": "Gutscheincode",
-                            "help_text": "Falls du einen Gutscheincode hast, gib ihn hier ein."
-                        },
-                        "amount": {
-                            "label": "Von dir gezahlter Betrag (in €)"
-                        }
-                        }
-                    },
-                    "consent": {
-                        "title": "Einverständniserklärung",
-                        "fields": {
-                        "retraction_consent": {
-                            "label": "Ich bestätige, dass die Rücknahme der Anmeldung nach dem {date} nicht mehr möglich ist."
-                        }
-                        }
-                    },
-                    "confirm": {
-                        "title": "Bestätigen",
-                        "hint": "Nach diesem Schritt kannst du deine Anmeldung nicht mehr bearbeiten.",
-                        "fields": {
-                        "comment": {
-                            "label": "Weitere Anmerkungen",
-                            "help_text": "Hier kannst du uns zusätzliche Anmerkungen mitteilen."
-                        }
-                        }
-                    }
-                },
-                "rules": {
-                    "email": {
-                        "valid": "Dies ist keine gültige E-Mail-Adresse"
-                    },
-                    "confirm_email": {
-                        "no_match": "Die E-Mail-Adressen stimmen nicht überein"
-                    },
-                    "confirm_password": {
-                        "no_match": "Die Passwörter stimmen nicht überein"
-                    },
-                    "amount": {
-                        "too_high": "Der Betrag ist höher als der maximal zu zahlende Betrag.",
-                        "too_low": "Der Betrag ist niedriger als der minimal zu zahlende Betrag."
-                    }
-                },
-                "help_text": {
-                    "guardian": "Für Erziehungsberechtigte",
-                    "participant": "Für Teilnehmende"
-                }
-            }
+        "help_text": {
+          "guardian": "Für Erziehungsberechtigte",
+          "participant": "Für Teilnehmende"
         }
+      }
     }
+  }
 }
diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 7d79af0..a9a8a5f 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -168,7 +168,9 @@ class Event(ExtensibleModel):
     # Other details
     cost = models.IntegerField(verbose_name=_("Cost in €"))
     min_cost = models.IntegerField(verbose_name=_("Minimum cost in €"), default=0)
-    max_cost = models.IntegerField(verbose_name=_("Maximum cost in €"), default=None, null=True, blank=True)
+    max_cost = models.IntegerField(
+        verbose_name=_("Maximum cost in €"), default=None, null=True, blank=True
+    )
     max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants"))
     information = RichTextField(verbose_name=_("Information about the event"))
     terms = models.ManyToManyField(Terms, verbose_name=_("Terms"), related_name="event", blank=True)
@@ -199,9 +201,7 @@ class Event(ExtensibleModel):
                 ("school_details", _("School details")),
             ],
         ),
-        verbose_name=_(
-            "Contact information fields to be shown."
-        ),
+        verbose_name=_("Contact information fields to be shown."),
         blank=True,
         default=[],
     )
@@ -341,7 +341,9 @@ class EventRegistration(ExtensibleModel):
 
     school = models.CharField(verbose_name=_("Name of school"), max_length=255, blank=True)
     school_class = models.CharField(verbose_name=_("School class"), max_length=255, blank=True)
-    school_place = models.CharField(verbose_name=_("Place of the school"), max_length=255, blank=True)
+    school_place = models.CharField(
+        verbose_name=_("Place of the school"), max_length=255, blank=True
+    )
 
     comment = models.TextField(verbose_name=_("Comment / remarks"), blank=True, default="")
     medical_information = models.TextField(
@@ -531,6 +533,4 @@ class Checkpoint(ExtensibleModel):
 class PaweljongGlobalPermissions(GlobalPermissionModel):
     class Meta:
         managed = False
-        permissions = (
-            ("generate_lists", _("Can generate lists of participants of a group")),
-        )
+        permissions = (("generate_lists", _("Can generate lists of participants of a group")),)
diff --git a/aleksis/apps/paweljong/preferences.py b/aleksis/apps/paweljong/preferences.py
index f42a0d0..bf0e572 100644
--- a/aleksis/apps/paweljong/preferences.py
+++ b/aleksis/apps/paweljong/preferences.py
@@ -61,12 +61,13 @@ class EventRegistrationRegisterParticipantHelpText(LongStringPreference):
 
     section = paweljong
     name = "event_registration_register_participant_help_text"
-    verbose_name = _("Participant help text for account registration step in event registration wizard.")
+    verbose_name = _(
+        "Participant help text for account registration step in event registration wizard."
+    )
     default = ""
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationContactDetailsParticipantHelpText(LongStringPreference):
     """Help text to be displayed to participants during the contact details step of the event registration wizard."""
@@ -78,7 +79,6 @@ class EventRegistrationContactDetailsParticipantHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationGuardiansParticipantHelpText(LongStringPreference):
     """Help text to be displayed to participants during the guardians step of the event registration wizard."""
@@ -90,19 +90,19 @@ class EventRegistrationGuardiansParticipantHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationAdditionalParticipantHelpText(LongStringPreference):
     """Help text to be displayed to participants during the additional information step of the event registration wizard."""
 
     section = paweljong
     name = "event_registration_additional_participant_help_text"
-    verbose_name = _("Participant help text for additional information step in event registration wizard.")
+    verbose_name = _(
+        "Participant help text for additional information step in event registration wizard."
+    )
     default = ""
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationFinancialParticipantHelpText(LongStringPreference):
     """Help text to be displayed to participants during the financial step of the event registration wizard."""
@@ -114,7 +114,6 @@ class EventRegistrationFinancialParticipantHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationConsentParticipantHelpText(LongStringPreference):
     """Help text to be displayed to participants during the consent step of the event registration wizard."""
@@ -126,7 +125,6 @@ class EventRegistrationConsentParticipantHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationConfirmParticipantHelpText(LongStringPreference):
     """Help text to be displayed to participants during the confirmation step of the event registration wizard."""
@@ -149,19 +147,19 @@ class EventRegistrationEmailGuardianHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationRegisterGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the account registration step of the event registration wizard."""
 
     section = paweljong
     name = "event_registration_register_guardian_help_text"
-    verbose_name = _("Guardian help text for account registration step in event registration wizard.")
+    verbose_name = _(
+        "Guardian help text for account registration step in event registration wizard."
+    )
     default = ""
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationContactDetailsGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the contact details step of the event registration wizard."""
@@ -173,7 +171,6 @@ class EventRegistrationContactDetailsGuardianHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationGuardiansGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the guardians step of the event registration wizard."""
@@ -185,19 +182,19 @@ class EventRegistrationGuardiansGuardianHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationAdditionalGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the additional information step of the event registration wizard."""
 
     section = paweljong
     name = "event_registration_additional_guardian_help_text"
-    verbose_name = _("Guardian help text for additional information step in event registration wizard.")
+    verbose_name = _(
+        "Guardian help text for additional information step in event registration wizard."
+    )
     default = ""
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationFinancialGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the financial step of the event registration wizard."""
@@ -209,7 +206,6 @@ class EventRegistrationFinancialGuardianHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationConsentGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the consent step of the event registration wizard."""
@@ -221,7 +217,6 @@ class EventRegistrationConsentGuardianHelpText(LongStringPreference):
     required = False
 
 
-
 @site_preferences_registry.register
 class EventRegistrationConfirmGuardianHelpText(LongStringPreference):
     """Help text to be displayed to guardians during the confirmation step of the event registration wizard."""
diff --git a/aleksis/apps/paweljong/rules.py b/aleksis/apps/paweljong/rules.py
index 79fd5c1..08a3ef9 100644
--- a/aleksis/apps/paweljong/rules.py
+++ b/aleksis/apps/paweljong/rules.py
@@ -239,9 +239,7 @@ create_registration_states_predicate = has_person & (
 rules.add_perm("paweljong.create_registration_states_rule", create_registration_states_predicate)
 
 # Generate lists
-generate_lists_predicate = has_person & (
-    has_global_perm("paweljong.generate_lists")
-)
+generate_lists_predicate = has_person & (has_global_perm("paweljong.generate_lists"))
 rules.add_perm("paweljong.generate_lists_rule", generate_lists_predicate)
 
 # View menu
@@ -271,14 +269,18 @@ create_event_additional_field_predicate = has_person & (
     has_global_perm("paweljong.add_eventadditionalfield")
     | has_object_perm("paweljong.add_eventadditionalfield")
 )
-rules.add_perm("paweljong.create_event_additional_field_rule", create_event_additional_field_predicate)
+rules.add_perm(
+    "paweljong.create_event_additional_field_rule", create_event_additional_field_predicate
+)
 
 # Delete event additional field
 delete_event_additional_field_predicate = has_person & (
     has_global_perm("paweljong.delete_eventadditionalfield")
     | has_object_perm("paweljong.delete_eventadditionalfield")
 )
-rules.add_perm("paweljong.delete_event_additional_field_rule", delete_event_additional_field_predicate)
+rules.add_perm(
+    "paweljong.delete_event_additional_field_rule", delete_event_additional_field_predicate
+)
 
 # Edit event additional field
 edit_event_additional_fieldpredicate = has_person & (
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
index 6cd371e..b97bf4b 100644
--- a/aleksis/apps/paweljong/schema/__init__.py
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -1,8 +1,5 @@
-from django.core.exceptions import PermissionDenied
 
 import graphene
-from graphene_django import DjangoObjectType
-from graphql import GraphQLError
 
 from aleksis.apps.tezor.models.base import Client
 from aleksis.apps.tezor.models.invoice import InvoiceGroup
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 7df9296..93eebc0 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -1,12 +1,11 @@
-from django.db import transaction, IntegrityError
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
+from django.db import IntegrityError, transaction
 from django.utils.text import slugify
 from django.utils.translation import gettext as _
 
 import graphene
 from graphene_django import DjangoObjectType
-from templated_email import send_templated_mail
 
 from aleksis.apps.postbuero.models import MailAddress, MailDomain
 from aleksis.apps.postbuero.schema import MailAddressInputType
@@ -14,7 +13,6 @@ from aleksis.core.models import Activity, Person
 from aleksis.core.schema.base import PermissionsTypeMixin
 from aleksis.core.schema.person import PersonInputType
 from aleksis.core.schema.user import UserInputType
-from aleksis.core.util.core_helpers import get_site_preferences
 
 from ..models import Event, EventRegistration, Terms, Voucher
 
@@ -27,7 +25,9 @@ class EventRegistrationType(PermissionsTypeMixin, DjangoObjectType):
 class PaymentInputType(graphene.InputObjectType):
     payment_method = graphene.String(required=True)
     voucher_code = graphene.String(required=False)
-    amount = graphene.Int(required=False) # FIXME: Fix frontend to allow 0, then make field required again
+    amount = graphene.Int(
+        required=False
+    )  # FIXME: Fix frontend to allow 0, then make field required again
 
 
 class EventRegistrationInputType(graphene.InputObjectType):
@@ -53,7 +53,9 @@ class SendEventRegistrationMutation(graphene.Mutation):
     ok = graphene.Boolean()
 
     @transaction.atomic
-    def mutate(self, info, event: graphene.ID, event_registration: EventRegistrationInputType, **kwargs):
+    def mutate(
+        self, info, event: graphene.ID, event_registration: EventRegistrationInputType, **kwargs
+    ):
         if not event_registration["retraction_consent"]:
             raise ValidationError(_("Retraction consent is required"))
 
@@ -107,10 +109,16 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         # Store contact information in database
         for field_name in ["date_of_birth", "sex", "address", "mobile_number"]:
-            if event_registration["person"] is not None and event_registration["person"][field_name] is not None and event_registration["person"][field_name] != "":
+            if (
+                event_registration["person"] is not None
+                and event_registration["person"][field_name] is not None
+                and event_registration["person"][field_name] != ""
+            ):
                 if field_name == "address":
                     for addr_field in ["street", "housenumber", "postal_code", "place"]:
-                        setattr(person, addr_field, event_registration["person"]["address"][addr_field])
+                        setattr(
+                            person, addr_field, event_registration["person"]["address"][addr_field]
+                        )
                 else:
                     setattr(person, field_name, event_registration["person"][field_name])
         person.save()
@@ -119,11 +127,11 @@ class SendEventRegistrationMutation(graphene.Mutation):
             for guardian_data in event_registration["person"]["guardians"]:
                 if "id" in guardian_data:
                     guardian = Person.objects.get(pk=guardian_data["id"])
-                    guardian.first_name=guardian_data["first_name"]
-                    guardian.last_name=guardian_data["last_name"]
-                    guardian.email=guardian_data["email"]
-                    guardian.mobile_number=guardian_data["mobile_number"]
-                    
+                    guardian.first_name = guardian_data["first_name"]
+                    guardian.last_name = guardian_data["last_name"]
+                    guardian.email = guardian_data["email"]
+                    guardian.mobile_number = guardian_data["mobile_number"]
+
                     guardian.save()
                 else:
                     try:
@@ -136,7 +144,10 @@ class SendEventRegistrationMutation(graphene.Mutation):
                             email=guardian_data["email"],
                         )
                     except IntegrityError:
-                        raise ValidationError(_("A person using the e-mail address %s already exists.") % guardian_data["email"])
+                        raise ValidationError(
+                            _("A person using the e-mail address %s already exists.")
+                            % guardian_data["email"]
+                        )
 
                     person.guardians.add(guardian)
             person.save()
@@ -205,19 +216,19 @@ class SendEventRegistrationMutation(graphene.Mutation):
         context = {}
         context["registration"] = registration
 
-#        send_templated_mail(
-#            template_name="event_registered",
-#            from_email=get_site_preferences()["mail__address"],
-#            recipient_list=[get_site_preferences()["paweljong__event_notification_recipient"]],
-#            headers={
-#                "reply_to": [
-#                    person.email,
-#                    person.guardians.first().email,
-#                ],
-#                "X-Zammad-Customer-Email": person.email,
-#            },
-#            context=context,
-#        )
+        #        send_templated_mail(
+        #            template_name="event_registered",
+        #            from_email=get_site_preferences()["mail__address"],
+        #            recipient_list=[get_site_preferences()["paweljong__event_notification_recipient"]],
+        #            headers={
+        #                "reply_to": [
+        #                    person.email,
+        #                    person.guardians.first().email,
+        #                ],
+        #                "X-Zammad-Customer-Email": person.email,
+        #            },
+        #            context=context,
+        #        )
 
         _act = Activity(
             title=_("You registered for an event"),
diff --git a/aleksis/apps/paweljong/schema/site_preferences.py b/aleksis/apps/paweljong/schema/site_preferences.py
index cddf3d5..04f321f 100644
--- a/aleksis/apps/paweljong/schema/site_preferences.py
+++ b/aleksis/apps/paweljong/schema/site_preferences.py
@@ -20,7 +20,6 @@ class PaweljongSitePreferencesType(graphene.ObjectType):
     event_registration_consent_guardian_help_text = graphene.String()
     event_registration_confirm_guardian_help_text = graphene.String()
 
-
     def resolve_event_registration_email_participant_help_text(parent, info, **kwargs):
         return parent["paweljong__event_registration_email_participant_help_text"]
 
diff --git a/aleksis/apps/paweljong/views.py b/aleksis/apps/paweljong/views.py
index c2bf874..85605cf 100644
--- a/aleksis/apps/paweljong/views.py
+++ b/aleksis/apps/paweljong/views.py
@@ -286,6 +286,7 @@ def set_email_needed(request, slug: Optional[str] = None):
 def is_email_needed(wizard):
     return wizard.request.session.get("email_needed", None)
 
+
 def is_financial_needed(wizard):
     return wizard.request.session.get("financial_needed", None)
 
@@ -317,7 +318,7 @@ class AccountRegisterWizardView(SessionWizardView):
         if step == "register":
             try:
                 cleaned_data_email = self.get_cleaned_data_for_step("email")
-            except KeyError: # FIXME
+            except KeyError:  # FIXME
                 cleaned_data_email = False
             if cleaned_data_email:
                 domain = cleaned_data_email["domain"]
@@ -346,7 +347,7 @@ class AccountRegisterWizardView(SessionWizardView):
         context = {}
         try:
             cleaned_data_email = self.get_cleaned_data_for_step("email")
-        except KeyError: # FIXME
+        except KeyError:  # FIXME
             cleaned_data_email = False
         cleaned_data_register = self.get_cleaned_data_for_step("register")
 
@@ -529,11 +530,7 @@ class RegisterEventWizardView(SessionWizardView):
                 )
                 for field_name in event.contact_information_visible_fields:
                     if hasattr(person, field_name):
-                        initial.update(
-                            {
-                                field_name: getattr(person, field_name)
-                            }
-                        )
+                        initial.update({field_name: getattr(person, field_name)})
 
             else:
                 cleaned_data_register = self.get_cleaned_data_for_step("register")
@@ -560,13 +557,13 @@ class RegisterEventWizardView(SessionWizardView):
         event = Event.objects.get(slug=self.kwargs["slug"])
         try:
             cleaned_data_email = self.get_cleaned_data_for_step("email")
-        except KeyError: # FIXME
+        except KeyError:  # FIXME
             cleaned_data_email = False
         cleaned_data_contact_details = self.get_cleaned_data_for_step("contact_details")
         cleaned_data_guardians = self.get_cleaned_data_for_step("guardians")
         try:
             cleaned_data_register = self.get_cleaned_data_for_step("register")
-        except KeyError: # FIXME
+        except KeyError:  # FIXME
             cleaned_data_register = False
         cleaned_data_additional = self.get_cleaned_data_for_step("additional")
 
@@ -607,7 +604,15 @@ class RegisterEventWizardView(SessionWizardView):
         )
 
         # Store contact information in database
-        for field_name in ["date_of_birth", "sex", "street", "housenumber", "postal_code", "place", "mobile_number"]:
+        for field_name in [
+            "date_of_birth",
+            "sex",
+            "street",
+            "housenumber",
+            "postal_code",
+            "place",
+            "mobile_number",
+        ]:
             if field_name in cleaned_data_contact_details:
                 setattr(person, field_name, cleaned_data_contact_details[field_name])
         person.save()
@@ -664,7 +669,10 @@ class RegisterEventWizardView(SessionWizardView):
         if is_financial_needed(self):
             if cleaned_data_financial["voucher_code"]:
                 vouchers = Voucher.objects.filter(
-                    person=person, event=event, used=False, code=cleaned_data_financial["voucher_code"]
+                    person=person,
+                    event=event,
+                    used=False,
+                    code=cleaned_data_financial["voucher_code"],
                 )
                 if vouchers:
                     voucher = vouchers.first()
-- 
GitLab


From 1887a88d6b4d05cb9c9cf6329f299110c5c87421 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:52:55 +0100
Subject: [PATCH 61/72] Adjust col widths

---
 .../event_registration/EventRegistrationForm.vue          | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index a830a4b..147bbaa 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -353,7 +353,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-col>
                 </v-row>
                 <v-row>
-                  <v-col v-if="isFieldVisible('street')" cols="12" lg="4">
+                  <v-col v-if="isFieldVisible('street')" cols="12" lg="6">
                     <div aria-required="true">
                       <v-text-field
                         outlined
@@ -368,7 +368,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       ></v-text-field>
                     </div>
                   </v-col>
-                  <v-col v-if="isFieldVisible('housenumber')" cols="12" lg="4">
+                  <v-col v-if="isFieldVisible('housenumber')" cols="12" lg="6">
                     <div aria-required="true">
                       <v-text-field
                         outlined
@@ -385,7 +385,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                   </v-col>
                 </v-row>
                 <v-row>
-                  <v-col v-if="isFieldVisible('postal_code')" cols="12" lg="4">
+                  <v-col v-if="isFieldVisible('postal_code')" cols="12" lg="6">
                     <div aria-required="true">
                       <v-text-field
                         outlined
@@ -400,7 +400,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       ></v-text-field>
                     </div>
                   </v-col>
-                  <v-col v-if="isFieldVisible('place')" cols="12" lg="4">
+                  <v-col v-if="isFieldVisible('place')" cols="12" lg="6">
                     <div aria-required="true">
                       <v-text-field
                         outlined
-- 
GitLab


From 7533daaa44a2f05ccb1f5c1b9a8998b439d477e3 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:57:41 +0100
Subject: [PATCH 62/72] Fix handling of free events

---
 .../components/event_registration/EventRegistrationForm.vue   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 147bbaa..e38c488 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -1082,7 +1082,7 @@ export default {
         user: filteredUserData,
       };
 
-      if (this.event.cost == 0 && this.event.maxCost == 0) {
+      if (this.event.maxCost == 0) {
         const { payment, ...filteredData } = data;
         data = filteredData;
       }
@@ -1166,7 +1166,7 @@ export default {
             this.paweljongSitePreferences
               ?.eventRegistrationAdditionalGuardianHelpText,
         },
-        ...(this.event.cost !== 0 && this.event.maxCost !== 0
+        ...(this.event.maxCost !== 0
           ? [
               {
                 name: "financial",
-- 
GitLab


From a613900f07bf74b82545cd3ce259143ca0eb7622 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 13:58:35 +0100
Subject: [PATCH 63/72] Adjust col widths

---
 .../components/event_registration/EventRegistrationForm.vue   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index e38c488..5e5b143 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -736,7 +736,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               </v-simple-table>
               <v-form v-model="validationStatuses['financial']">
                 <v-row>
-                  <v-col>
+                  <v-col cols="12" md="6">
                     <div aria-required="true">
                       <v-select
                         :items="paweljongPaymentChoices"
@@ -765,7 +765,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       ></v-select>
                     </div>
                   </v-col>
-                  <v-col>
+                  <v-col cols="12" md="6">
                     <div aria-required="true">
                       <positive-small-integer-field
                         outlined
-- 
GitLab


From e8b750cb6a6169811f1df25c3f99eb0eca410e19 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 14:36:20 +0100
Subject: [PATCH 64/72] Change help text position

---
 .../EventRegistrationForm.vue                 | 40 +++++++++----------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 5e5b143..dcff5d4 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -27,6 +27,26 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
     </div>
     <div v-else-if="event">
       <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
+      <v-row v-if="currentParticipantHelpText || currentGuardianHelpText" class="mb-2">
+        <v-col v-if="currentParticipantHelpText" cols="12" :md="currentGuardianHelpText ? '6' : '12'">
+          <v-card>
+            <v-card-title>
+              {{
+                $t("paweljong.event_registration.form.help_text.participant")
+              }}
+            </v-card-title>
+            <v-card-text>{{ currentParticipantHelpText }}</v-card-text>
+          </v-card>
+        </v-col>
+        <v-col v-if="currentGuardianHelpText" cols="12" :md="currentParticipantHelpText ? '6' : '12'">
+          <v-card>
+            <v-card-title>
+              {{ $t("paweljong.event_registration.form.help_text.guardian") }}
+            </v-card-title>
+            <v-card-text>{{ currentGuardianHelpText }}</v-card-text>
+          </v-card>
+        </v-col>
+      </v-row>
       <v-stepper v-model="step" class="mb-4">
         <v-stepper-header class="flex-nowrap">
           <template v-for="(stepChoice, index) in steps">
@@ -884,26 +904,6 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
           </v-stepper-content>
         </v-stepper-items>
       </v-stepper>
-      <v-row v-if="currentParticipantHelpText || currentGuardianHelpText">
-        <v-col v-if="currentParticipantHelpText" cols="12" md="6">
-          <v-card>
-            <v-card-title>
-              {{
-                $t("paweljong.event_registration.form.help_text.participant")
-              }}
-            </v-card-title>
-            <v-card-text>{{ currentParticipantHelpText }}</v-card-text>
-          </v-card>
-        </v-col>
-        <v-col v-if="currentGuardianHelpText" cols="12" md="6">
-          <v-card>
-            <v-card-title>
-              {{ $t("paweljong.event_registration.form.help_text.guardian") }}
-            </v-card-title>
-            <v-card-text>{{ currentGuardianHelpText }}</v-card-text>
-          </v-card>
-        </v-col>
-      </v-row>
     </div>
     <div v-else-if="$apollo.queries.event.loading">
       <v-skeleton-loader type="heading, text, actions" />
-- 
GitLab


From 591ecd485a7ef3d4ffcf49f893c58ae10d5d8fdf Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 15:24:05 +0100
Subject: [PATCH 65/72] Reformat

---
 .../EventRegistrationForm.vue                 | 21 ++++++++++++++-----
 aleksis/apps/paweljong/schema/__init__.py     |  1 -
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index dcff5d4..32337f5 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -27,8 +27,15 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
     </div>
     <div v-else-if="event">
       <h1 class="text-h4 mb-4">{{ event.displayName }}</h1>
-      <v-row v-if="currentParticipantHelpText || currentGuardianHelpText" class="mb-2">
-        <v-col v-if="currentParticipantHelpText" cols="12" :md="currentGuardianHelpText ? '6' : '12'">
+      <v-row
+        v-if="currentParticipantHelpText || currentGuardianHelpText"
+        class="mb-2"
+      >
+        <v-col
+          v-if="currentParticipantHelpText"
+          cols="12"
+          :md="currentGuardianHelpText ? '6' : '12'"
+        >
           <v-card>
             <v-card-title>
               {{
@@ -38,7 +45,11 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
             <v-card-text>{{ currentParticipantHelpText }}</v-card-text>
           </v-card>
         </v-col>
-        <v-col v-if="currentGuardianHelpText" cols="12" :md="currentParticipantHelpText ? '6' : '12'">
+        <v-col
+          v-if="currentGuardianHelpText"
+          cols="12"
+          :md="currentParticipantHelpText ? '6' : '12'"
+        >
           <v-card>
             <v-card-title>
               {{ $t("paweljong.event_registration.form.help_text.guardian") }}
@@ -1102,7 +1113,7 @@ export default {
     },
     steps() {
       return [
-        ...(this.$root.whoAmI.isAnonymous
+        ...(this.$root?.whoAmI.isAnonymous
           ? [
               {
                 name: "email",
@@ -1116,7 +1127,7 @@ export default {
               },
             ]
           : []),
-        ...(this.$root.whoAmI.isAnonymous
+        ...(this.$root?.whoAmI.isAnonymous
           ? [
               {
                 name: "register",
diff --git a/aleksis/apps/paweljong/schema/__init__.py b/aleksis/apps/paweljong/schema/__init__.py
index b97bf4b..763530b 100644
--- a/aleksis/apps/paweljong/schema/__init__.py
+++ b/aleksis/apps/paweljong/schema/__init__.py
@@ -1,4 +1,3 @@
-
 import graphene
 
 from aleksis.apps.tezor.models.base import Client
-- 
GitLab


From 9a54d31246038e4911adcba942b6e1a404e170b6 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 15:29:46 +0100
Subject: [PATCH 66/72] Add color to tab texts

---
 .../event_registration/EventRegistrationForm.vue          | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 32337f5..3c47ddc 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -91,14 +91,14 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                 optional
                 show-arrows
               >
-                <v-tab tab-value="postbuero" style="max-width: 50vw">
+                <v-tab style="max-width: 50vw" class="primary--text">
                   {{
                     $t(
                       "paweljong.event_registration.form.steps.email.choose_mode.continue_aleksis",
                     )
                   }}
                 </v-tab>
-                <v-tab tab-value="own" style="max-width: 50vw">
+                <v-tab  style="max-width: 50vw" class="primary--text">
                   {{
                     $t(
                       "paweljong.event_registration.form.steps.email.choose_mode.continue_own",
@@ -108,7 +108,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               </v-tabs>
               <v-form v-model="validationStatuses['email']">
                 <v-tabs-items v-model="emailMode">
-                  <v-tab-item key="postbuero">
+                  <v-tab-item>
                     <v-row class="mt-4">
                       <v-col cols="12" md="6">
                         <div aria-required="true">
@@ -152,7 +152,7 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                       </v-col>
                     </v-row>
                   </v-tab-item>
-                  <v-tab-item key="own">
+                  <v-tab-item>
                     <v-row class="mt-4">
                       <v-col cols="12" md="6">
                         <div aria-required="true">
-- 
GitLab


From d323a7d6438c4c78bd936ac8de76bbe77ebf13d3 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 16:16:54 +0100
Subject: [PATCH 67/72] Only set guardians if they exist

---
 .../components/event_registration/EventRegistrationForm.vue   | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 3c47ddc..1f3d7e3 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -971,7 +971,9 @@ export default {
           const filteredGuardians = guardians.map(
             ({ __typename, ...filteredGuardian }) => filteredGuardian,
           );
-          this.data.person.guardians = filteredGuardians;
+          if (this.data.person.guardians.length) {
+            this.data.person.guardians = filteredGuardians;
+          }
 
           this.data.person.address = {
             street: street,
-- 
GitLab


From e30813c9e9f131ba710d52de3fd75cc0d5312280 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 16:35:20 +0100
Subject: [PATCH 68/72] Make phonenumber field required again

---
 .../EventRegistrationForm.vue                 | 24 +++++++++++--------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 1f3d7e3..1bf7dff 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -349,16 +349,20 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
                     md="6"
                     lg="4"
                   >
-                    <v-text-field
-                      outlined
-                      v-model="data.person.mobileNumber"
-                      :label="
-                        $t(
-                          'paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label',
-                        )
-                      "
-                      prepend-icon="mdi-phone-outline"
-                    ></v-text-field>
+                    <div aria-required="true">
+                      <v-text-field
+                        outlined
+                        v-model="data.person.mobileNumber"
+                        :label="
+                          $t(
+                            'paweljong.event_registration.form.steps.contact_details.fields.mobile_number.label',
+                          )
+                        "
+                        required
+                        prepend-icon="mdi-phone-outline"
+                        :rules="$rules().required.build()"
+                      ></v-text-field>
+                    </div>
                   </v-col>
                   <v-col v-if="isFieldVisible('sex')" cols="12" lg="4">
                     <div aria-required="true">
-- 
GitLab


From 3211251c3c31d0f83679823eb3b183541fbf44bc Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 16:55:09 +0100
Subject: [PATCH 69/72] Add button for finishing payment

---
 .../event_registration/EventRegistrationForm.vue   | 14 ++++++++++++++
 .../eventRegistrationMutation.graphql              |  1 +
 aleksis/apps/paweljong/frontend/messages/de.json   |  4 +++-
 aleksis/apps/paweljong/frontend/messages/en.json   |  4 +++-
 .../apps/paweljong/schema/event_registration.py    |  7 ++++++-
 5 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 1bf7dff..a5908e4 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -22,7 +22,17 @@ import SecondaryActionButton from "aleksis.core/components/generic/buttons/Secon
               "paweljong.event_registration.form.submitted.submitted_successfully",
             )
           }}
+          <template v-if="doPaymentToken">
+            {{
+              $t(
+                "paweljong.event_registration.form.submitted.payment_text",
+              )
+            }}
+          </template>
         </v-card-text>
+        <v-card-actions v-if="doPaymentToken">
+          <primary-action-button :to="{ name: 'tezor.doPayment', params: { token: doPaymentToken } }" i18n-key="paweljong.event_registration.form.submitted.payment_button"/>
+        </v-card-actions>
       </v-card>
     </div>
     <div v-else-if="event">
@@ -1002,6 +1012,9 @@ export default {
       if (data.sendEventRegistration.ok) {
         this.eventRegistrationSent = true;
       }
+      if (data.sendEventRegistration.doPaymentToken) {
+        this.doPaymentToken = data.sendEventRegistration.doPaymentToken;
+      }
     },
     isFieldVisible(fieldName) {
       return this.event?.contactInformationVisibleFields.includes(fieldName);
@@ -1279,6 +1292,7 @@ export default {
         terms: {},
         retractionConsent: false,
       },
+      doPaymentToken: null,
     };
   },
 };
diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql b/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
index ef1b863..7dc363e 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/eventRegistrationMutation.graphql
@@ -4,5 +4,6 @@ mutation sendEventRegistration(
 ) {
   sendEventRegistration(event: $event, eventRegistration: $eventRegistration) {
     ok
+    doPaymentToken
   }
 }
diff --git a/aleksis/apps/paweljong/frontend/messages/de.json b/aleksis/apps/paweljong/frontend/messages/de.json
index f1928c4..68c8af7 100644
--- a/aleksis/apps/paweljong/frontend/messages/de.json
+++ b/aleksis/apps/paweljong/frontend/messages/de.json
@@ -56,7 +56,9 @@
       "form": {
         "submitted": {
           "thank_you": "Danke!",
-          "submitted_successfully": "Deine Anmeldung wurde erfolgreich abgeschickt."
+          "submitted_successfully": "Deine Anmeldung wurde erfolgreich abgeschickt.",
+          "payment_text": "Du musst noch den Zahlungsvorgang für deine Anmeldung abschließen.",
+          "payment_button": "Zahlung abschließen"
         },
         "steps": {
           "email": {
diff --git a/aleksis/apps/paweljong/frontend/messages/en.json b/aleksis/apps/paweljong/frontend/messages/en.json
index 84c89b9..b7c54f8 100644
--- a/aleksis/apps/paweljong/frontend/messages/en.json
+++ b/aleksis/apps/paweljong/frontend/messages/en.json
@@ -56,7 +56,9 @@
       "form": {
         "submitted": {
           "thank_you": "Thanks!",
-          "submitted_successfully": "Your registration was submitted successfully."
+          "submitted_successfully": "Your registration was submitted successfully.",
+          "payment_text": "You need to finish the payment process for your registration.",
+          "payment_button": "Finish payment"
         },
         "steps": {
           "email": {
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 93eebc0..ef12660 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -51,6 +51,7 @@ class SendEventRegistrationMutation(graphene.Mutation):
         event_registration = EventRegistrationInputType(required=True)
 
     ok = graphene.Boolean()
+    do_payment_token = graphene.String()
 
     @transaction.atomic
     def mutate(
@@ -182,6 +183,8 @@ class SendEventRegistrationMutation(graphene.Mutation):
 
         registration.cost = event.cost
 
+        invoice_token = None
+
         if event.max_cost is None or event.max_cost > 0:
             amount = event_registration["payment"]["amount"]
             if amount is None:
@@ -210,6 +213,8 @@ class SendEventRegistrationMutation(graphene.Mutation):
                 if invoice.variant == "" or invoice.variant is None:
                     raise ValidationError(_("The field 'Payment method' is required."))
             invoice.save()
+            if amount != 0:
+                invoice_token = invoice.token
 
         registration.save()
 
@@ -237,4 +242,4 @@ class SendEventRegistrationMutation(graphene.Mutation):
             user=person,
         )
 
-        return SendEventRegistrationMutation(ok=True)
+        return SendEventRegistrationMutation(ok=True, do_payment_token=invoice_token)
-- 
GitLab


From 5aa00ede21dceb72c4bb6b80c1b4fd340d8f090c Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 17:00:08 +0100
Subject: [PATCH 70/72] Fix guardians check

---
 .../components/event_registration/EventRegistrationForm.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index a5908e4..cf13bc7 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -985,7 +985,7 @@ export default {
           const filteredGuardians = guardians.map(
             ({ __typename, ...filteredGuardian }) => filteredGuardian,
           );
-          if (this.data.person.guardians.length) {
+          if (filteredGuardians.length) {
             this.data.person.guardians = filteredGuardians;
           }
 
-- 
GitLab


From 364bb451bb19efb337cfca9e27ce05ad9493a4c2 Mon Sep 17 00:00:00 2001
From: Tom Teichler <tom.teichler@teckids.org>
Date: Sat, 15 Feb 2025 19:23:23 +0100
Subject: [PATCH 71/72] Fix participation fee

---
 aleksis/apps/paweljong/locale/ar/LC_MESSAGES/django.po     | 6 +++++-
 aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po  | 6 +++++-
 aleksis/apps/paweljong/locale/fr/LC_MESSAGES/django.po     | 6 +++++-
 aleksis/apps/paweljong/locale/la/LC_MESSAGES/django.po     | 6 +++++-
 aleksis/apps/paweljong/locale/nb_NO/LC_MESSAGES/django.po  | 6 +++++-
 aleksis/apps/paweljong/locale/ru/LC_MESSAGES/django.po     | 6 +++++-
 aleksis/apps/paweljong/locale/tr_TR/LC_MESSAGES/django.po  | 6 +++++-
 aleksis/apps/paweljong/locale/uk/LC_MESSAGES/django.po     | 6 +++++-
 aleksis/apps/paweljong/templates/paweljong/event/full.html | 2 +-
 9 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/aleksis/apps/paweljong/locale/ar/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/ar/LC_MESSAGES/django.po
index 4dddbd6..ba796c2 100644
--- a/aleksis/apps/paweljong/locale/ar/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/ar/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -995,6 +995,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po
index 8a2e506..ca5389f 100644
--- a/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -994,6 +994,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/fr/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/fr/LC_MESSAGES/django.po
index 0584bc2..e7ffe22 100644
--- a/aleksis/apps/paweljong/locale/fr/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/fr/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -995,6 +995,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/la/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/la/LC_MESSAGES/django.po
index 8a2e506..ca5389f 100644
--- a/aleksis/apps/paweljong/locale/la/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/la/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -994,6 +994,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/nb_NO/LC_MESSAGES/django.po
index 8a2e506..ca5389f 100644
--- a/aleksis/apps/paweljong/locale/nb_NO/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/nb_NO/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -994,6 +994,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/ru/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/ru/LC_MESSAGES/django.po
index 1f72755..dc197a6 100644
--- a/aleksis/apps/paweljong/locale/ru/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/ru/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -995,6 +995,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/tr_TR/LC_MESSAGES/django.po
index 8a2e506..ca5389f 100644
--- a/aleksis/apps/paweljong/locale/tr_TR/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/tr_TR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -994,6 +994,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/locale/uk/LC_MESSAGES/django.po b/aleksis/apps/paweljong/locale/uk/LC_MESSAGES/django.po
index 343a0ec..e241d3b 100644
--- a/aleksis/apps/paweljong/locale/uk/LC_MESSAGES/django.po
+++ b/aleksis/apps/paweljong/locale/uk/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-15 18:19+0000\n"
+"POT-Creation-Date: 2025-02-15 18:23+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -995,6 +995,10 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
+#: aleksis/apps/paweljong/templates/paweljong/event/full.html:37
+msgid "Self-determined"
+msgstr ""
+
 #: aleksis/apps/paweljong/templates/paweljong/event/full.html:58
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:109
 #: aleksis/apps/paweljong/templates/paweljong/event/register_start.html:119
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/full.html b/aleksis/apps/paweljong/templates/paweljong/event/full.html
index bc5fd3a..06b637b 100644
--- a/aleksis/apps/paweljong/templates/paweljong/event/full.html
+++ b/aleksis/apps/paweljong/templates/paweljong/event/full.html
@@ -34,7 +34,7 @@
             <tr>
               <td><i class="material-icons small">money</i></td>
               <td colspan="2">{% trans "Participation fee (all inclusive)" %}</td>
-              <td>{{ individual_cost }}</td>
+              <td>{% trans "Self-determined" %}</td>
             </tr>
             <tr>
               <td><i class="material-icons small">group</i></td>
-- 
GitLab


From 979aa7855e4ae5fe71aa02cf8e790e9cae1d6b48 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 15 Feb 2025 19:36:37 +0100
Subject: [PATCH 72/72] Fix guardian handling

---
 .../EventRegistrationForm.vue                 | 28 +++++++++++++++----
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index cf13bc7..5ea4d8b 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -987,6 +987,15 @@ export default {
           );
           if (filteredGuardians.length) {
             this.data.person.guardians = filteredGuardians;
+          } else {
+            this.data.person.guardians = [
+              {
+                firstName: "",
+                lastName: "",
+                email: "",
+                mobileNumber: "",
+              },
+            ];
           }
 
           this.data.person.address = {
@@ -1020,12 +1029,19 @@ export default {
       return this.event?.contactInformationVisibleFields.includes(fieldName);
     },
     addGuardian() {
-      this.data.person.guardians.push({
-        firstName: "",
-        lastName: "",
-        email: "",
-        mobileNumber: "",
-      });
+      const updatedPerson = {
+        ...this.data.person,
+        guardians: [
+          ...this.data.person.guardians,
+          {
+            firstName: "",
+            lastName: "",
+            email: "",
+            mobileNumber: "",
+          },
+        ]
+      };
+      this.$set(this.data, "person", updatedPerson);
     },
     isStepEnabled(stepName) {
       return this.steps.some((s) => s.name === stepName);
-- 
GitLab