Fix für Short-Signups
This commit is contained in:
@@ -3,6 +3,7 @@ import {computed} from 'vue'
|
||||
import {useShortSignupForm} from './composables/useShortSignupForm.js'
|
||||
import {
|
||||
SHORT_STEP_PERSON,
|
||||
SHORT_STEP_FEE,
|
||||
SHORT_STEP_PARTICIPATION_OPTIONS,
|
||||
SHORT_STEP_PHOTO,
|
||||
SHORT_STEP_PAYMENT,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
stepVisible,
|
||||
} from './composables/shortStepFlow.js'
|
||||
import ShortStepPerson from './steps/ShortStepPerson.vue'
|
||||
import ShortStepFee from './steps/ShortStepFee.vue'
|
||||
import ShortStepSummary from './steps/ShortStepSummary.vue'
|
||||
import StepParticipationOptions from '../SignUpForm/steps/StepParticipationOptions.vue'
|
||||
import StepPhotoPermissions from '../SignUpForm/steps/StepPhotoPermissions.vue'
|
||||
@@ -25,7 +27,7 @@ const props = defineProps({
|
||||
const {
|
||||
currentStep, goToStep, goNext, goBack, formData, isMinor, localGroups, needsLocalGroup,
|
||||
submit, submitting, submitResult, submitError,
|
||||
summaryLoading, summaryAmount, summaryAmountValue,
|
||||
summaryLoading, summaryAmount, feeOptions, selectedFee, flowState,
|
||||
} = useShortSignupForm(props.event, props.participantData)
|
||||
|
||||
// Die aus dem langen Prozess übernommenen Schritte emittieren ihre Zielnummer aus jenem Flow mit. Hier zählt
|
||||
@@ -35,6 +37,7 @@ const onBack = () => goBack()
|
||||
|
||||
const allSteps = [
|
||||
{step: SHORT_STEP_PERSON, label: 'Person'},
|
||||
{step: SHORT_STEP_FEE, label: 'Beitrag'},
|
||||
{step: SHORT_STEP_PARTICIPATION_OPTIONS, label: 'Teilnahmeoptionen'},
|
||||
{step: SHORT_STEP_PHOTO, label: 'Fotoerlaubnis'},
|
||||
{step: SHORT_STEP_PAYMENT, label: 'Zahlungsart'},
|
||||
@@ -43,7 +46,7 @@ const allSteps = [
|
||||
|
||||
// Nur die tatsächlich vorkommenden Schritte anzeigen, sonst zeigt die Leiste Schritte, die nie erscheinen.
|
||||
const steps = computed(() =>
|
||||
allSteps.filter(s => stepVisible(props.event, s.step, summaryAmountValue.value))
|
||||
allSteps.filter(s => stepVisible(props.event, s.step, flowState.value))
|
||||
)
|
||||
|
||||
const currentIndex = computed(() => steps.value.findIndex(s => s.step === currentStep.value))
|
||||
@@ -101,6 +104,11 @@ const currentIndex = computed(() => steps.value.findIndex(s => s.step === curren
|
||||
:localGroups="localGroups" :needsLocalGroup="needsLocalGroup"
|
||||
@next="onNext"
|
||||
/>
|
||||
<ShortStepFee
|
||||
v-if="currentStep === SHORT_STEP_FEE"
|
||||
:formData="formData" :feeOptions="feeOptions"
|
||||
@next="onNext" @back="onBack"
|
||||
/>
|
||||
<StepParticipationOptions
|
||||
v-if="currentStep === SHORT_STEP_PARTICIPATION_OPTIONS"
|
||||
:formData="formData" :event="event"
|
||||
@@ -122,7 +130,7 @@ const currentIndex = computed(() => steps.value.findIndex(s => s.step === curren
|
||||
:event="event"
|
||||
:isMinor="isMinor"
|
||||
:summaryAmount="summaryAmount"
|
||||
:summaryAmountValue="summaryAmountValue"
|
||||
:feeLabel="feeOptions.length > 1 ? selectedFee?.label : null"
|
||||
:summaryLoading="summaryLoading"
|
||||
:submitting="submitting"
|
||||
:submitError="submitError"
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
// Schrittreihenfolge der Kurzanmeldung. Zwei Schritte sind optional: die Teilnahmeoptionen entfallen, wenn für
|
||||
// Schrittreihenfolge der Kurzanmeldung. Drei Schritte sind optional: der Beitrag erscheint nur, wenn neben dem
|
||||
// Standardbeitrag weitere Stufen (reduziert, Solidarität) hinterlegt sind, die Teilnahmeoptionen entfallen, wenn für
|
||||
// die Veranstaltung keine definiert sind, die Zahlungsart entfällt bei Betrag 0 oder wenn es ohnehin nur eine
|
||||
// Zahlungsart gibt (die wird dann vorausgewählt).
|
||||
export const SHORT_STEP_PERSON = 1
|
||||
export const SHORT_STEP_PARTICIPATION_OPTIONS = 2
|
||||
export const SHORT_STEP_PHOTO = 3
|
||||
export const SHORT_STEP_PAYMENT = 4
|
||||
export const SHORT_STEP_SUMMARY = 5
|
||||
export const SHORT_STEP_FEE = 2
|
||||
export const SHORT_STEP_PARTICIPATION_OPTIONS = 3
|
||||
export const SHORT_STEP_PHOTO = 4
|
||||
export const SHORT_STEP_PAYMENT = 5
|
||||
export const SHORT_STEP_SUMMARY = 6
|
||||
|
||||
// `state` kommt mit den Beiträgen vom Server: { amountValue: Betrag der gewählten Stufe, feeOptionCount }.
|
||||
export function stepVisible(event, step, state) {
|
||||
if (step === SHORT_STEP_FEE) {
|
||||
return state.feeOptionCount > 1
|
||||
}
|
||||
|
||||
export function stepVisible(event, step, amountValue) {
|
||||
if (step === SHORT_STEP_PARTICIPATION_OPTIONS) {
|
||||
return (event.participationOptions?.length ?? 0) > 0
|
||||
}
|
||||
|
||||
if (step === SHORT_STEP_PAYMENT) {
|
||||
const activeMethods = (event.paymentMethods ?? []).filter(m => m.active)
|
||||
return amountValue > 0 && activeMethods.length > 1
|
||||
return state.amountValue > 0 && activeMethods.length > 1
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function nextVisibleFrom(event, step, amountValue) {
|
||||
export function nextVisibleFrom(event, step, state) {
|
||||
let candidate = step + 1
|
||||
while (candidate < SHORT_STEP_SUMMARY && !stepVisible(event, candidate, amountValue)) {
|
||||
while (candidate < SHORT_STEP_SUMMARY && !stepVisible(event, candidate, state)) {
|
||||
candidate++
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
export function prevVisibleFrom(event, step, amountValue) {
|
||||
export function prevVisibleFrom(event, step, state) {
|
||||
let candidate = step - 1
|
||||
while (candidate > SHORT_STEP_PERSON && !stepVisible(event, candidate, amountValue)) {
|
||||
while (candidate > SHORT_STEP_PERSON && !stepVisible(event, candidate, state)) {
|
||||
candidate--
|
||||
}
|
||||
return candidate
|
||||
|
||||
+36
-14
@@ -1,4 +1,4 @@
|
||||
import {computed, reactive, ref} from 'vue'
|
||||
import {computed, reactive, ref, watch} from 'vue'
|
||||
import axios from 'axios'
|
||||
import {differenceInYears, parseISO} from 'date-fns'
|
||||
import {SHORT_STEP_PERSON, nextVisibleFrom, prevVisibleFrom} from './shortStepFlow.js'
|
||||
@@ -33,6 +33,8 @@ export function useShortSignupForm(event, participantData) {
|
||||
intolerances: participantData.intolerances ?? '',
|
||||
first_aid: '-1',
|
||||
badeerlaubnis: '-1',
|
||||
// Beitragsstufe; wählbar nur, wenn weitere Stufen hinterlegt sind.
|
||||
feeType: 'standard',
|
||||
// Antworten auf die event-spezifischen Teilnahmeoptionen: { [question.key]: option.value }
|
||||
participationOptions: {},
|
||||
foto: {socialmedia: false, print: false, webseite: false, partner: false, intern: false},
|
||||
@@ -53,12 +55,18 @@ export function useShortSignupForm(event, participantData) {
|
||||
return differenceInYears(new Date(), parseISO(formData.geburtsdatum)) < 18
|
||||
})
|
||||
|
||||
const summaryAmount = ref('')
|
||||
const summaryAmountValue = ref(0)
|
||||
// Beitragsstufen mit fertig berechnetem Betrag: [{ value, label, amount, amountValue }]
|
||||
const feeOptions = ref([])
|
||||
const amountLoaded = ref(false)
|
||||
|
||||
// Der Betrag steht für die ganze Anmeldung fest (feste Teilnahmegruppe, fester Zeitraum) und wird deshalb
|
||||
// genau einmal geholt. Er entscheidet, ob der Zahlungsschritt überhaupt vorkommt.
|
||||
const selectedFee = computed(() =>
|
||||
feeOptions.value.find(option => option.value === formData.feeType) ?? feeOptions.value[0] ?? null
|
||||
)
|
||||
const summaryAmount = computed(() => selectedFee.value?.amount ?? '')
|
||||
const summaryAmountValue = computed(() => Number(selectedFee.value?.amountValue ?? 0))
|
||||
|
||||
// Teilnahmegruppe und Zeitraum stehen für die ganze Anmeldung fest, deshalb werden die Beträge aller Stufen
|
||||
// genau einmal geholt. Die Wahl der Stufe entscheidet dann nur noch lokal über den Betrag.
|
||||
const ensureAmount = async () => {
|
||||
if (amountLoaded.value) {
|
||||
return
|
||||
@@ -67,31 +75,41 @@ export function useShortSignupForm(event, participantData) {
|
||||
summaryLoading.value = true
|
||||
try {
|
||||
const res = await axios.post('/api/v1/event/' + event.id + '/short-calculate-amount')
|
||||
summaryAmount.value = res.data.amount
|
||||
summaryAmountValue.value = Number(res.data.amountValue ?? 0)
|
||||
feeOptions.value = res.data.feeTypes ?? []
|
||||
amountLoaded.value = true
|
||||
|
||||
if (summaryAmountValue.value <= 0) {
|
||||
formData.paymentMethod = null
|
||||
formData.paymentOptions = {}
|
||||
}
|
||||
} finally {
|
||||
summaryLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Bei Betrag 0 gibt es nichts zu bezahlen. Wechselt die Stufe zurück auf einen Betrag, wird die einzige
|
||||
// Zahlungsart wieder vorausgewählt -- sonst fragt sie niemand mehr ab.
|
||||
watch(summaryAmountValue, (value) => {
|
||||
if (value <= 0) {
|
||||
formData.paymentMethod = null
|
||||
formData.paymentOptions = {}
|
||||
} else if (formData.paymentMethod === null && activeMethods.length === 1) {
|
||||
formData.paymentMethod = activeMethods[0].slug
|
||||
}
|
||||
})
|
||||
|
||||
const flowState = computed(() => ({
|
||||
amountValue: summaryAmountValue.value,
|
||||
feeOptionCount: feeOptions.value.length,
|
||||
}))
|
||||
|
||||
const goToStep = (step) => {
|
||||
currentStep.value = step
|
||||
}
|
||||
|
||||
const goNext = async () => {
|
||||
await ensureAmount()
|
||||
goToStep(nextVisibleFrom(event, currentStep.value, summaryAmountValue.value))
|
||||
goToStep(nextVisibleFrom(event, currentStep.value, flowState.value))
|
||||
}
|
||||
|
||||
const goBack = async () => {
|
||||
await ensureAmount()
|
||||
goToStep(prevVisibleFrom(event, currentStep.value, summaryAmountValue.value))
|
||||
goToStep(prevVisibleFrom(event, currentStep.value, flowState.value))
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
@@ -120,6 +138,7 @@ export function useShortSignupForm(event, participantData) {
|
||||
participationOptions: formData.participationOptions,
|
||||
paymentMethod: formData.paymentMethod,
|
||||
paymentOptions: formData.paymentOptions,
|
||||
feeType: formData.feeType,
|
||||
})
|
||||
|
||||
submitResult.value = {
|
||||
@@ -150,6 +169,9 @@ export function useShortSignupForm(event, participantData) {
|
||||
summaryLoading,
|
||||
summaryAmount,
|
||||
summaryAmountValue,
|
||||
feeOptions,
|
||||
selectedFee,
|
||||
flowState,
|
||||
ensureAmount,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
// Erscheint nur, wenn neben dem Standardbeitrag weitere Stufen hinterlegt sind. Die Beträge gelten für die gesamte
|
||||
// Veranstaltung -- die Kurzanmeldung hat keinen wählbaren Zeitraum.
|
||||
defineProps({
|
||||
formData: Object,
|
||||
feeOptions: {type: Array, default: () => []},
|
||||
})
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>Teilnahmebeitrag</h3>
|
||||
<p style="margin: 0 0 16px 0; color: #6b7280; font-size: 0.95rem;">
|
||||
Bitte wähle deinen Beitrag für die gesamte Veranstaltung.
|
||||
</p>
|
||||
|
||||
<label
|
||||
v-for="option in feeOptions"
|
||||
:key="option.value"
|
||||
style="display: block; margin-bottom: 8px; cursor: pointer;"
|
||||
>
|
||||
<input type="radio" v-model="formData.feeType" :value="option.value" />
|
||||
{{ option.label }}
|
||||
<span style="color: #606060;">({{ option.amount }})</span>
|
||||
</label>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="emit('back')">← Zurück</button>
|
||||
<button type="button" class="btn-primary" @click="emit('next')">Weiter →</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,7 +8,8 @@ const props = defineProps({
|
||||
event: Object,
|
||||
isMinor: Boolean,
|
||||
summaryAmount: String,
|
||||
summaryAmountValue: {type: Number, default: 0},
|
||||
// Nur gesetzt, wenn zwischen mehreren Beitragsstufen gewählt werden konnte.
|
||||
feeLabel: {type: String, default: null},
|
||||
summaryLoading: Boolean,
|
||||
submitting: Boolean,
|
||||
submitError: {type: String, default: ''},
|
||||
@@ -137,6 +138,10 @@ const canSubmit = computed(() =>
|
||||
|
||||
<h4 style="margin: 0 0 8px 0;">Kostenübersicht</h4>
|
||||
<table class="form-table cost-table" style="margin-bottom: 20px;">
|
||||
<tr v-if="feeLabel">
|
||||
<td>Beitragsstufe:</td>
|
||||
<td>{{ feeLabel }}</td>
|
||||
</tr>
|
||||
<tr class="cost-total">
|
||||
<td><strong>Teilnahmebeitrag:</strong></td>
|
||||
<td><strong>{{ summaryAmount }}</strong></td>
|
||||
|
||||
Reference in New Issue
Block a user