227 lines
5.9 KiB
Vue
227 lines
5.9 KiB
Vue
<script setup>
|
|
import {computed} from "vue";
|
|
import PaymentMethodInputs from "../components/PaymentMethodInputs.vue";
|
|
import Icon from "../../../../../../Views/Components/Icon.vue";
|
|
|
|
const props = defineProps({formData: Object, event: Object})
|
|
const emit = defineEmits(['next', 'back'])
|
|
|
|
// Für die Aktion freigeschaltete Zahlungsmethoden.
|
|
const methods = computed(() => (props.event.paymentMethods ?? []).filter(m => m.active))
|
|
|
|
const selectedMethod = computed(() =>
|
|
methods.value.find(m => m.slug === props.formData.paymentMethod) ?? null
|
|
)
|
|
|
|
const participantSchema = computed(() => selectedMethod.value?.participantOptionsSchema ?? [])
|
|
|
|
// Font-Awesome-Icon je Zahlungsart: konfiguriertes Symbol hat Vorrang, sonst Slug-Default
|
|
// (Fallback: generische Karte).
|
|
function iconFor(method) {
|
|
const configured = method.configuration?.icon
|
|
if (configured) return configured
|
|
if (method.slug === 'PAYMENT_ACCOUNT_TRANSACTION') return 'building-columns'
|
|
if (method.slug === 'PAYMENT_NOT_DEFINED') return 'money-bill-wave'
|
|
return 'credit-card'
|
|
}
|
|
|
|
function selectMethod(slug) {
|
|
props.formData.paymentMethod = slug
|
|
// Eingaben zurücksetzen -- pro Methode eigene Felder.
|
|
props.formData.paymentOptions = {}
|
|
}
|
|
|
|
// "Weiter" ist erst möglich, wenn eine Methode gewählt und alle Pflicht-Eingaben befüllt sind.
|
|
const canProceed = computed(() => {
|
|
if (!props.formData.paymentMethod) return false
|
|
return participantSchema.value
|
|
.filter(o => o.required)
|
|
.every(o => {
|
|
const value = props.formData.paymentOptions[o.name]
|
|
return value !== undefined && value !== null && String(value).trim() !== ''
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<h3 style="margin: 0 0 6px 0; color: #111827;">Zahlungsweise</h3>
|
|
<p style="margin: 0 0 24px 0; color: #6b7280; font-size: 0.95rem;">Bitte wähle, wie du deinen Teilnahmebeitrag
|
|
begleichen möchtest.</p>
|
|
|
|
<div class="pay-card-row">
|
|
<div
|
|
v-for="method in methods"
|
|
:key="method.slug"
|
|
class="pay-card"
|
|
:class="{ 'pay-card--active': formData.paymentMethod === method.slug }"
|
|
role="radio"
|
|
:aria-checked="formData.paymentMethod === method.slug"
|
|
tabindex="0"
|
|
@click="selectMethod(method.slug)"
|
|
@keydown.enter.prevent="selectMethod(method.slug)"
|
|
@keydown.space.prevent="selectMethod(method.slug)"
|
|
>
|
|
<div class="pay-card__badge">
|
|
<Icon :name="iconFor(method)"/>
|
|
</div>
|
|
<div class="pay-card__body">
|
|
<h4 class="pay-card__title">{{ method.name }}</h4>
|
|
<p v-if="method.description" class="pay-card__desc">{{ method.description }}</p>
|
|
</div>
|
|
<div class="pay-card__check" aria-hidden="true">✓</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="participantSchema.length > 0" class="pay-inputs">
|
|
<PaymentMethodInputs :schema="participantSchema" v-model="formData.paymentOptions"/>
|
|
</div>
|
|
|
|
<div class="btn-row">
|
|
<button type="button" class="btn-secondary" @click="emit('back', 8)">← Zurück</button>
|
|
<button type="button" class="btn-primary" :disabled="!canProceed" @click="emit('next', 10)">Weiter →
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pay-card-row {
|
|
display: flex;
|
|
gap: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.pay-card {
|
|
position: relative;
|
|
flex: 1 1 280px;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
background: #f8fafc;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 12px;
|
|
padding: 28px 20px;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s, box-shadow 0.15s, transform 0.1s;
|
|
text-align: center;
|
|
}
|
|
|
|
.pay-card:hover {
|
|
border-color: #2563eb;
|
|
box-shadow: 0 4px 16px rgba(37, 99, 235, 0.12);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.pay-card--active {
|
|
border-color: #2563eb;
|
|
background: #eff6ff;
|
|
box-shadow: 0 4px 16px rgba(37, 99, 235, 0.12);
|
|
}
|
|
|
|
.pay-card__badge {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 64px;
|
|
height: 64px;
|
|
margin-bottom: 14px;
|
|
font-size: 1.6rem;
|
|
color: #2563eb;
|
|
background: #e0f2fe;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.pay-card--active .pay-card__badge {
|
|
background: #dbeafe;
|
|
}
|
|
|
|
.pay-card__body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.pay-card__title {
|
|
margin: 0;
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
color: #111827;
|
|
}
|
|
|
|
.pay-card__desc {
|
|
margin: 0;
|
|
font-size: 0.9rem;
|
|
color: #374151;
|
|
}
|
|
|
|
.pay-card__check {
|
|
position: absolute;
|
|
top: 12px;
|
|
right: 12px;
|
|
width: 24px;
|
|
height: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 50%;
|
|
background: #2563eb;
|
|
color: #fff;
|
|
font-size: 0.8rem;
|
|
font-weight: 700;
|
|
opacity: 0;
|
|
transform: scale(0.6);
|
|
transition: opacity 0.12s, transform 0.12s;
|
|
}
|
|
|
|
.pay-card--active .pay-card__check {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.pay-inputs {
|
|
margin-top: 20px;
|
|
padding: 16px;
|
|
background: #f8fafc;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
/* ─── Smartphone ─── */
|
|
@media (max-width: 639px) {
|
|
.pay-card-row {
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.pay-card {
|
|
flex: 1 1 100%;
|
|
flex-direction: row;
|
|
text-align: left;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 16px 14px;
|
|
}
|
|
|
|
.pay-card__badge {
|
|
flex: 0 0 52px;
|
|
width: 52px;
|
|
height: 52px;
|
|
margin-bottom: 0;
|
|
font-size: 1.6rem;
|
|
}
|
|
|
|
.pay-card__body {
|
|
flex: 1;
|
|
align-items: flex-start;
|
|
text-align: left;
|
|
}
|
|
|
|
.pay-card__title {
|
|
font-size: 1rem;
|
|
}
|
|
}
|
|
</style>
|