Index: /views/mods/mod_revocation/page_form.phtml
===================================================================
--- /views/mods/mod_revocation/page_form.phtml	(revision 8538)
+++ /views/mods/mod_revocation/page_form.phtml	(revision 8538)
@@ -0,0 +1,191 @@
+<?php
+
+	declare(strict_types=1);
+
+	/**
+	 * EigenstÃ€ndige Widerrufsformular-Seite (kein Popup).
+	 * Wird von wpsgRevocation\Action::content_filter() auf der Seite
+	 * mit der Option wpsg_page_widerrufsformular gerendert.
+	 *
+	 * @author: Claude
+	 */
+
+	namespace wpsgRevocation;
+
+	use wpsg_ShopController;
+
+	$arReason = Revocation::getReasons();
+
+	// Vorbelegung des Kontaktfeldes fÃŒr eingeloggte Kunden
+	$contact_email = '';
+	$customer_login_id = intval(\wpsg_ShopController::getShop()->callMod('wpsg_mod_kundenverwaltung', 'isLoggedIn'));
+
+	if ($customer_login_id > 0) {
+
+		$oCustomer = \wpsg_customer::getInstance($customer_login_id);
+		$contact_email = $oCustomer->getEmail();
+
+	}
+
+	// Vorbelegung ÃŒber GET-Parameter (z.B. aus Verlinkung in der BestellbestÃ€tigungsmail)
+	$get_order   = isset($_GET['wpsg_revo_order'])   ? \sanitize_text_field($_GET['wpsg_revo_order'])   : '';
+	$get_product = isset($_GET['wpsg_revo_product']) ? \sanitize_text_field($_GET['wpsg_revo_product']) : '';
+	$get_amount  = isset($_GET['wpsg_revo_amount'])  ? max(1, intval($_GET['wpsg_revo_amount']))        : 1;
+	$get_contact = isset($_GET['wpsg_revo_contact']) ? \sanitize_email($_GET['wpsg_revo_contact'])      : '';
+
+	if ($contact_email === '' && $get_contact !== '' && \is_email($get_contact)) $contact_email = $get_contact;
+
+	$key = 0;
+
+?>
+
+<div class="wpsg wpsg_revocation_page">
+
+	<form id="wpsg_revocation_page_form" class="wpsg">
+
+		<div id="wpsg_revocation_page_msg"></div>
+
+		<fieldset class="wpsg_revocation_order_product">
+
+			<div class="form_field">
+				<div class="label"><?php echo \__('Artikel - Nr.', 'wpsgRevocation'); ?></div>
+				<div class="value">
+					<input type="text" name="wpsg_revocation_reason[row][<?php echo $key; ?>][product_nr]" value="<?php echo \esc_attr($get_product); ?>" />
+				</div>
+			</div>
+
+			<div class="form_field">
+				<div class="label"><?php echo \__('Bestellnummer / Menge', 'wpsgRevocation'); ?></div>
+				<div class="value">
+					<input type="text" name="wpsg_revocation_reason[row][<?php echo $key; ?>][order_nr]" value="<?php echo \esc_attr($get_order); ?>" />
+					<input type="number" min="1" name="wpsg_revocation_reason[row][<?php echo $key; ?>][amount]" value="<?php echo intval($get_amount); ?>" />
+				</div>
+			</div>
+
+			<div class="form_field">
+				<div class="label"><?php echo \__('Widerrufsgrund', 'wpsgRevocation'); ?></div>
+				<div class="value">
+					<select name="wpsg_revocation_reason[row][<?php echo $key; ?>][reason]" required>
+						<option value=""><?php echo __("Keinen Grund angeben", 'wpsgRevocation') ?></option>
+						<?php foreach ($arReason as $reason) { ?>
+							<option value="<?php echo \htmlspecialchars($reason['id']); ?>"><?php echo \htmlspecialchars($reason['text']); ?></option>
+						<?php } ?>
+					</select>
+				</div>
+			</div>
+
+			<div class="form_field">
+				<div class="label"><?php echo \__('Bemerkung', 'wpsgRevocation'); ?></div>
+				<div class="value">
+					<textarea name="wpsg_revocation_reason[row][<?php echo $key; ?>][comment]"></textarea>
+				</div>
+			</div>
+
+		</fieldset>
+
+		<div class="form_field">
+			<div class="label"><?php echo \__('Kontakt - E-Mail', 'wpsgRevocation'); ?></div>
+			<div class="value">
+				<input type="email" required name="wpsg_revocation_reason[contact]" value="<?php echo \esc_attr($contact_email); ?>"
+					<?php echo (($customer_login_id > 0 && $contact_email !== '') ? 'readonly' : ''); ?>
+				/>
+			</div>
+		</div>
+
+		<p class="wpsg_revocation_page_datenschutz">
+			<?php echo \__('Es gelten unsere', 'wpsgRevocation'); ?>
+			<a href="<?php echo $GLOBALS['wpsg_sc']->getURL(wpsg_ShopController::URL_DATENSCHUTZ); ?>"><?php echo \__('Datenschutzbestimmungen', 'wpsgRevocation'); ?></a>.
+		</p>
+
+		<div class="form_field wpsg_revocation_page_submit_wrap">
+			<button type="submit" class="button button-primary wpsg_revocation_page_submit"><?php echo \__('Widerruf bestÃ€tigen', 'wpsgRevocation'); ?></button>
+		</div>
+
+	</form>
+
+</div>
+
+<script>
+(function () {
+
+	const form = document.getElementById('wpsg_revocation_page_form');
+	if (form === null) return;
+
+	const nonce    = '<?php echo \wp_create_nonce('wpsg_mod_revocation_nonce'); ?>';
+	const endpoint = '<?php echo \admin_url('admin-ajax.php'); ?>?action=wpsg_mod_revocation&do=ajaxDialogSubmit';
+	const msg      = document.getElementById('wpsg_revocation_page_msg');
+	const button   = form.querySelector('.wpsg_revocation_page_submit');
+
+	const formDataToObject = (formData) => {
+
+		const obj = {};
+
+		for (const [key, value] of formData.entries()) {
+
+			const keys = key.split(/\[|\]/).filter(Boolean);
+			let current = obj;
+
+			keys.forEach((k, i) => {
+
+				const isLast = i === keys.length - 1;
+
+				if (isLast) { current[k] = value; }
+				else {
+
+					if (!current[k]) { current[k] = isNaN(keys[i + 1]) ? {} : []; }
+					current = current[k];
+
+				}
+
+			});
+
+		}
+
+		return obj;
+
+	};
+
+	form.addEventListener('submit', event => {
+
+		event.preventDefault();
+
+		msg.innerHTML = '';
+		button.disabled = true;
+
+		const form_data = formDataToObject(new FormData(form));
+
+		fetch(endpoint, {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify({ nonce: nonce, form_data: form_data })
+		}).then(res => res.json()).then(res => {
+
+			button.disabled = false;
+
+			if ((res.success ?? false) === false) {
+
+				let html = '<ul id="wpsg_error" class="wpsg_error">';
+				(res.arError ?? []).forEach(error => { html += `<li>${error}</li>`; });
+				html += '</ul>';
+				msg.innerHTML = html;
+				msg.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
+			} else {
+
+				form.style.display = 'none';
+				msg.innerHTML = '<div class="wpsg_revocation_page_success"><?php echo \esc_js(\__('Ihr Widerruf ist bei uns eingegangen und wird nun bearbeitet.', 'wpsgRevocation')); ?></div>';
+				msg.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
+			}
+
+		}).catch(() => {
+
+			button.disabled = false;
+			msg.innerHTML = '<ul id="wpsg_error" class="wpsg_error"><li><?php echo \esc_js(\__('Es ist ein Fehler aufgetreten. Bitte versuchen Sie es spÃ€ter erneut.', 'wpsgRevocation')); ?></li></ul>';
+
+		});
+
+	});
+
+})();
+</script>
