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] 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