Creating Participation refunds
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user