diff --git a/aleksis/apps/postbuero/frontend/components/injectables/MailAddressFormStep.vue b/aleksis/apps/postbuero/frontend/components/injectables/MailAddressFormStep.vue new file mode 100644 index 0000000000000000000000000000000000000000..762db6e9fa40e19ea55dd08d244589a50fa42d69 --- /dev/null +++ b/aleksis/apps/postbuero/frontend/components/injectables/MailAddressFormStep.vue @@ -0,0 +1,175 @@ +<template> + <v-form :value="value" @input="$emit('input', getFormStepStatus($event))"> + <v-tabs + v-model="emailMode" + :grow="!$vuetify.breakpoint.mdAndDown" + optional + show-arrows + > + <v-tab style="max-width: 50vw" class="primary--text"> + {{ $t("postbuero.mail_addresses.form_step.mode.new") }} + </v-tab> + <v-tab style="max-width: 50vw" class="primary--text"> + {{ $t("postbuero.mail_addresses.form_step.mode.existing") }} + </v-tab> + </v-tabs> + <v-tabs-items v-model="emailMode"> + <v-tab-item> + <v-row class="mt-4"> + <v-col cols="12" md="6"> + <div aria-required="true"> + <v-text-field + outlined + v-model="data.mailAddress.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.mailAddress.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> + <v-row class="mt-4"> + <v-col cols="12" md="6"> + <div aria-required="true"> + <v-text-field + outlined + v-model="data.accountRegistration.user.email" + :label=" + $t('postbuero.mail_addresses.form_step.fields.email.label') + " + required + :rules=" + emailMode === 1 ? $rules().required.isEmail.build() : [] + " + 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="confirmFields.confirmEmail" + :label=" + $t( + 'postbuero.mail_addresses.form_step.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> + +<script> +import { + mailDomainsForUser, + disallowedLocalParts, +} from "../mail_addresses/mailAddresses.graphql"; + +import formRulesMixin from "aleksis.core/mixins/formRulesMixin"; + +export default { + name: "MailAddressFormStep", + apollo: { + mailDomains: mailDomainsForUser, + disallowedLocalParts: disallowedLocalParts, + }, + mixins: [formRulesMixin], + emits: ["dataChange", "emailModeChange"], + props: { + value: { + type: Boolean, + required: true, + }, + }, + data() { + return { + data: { + mailAddress: { + localPart: "", + domain: "", + }, + accountRegistration: { + user: { + email: "", + }, + }, + }, + confirmFields: { + email: "", + }, + emailMode: null, + }; + }, + computed: { + rules() { + return { + confirmEmail: [ + (v) => + this.data.accountRegistration.user.email == v || + this.$t("accounts.signup.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", + ), + ], + }; + }, + }, + methods: { + getFormStepStatus(formStatus) { + return this.emailMode !== null && formStatus; + }, + }, + watch: { + data: { + deep: true, + handler(newValue) { + this.$emit("dataChange", newValue); + }, + }, + }, +}; +</script> diff --git a/aleksis/apps/postbuero/frontend/components/injectables/registerMailAddress.graphql b/aleksis/apps/postbuero/frontend/components/injectables/registerMailAddress.graphql new file mode 100644 index 0000000000000000000000000000000000000000..461aada433f3f913b6144bb89d57972fdffcf99b --- /dev/null +++ b/aleksis/apps/postbuero/frontend/components/injectables/registerMailAddress.graphql @@ -0,0 +1,7 @@ +mutation registerMailAddress( + $mailAddress: MailAddressInputType +) { + registerMailAddress(mailAddress: $mailAddress) { + ok + } +} diff --git a/aleksis/apps/postbuero/frontend/index.js b/aleksis/apps/postbuero/frontend/index.js index a6e922fc6052b2aa2ef3276b6087a78b7b322232..7db23ffe7f3078fce30ea534a1867b579d4dcf78 100644 --- a/aleksis/apps/postbuero/frontend/index.js +++ b/aleksis/apps/postbuero/frontend/index.js @@ -3,6 +3,24 @@ import { hasPersonValidator, } from "aleksis.core/routeValidators"; +import { registerMailAddress } from "./components/injectables/registerMailAddress.graphql"; + +export const collectionItems = { + coreAccountRegistrationSteps: [ + { + key: "email", + component: () => + import("./components/injectables/MailAddressFormStep.vue"), + }, + ], + coreAccountRegistrationExtraMutations: [ + { + key: "email", + mutation: registerMailAddress, + }, + ], +}; + export default { meta: { inMenu: true, diff --git a/aleksis/apps/postbuero/frontend/messages/en.json b/aleksis/apps/postbuero/frontend/messages/en.json index d955d5ab809acca9dacfb63811b2e5f984676512..227ad28286c8692856a4affe49204f37fea3a546 100644 --- a/aleksis/apps/postbuero/frontend/messages/en.json +++ b/aleksis/apps/postbuero/frontend/messages/en.json @@ -13,7 +13,21 @@ "local_part_disallowed": "This local part name is disallowed." } }, - "create": "Create new mail address" + "create": "Create new mail address", + "form_step": { + "mode": { + "new": "New email address", + "existing": "Existing email address" + }, + "fields": { + "email": { + "label": "E-Mail address" + }, + "confirm_email": { + "label": "Confirm e-mail address" + } + } + } }, "mail_domains": { "title_plural": "Manage mail domains", diff --git a/aleksis/apps/postbuero/schema.py b/aleksis/apps/postbuero/schema.py index 0fc7c5b8c140062e31354ac7a23bfc556fd80776..1502cd04d36aa2131ed4b44ea06983fe0398753a 100644 --- a/aleksis/apps/postbuero/schema.py +++ b/aleksis/apps/postbuero/schema.py @@ -1,5 +1,8 @@ +from typing import Optional + from django.conf import settings -from django.core.exceptions import PermissionDenied +from django.core.exceptions import PermissionDenied, ValidationError +from django.db import IntegrityError from django.utils.translation import gettext_lazy as _ import graphene @@ -113,6 +116,59 @@ class MailDomainBatchPatchMutation(BaseBatchPatchMutation): permissions = ("postbuero.change_maildomain",) +class MailAddressRegistrationMutation(graphene.Mutation): + class Arguments: + mail_address = MailAddressInputType(required=False) + + ok = graphene.Boolean() + + @classmethod + def mutate(cls, root, info, mail_address: Optional[MailAddressInputType] = None): + if not hasattr(info.context, "_registering_person"): + raise PermissionDenied(_("This mutation may only be used during account registration.")) + + person = getattr(info.context, "_registering_person", None) + if person is None: + return MailAddressRegistrationMutation(ok=False) + + if mail_address is not None and mail_address["domain"] is not None and mail_address["domain"] != "" and mail_address["local_part"] is not None and mail_address["local_part"] != "": + try: + domain = MailDomain.objects.get(pk=mail_address.domain) + except IntegrityError as exc: + raise ValidationError(_("Mail domain does not exist.")) from exc + try: + _mail_address = MailAddress.objects.create( + local_part=mail_address.local_part, + domain=domain, + ) + except IntegrityError as exc: + raise ValidationError(_("Mail address already in use.")) from exc + + _mail_address.person = person + _mail_address.save() + + try: + person.email = str(_mail_address) + person.save() + except IntegrityError as exc: + raise ValidationError(_("Person with this mail address already exists.")) from exc + + user = person.user + + try: + user.email = str(_mail_address) + user.save() + except IntegrityError as exc: + raise ValidationError(_("User with this mail address already exists.")) from exc + + return MailAddressRegistrationMutation(ok=True) + elif hasattr(person, "email") and person.email != "": + # If person already has email set, not registering an address here is fine. + return MailAddressRegistrationMutation(ok=True) + else: + raise ValidationError(_("Mail address is required.")) + + class Query(graphene.ObjectType): mail_addresses_for_user = FilterOrderList(MailAddressType) @@ -163,3 +219,5 @@ class Mutation(graphene.ObjectType): create_mail_domains = MailDomainBatchCreateMutation.Field() delete_mail_domains = MailDomainBatchDeleteMutation.Field() patch_mail_domains = MailDomainBatchPatchMutation.Field() + + register_mail_address = MailAddressRegistrationMutation.Field()