250 lines
7.9 KiB
Vue
250 lines
7.9 KiB
Vue
<script setup>
|
|
import {reactive, ref} from 'vue'
|
|
import {toast} from 'vue3-toastify'
|
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
|
import ShadowedBox from '../../../Views/Components/ShadowedBox.vue'
|
|
import ErrorText from '../../../Views/Components/ErrorText.vue'
|
|
import IbanInput from '../../../Views/Components/IbanInput.vue'
|
|
import TextResource from '../../../Views/Components/TextResource.vue'
|
|
import {useAjax} from '../../../../resources/js/components/ajaxHandler.js'
|
|
|
|
const {request} = useAjax()
|
|
|
|
/**
|
|
* `state` steuert die ganze Seite:
|
|
* open -- Formular für die Bankverbindung
|
|
* accepted -- Angaben liegen vor, nichts mehr zu tun
|
|
* unavailable -- Token unbekannt oder Erstattung abgebrochen
|
|
*/
|
|
const props = defineProps({
|
|
state: String,
|
|
message: String,
|
|
token: String,
|
|
name: String,
|
|
eventTitle: String,
|
|
eventEmail: String,
|
|
amount: String,
|
|
reason: String,
|
|
reasonNote: String,
|
|
acceptedAt: String,
|
|
})
|
|
|
|
// Der Zustand wandert in ein ref, damit die Seite nach dem Absenden umschalten kann, ohne neu zu laden.
|
|
const state = ref(props.state)
|
|
|
|
const form = reactive({accountOwner: '', accountIban: '', declarationAccepted: false})
|
|
const errors = reactive({accountOwner: '', accountIban: '', declaration: ''})
|
|
const saving = ref(false)
|
|
|
|
function validate() {
|
|
errors.accountOwner = form.accountOwner.trim() ? '' : 'Bitte gib an, wem das Konto gehört.'
|
|
errors.accountIban = form.accountIban.trim() ? '' : 'Bitte gib die IBAN des Kontos ein.'
|
|
errors.declaration = form.declarationAccepted ? '' : 'Bitte bestätige die Erklärung.'
|
|
|
|
return !errors.accountOwner && !errors.accountIban && !errors.declaration
|
|
}
|
|
|
|
async function submit() {
|
|
if (!validate() || saving.value) return
|
|
|
|
saving.value = true
|
|
|
|
try {
|
|
const response = await request('/api/v1/participant-refund/' + props.token + '/accept', {
|
|
method: 'POST',
|
|
body: {
|
|
accountOwner: form.accountOwner,
|
|
accountIban: form.accountIban,
|
|
declarationAccepted: form.declarationAccepted,
|
|
},
|
|
})
|
|
|
|
if (!response) {
|
|
toast.error('Deine Angaben konnten nicht gespeichert werden. Bitte versuche es später erneut.')
|
|
return
|
|
}
|
|
|
|
// Feldbezogene Fehler kommen mit Status 200 als `error_types` zurück -- der HttpClient des
|
|
// Projekts verwirft Antworten mit Fehlerstatus.
|
|
if (response.status !== 'success') {
|
|
Object.keys(response.error_types ?? {}).forEach((key) => {
|
|
if (key in errors) errors[key] = response.error_types[key]
|
|
})
|
|
|
|
toast.error(response.message ?? 'Bitte prüfe deine Angaben.')
|
|
return
|
|
}
|
|
|
|
toast.success(response.message)
|
|
state.value = 'accepted'
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout title="Rückerstattung">
|
|
<shadowed-box style="max-width: 640px; margin: 60px auto; padding: 24px;">
|
|
|
|
<template v-if="state === 'unavailable'">
|
|
<h2>Rückerstattung</h2>
|
|
<p class="hint">{{ props.message }}</p>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<h2>Rückerstattung deines Teilnahmebeitrags</h2>
|
|
|
|
<p>
|
|
Hallo {{ props.name }}, für deine Abmeldung von der Veranstaltung
|
|
<strong>{{ props.eventTitle }}</strong> wurde eine Rückerstattung freigegeben.
|
|
</p>
|
|
|
|
<table class="summary">
|
|
<tr>
|
|
<th>Betrag</th>
|
|
<td><strong>{{ props.amount }}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Grund</th>
|
|
<td>{{ props.reason }}</td>
|
|
</tr>
|
|
<tr v-if="props.reasonNote">
|
|
<th>Anmerkung</th>
|
|
<td>{{ props.reasonNote }}</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<template v-if="state === 'accepted'">
|
|
<p class="hint">
|
|
Deine Angaben liegen uns vor<span v-if="props.acceptedAt"> (seit {{ props.acceptedAt }})</span>.
|
|
Die Überweisung veranlasst die Aktionsleitung. Stimmt etwas nicht, wende dich bitte
|
|
an sie: {{ props.eventEmail }}
|
|
</p>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<p class="hint">
|
|
Der Betrag steht fest und lässt sich hier nicht ändern. Hast du dazu Fragen, wende
|
|
dich bitte an die Aktionsleitung: {{ props.eventEmail }}
|
|
</p>
|
|
|
|
<h3>Auf welches Konto sollen wir überweisen?</h3>
|
|
|
|
<form @submit.prevent="submit">
|
|
<div class="field">
|
|
<label for="account-owner">Kontoinhaber*in</label>
|
|
<input
|
|
id="account-owner"
|
|
v-model="form.accountOwner"
|
|
type="text"
|
|
class="form-input"
|
|
autocomplete="name"
|
|
/>
|
|
<ErrorText :message="errors.accountOwner" />
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="account-iban">IBAN</label>
|
|
<IbanInput id="account-iban" v-model="form.accountIban" class="form-input" />
|
|
<ErrorText :message="errors.accountIban" />
|
|
</div>
|
|
|
|
<!--
|
|
Die Erklärung, die anschließend auf dem Eigenbeleg steht. Sie muss hier
|
|
gelesen und angekreuzt werden -- sonst schriebe der Beleg dem Teili eine
|
|
Zusicherung zu, die er nie abgegeben hat.
|
|
-->
|
|
<div class="declaration">
|
|
<input
|
|
id="refund-declaration"
|
|
v-model="form.declarationAccepted"
|
|
type="checkbox"
|
|
/>
|
|
<TextResource
|
|
text-name="CONFIRMATION_PARTICIPANT_REFUND"
|
|
belongs-to="refund-declaration"
|
|
/>
|
|
</div>
|
|
<ErrorText :message="errors.declaration" />
|
|
|
|
<button class="button" type="submit" :disabled="saving || !form.declarationAccepted">
|
|
{{ saving ? 'Wird gespeichert…' : 'Angaben absenden' }}
|
|
</button>
|
|
</form>
|
|
</template>
|
|
</template>
|
|
|
|
</shadowed-box>
|
|
</AppLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
h2 {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
h3 {
|
|
margin: 24px 0 12px;
|
|
}
|
|
|
|
.hint {
|
|
margin: 16px 0;
|
|
padding: 10px 12px;
|
|
border-left: 3px solid #f5c400;
|
|
background-color: #fffef5;
|
|
font-size: 0.9rem;
|
|
color: #4b5563;
|
|
}
|
|
|
|
.summary {
|
|
border-collapse: collapse;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.summary th {
|
|
text-align: left;
|
|
padding: 6px 24px 6px 0;
|
|
color: #555;
|
|
font-weight: normal;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.summary td {
|
|
padding: 6px 0;
|
|
}
|
|
|
|
.field {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.field label {
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
font-size: 0.9rem;
|
|
color: #4b5563;
|
|
}
|
|
|
|
.field .form-input {
|
|
width: 100%;
|
|
}
|
|
|
|
.declaration {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
margin: 20px 0 8px;
|
|
font-size: 0.9rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.declaration input {
|
|
margin-top: 3px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.declaration :deep(label) {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|