|
@@ -1,15 +1,22 @@
|
|
|
(function () {
|
|
(function () {
|
|
|
const EMAIL_STORAGE_KEY = 'ff_member_form_email_v1';
|
|
const EMAIL_STORAGE_KEY = 'ff_member_form_email_v1';
|
|
|
|
|
+ const AUTO_OTP_SESSION_PREFIX = 'ff_member_form_auto_otp_sent_v1:';
|
|
|
const boot = window.APP_BOOT || { steps: [], csrf: '', contactEmail: '' };
|
|
const boot = window.APP_BOOT || { steps: [], csrf: '', contactEmail: '' };
|
|
|
const baseUrl = String(boot.baseUrl || '').replace(/\/+$/, '');
|
|
const baseUrl = String(boot.baseUrl || '').replace(/\/+$/, '');
|
|
|
const schemaSteps = Array.isArray(boot.steps) ? boot.steps : [];
|
|
const schemaSteps = Array.isArray(boot.steps) ? boot.steps : [];
|
|
|
|
|
+ const verificationConfig = boot.verification && typeof boot.verification === 'object' ? boot.verification : {};
|
|
|
|
|
|
|
|
const state = {
|
|
const state = {
|
|
|
email: '',
|
|
email: '',
|
|
|
|
|
+ otpEmail: '',
|
|
|
|
|
+ isVerified: false,
|
|
|
|
|
+ lastUserActivityAt: Math.floor(Date.now() / 1000),
|
|
|
currentStep: 1,
|
|
currentStep: 1,
|
|
|
totalSteps: schemaSteps.length,
|
|
totalSteps: schemaSteps.length,
|
|
|
summaryStep: schemaSteps.length + 1,
|
|
summaryStep: schemaSteps.length + 1,
|
|
|
autosaveId: null,
|
|
autosaveId: null,
|
|
|
|
|
+ otpCooldownId: null,
|
|
|
|
|
+ otpCooldownRemaining: 0,
|
|
|
uploads: {},
|
|
uploads: {},
|
|
|
isSubmitting: false,
|
|
isSubmitting: false,
|
|
|
summaryMissingCount: 0,
|
|
summaryMissingCount: 0,
|
|
@@ -28,6 +35,13 @@
|
|
|
const startEmailInput = document.getElementById('startEmail');
|
|
const startEmailInput = document.getElementById('startEmail');
|
|
|
const startEmailError = document.getElementById('startEmailError');
|
|
const startEmailError = document.getElementById('startEmailError');
|
|
|
const startSubmitBtn = document.getElementById('startSubmitBtn');
|
|
const startSubmitBtn = document.getElementById('startSubmitBtn');
|
|
|
|
|
+ const otpSection = document.getElementById('otpSection');
|
|
|
|
|
+ const otpInfoText = document.getElementById('otpInfoText');
|
|
|
|
|
+ const startOtpInput = document.getElementById('startOtp');
|
|
|
|
|
+ const startOtpError = document.getElementById('startOtpError');
|
|
|
|
|
+ const verifyOtpBtn = document.getElementById('verifyOtpBtn');
|
|
|
|
|
+ const resendOtpBtn = document.getElementById('resendOtpBtn');
|
|
|
|
|
+ const otpCooldownMessage = document.getElementById('otpCooldownMessage');
|
|
|
const resetDataBtn = document.getElementById('resetDataBtn');
|
|
const resetDataBtn = document.getElementById('resetDataBtn');
|
|
|
const compactStatusBox = document.getElementById('compactStatusBox');
|
|
const compactStatusBox = document.getElementById('compactStatusBox');
|
|
|
const statusEmailValue = document.getElementById('statusEmailValue');
|
|
const statusEmailValue = document.getElementById('statusEmailValue');
|
|
@@ -136,6 +150,13 @@
|
|
|
startEmailError.textContent = text || '';
|
|
startEmailError.textContent = text || '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function setStartOtpError(text) {
|
|
|
|
|
+ if (!startOtpError) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ startOtpError.textContent = text || '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function setDisclaimerError(text) {
|
|
function setDisclaimerError(text) {
|
|
|
if (!disclaimerReadError) {
|
|
if (!disclaimerReadError) {
|
|
|
return;
|
|
return;
|
|
@@ -169,6 +190,10 @@
|
|
|
return (email || '').trim().toLowerCase();
|
|
return (email || '').trim().toLowerCase();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function normalizeOtpCode(code) {
|
|
|
|
|
+ return String(code || '').replace(/[^\d]/g, '').slice(0, 6);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function isValidEmail(email) {
|
|
function isValidEmail(email) {
|
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
|
}
|
|
}
|
|
@@ -197,6 +222,26 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function autoOtpSessionKey(email) {
|
|
|
|
|
+ return AUTO_OTP_SESSION_PREFIX + normalizeEmail(email);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function hasAutoOtpSessionFlag(email) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return sessionStorage.getItem(autoOtpSessionKey(email)) === '1';
|
|
|
|
|
+ } catch (_err) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function markAutoOtpSessionFlag(email) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ sessionStorage.setItem(autoOtpSessionKey(email), '1');
|
|
|
|
|
+ } catch (_err) {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function updateDisclaimerAcceptanceState() {
|
|
function updateDisclaimerAcceptanceState() {
|
|
|
if (!acceptDisclaimerBtn || !disclaimerReadCheckbox) {
|
|
if (!acceptDisclaimerBtn || !disclaimerReadCheckbox) {
|
|
|
return;
|
|
return;
|
|
@@ -225,6 +270,9 @@
|
|
|
startIntroText.classList.add('hidden');
|
|
startIntroText.classList.add('hidden');
|
|
|
startEmailField.classList.add('hidden');
|
|
startEmailField.classList.add('hidden');
|
|
|
startActions.classList.add('hidden');
|
|
startActions.classList.add('hidden');
|
|
|
|
|
+ if (otpSection) {
|
|
|
|
|
+ otpSection.classList.add('hidden');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function leaveCompactStatus() {
|
|
function leaveCompactStatus() {
|
|
@@ -235,11 +283,91 @@
|
|
|
startActions.classList.remove('hidden');
|
|
startActions.classList.remove('hidden');
|
|
|
statusEmailValue.textContent = '-';
|
|
statusEmailValue.textContent = '-';
|
|
|
setDraftStatus('Noch nicht gespeichert', false);
|
|
setDraftStatus('Noch nicht gespeichert', false);
|
|
|
- setResetActionVisible(true);
|
|
|
|
|
|
|
+ setResetActionVisible(false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function showOtpSection(email, message) {
|
|
|
|
|
+ state.otpEmail = normalizeEmail(email);
|
|
|
|
|
+ if (otpSection) {
|
|
|
|
|
+ otpSection.classList.remove('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (otpInfoText) {
|
|
|
|
|
+ otpInfoText.textContent = message || ('Code wurde an ' + state.otpEmail + ' gesendet.');
|
|
|
|
|
+ otpInfoText.classList.remove('error-text');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (startOtpInput) {
|
|
|
|
|
+ startOtpInput.value = '';
|
|
|
|
|
+ startOtpInput.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ setStartOtpError('');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function hideOtpSection() {
|
|
|
|
|
+ if (otpSection) {
|
|
|
|
|
+ otpSection.classList.add('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (otpInfoText) {
|
|
|
|
|
+ otpInfoText.textContent = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ setStartOtpError('');
|
|
|
|
|
+ clearOtpCooldown();
|
|
|
|
|
+ state.otpEmail = '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function clearOtpCooldown() {
|
|
|
|
|
+ if (state.otpCooldownId) {
|
|
|
|
|
+ clearInterval(state.otpCooldownId);
|
|
|
|
|
+ state.otpCooldownId = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ state.otpCooldownRemaining = 0;
|
|
|
|
|
+ if (resendOtpBtn) {
|
|
|
|
|
+ resendOtpBtn.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (otpCooldownMessage) {
|
|
|
|
|
+ otpCooldownMessage.textContent = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function setOtpCooldown(seconds) {
|
|
|
|
|
+ clearOtpCooldown();
|
|
|
|
|
+ const total = Math.max(0, Number(seconds || 0));
|
|
|
|
|
+ state.otpCooldownRemaining = total;
|
|
|
|
|
+ if (total <= 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (resendOtpBtn) {
|
|
|
|
|
+ resendOtpBtn.disabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const update = () => {
|
|
|
|
|
+ if (!otpCooldownMessage) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (state.otpCooldownRemaining <= 0) {
|
|
|
|
|
+ otpCooldownMessage.textContent = '';
|
|
|
|
|
+ if (resendOtpBtn) {
|
|
|
|
|
+ resendOtpBtn.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (state.otpCooldownId) {
|
|
|
|
|
+ clearInterval(state.otpCooldownId);
|
|
|
|
|
+ state.otpCooldownId = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ otpCooldownMessage.textContent = 'Neuer Code in ' + String(state.otpCooldownRemaining) + 's verfuegbar.';
|
|
|
|
|
+ state.otpCooldownRemaining -= 1;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ update();
|
|
|
|
|
+ state.otpCooldownId = setInterval(update, 1000);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function lockEmail(email) {
|
|
function lockEmail(email) {
|
|
|
state.email = email;
|
|
state.email = email;
|
|
|
|
|
+ state.isVerified = true;
|
|
|
applicationEmail.value = email;
|
|
applicationEmail.value = email;
|
|
|
startEmailInput.value = email;
|
|
startEmailInput.value = email;
|
|
|
startEmailInput.readOnly = true;
|
|
startEmailInput.readOnly = true;
|
|
@@ -249,8 +377,10 @@
|
|
|
enterCompactStatus(email);
|
|
enterCompactStatus(email);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function unlockEmail(clearInput) {
|
|
|
|
|
|
|
+ function unlockEmail(clearInput, forgetEmail) {
|
|
|
|
|
+ const shouldForget = forgetEmail !== false;
|
|
|
state.email = '';
|
|
state.email = '';
|
|
|
|
|
+ state.isVerified = false;
|
|
|
applicationEmail.value = '';
|
|
applicationEmail.value = '';
|
|
|
startEmailInput.readOnly = false;
|
|
startEmailInput.readOnly = false;
|
|
|
startEmailInput.removeAttribute('aria-readonly');
|
|
startEmailInput.removeAttribute('aria-readonly');
|
|
@@ -259,8 +389,12 @@
|
|
|
setStartEmailError('');
|
|
setStartEmailError('');
|
|
|
}
|
|
}
|
|
|
updateStartEmailRequiredMarker();
|
|
updateStartEmailRequiredMarker();
|
|
|
- forgetRememberedEmail();
|
|
|
|
|
|
|
+ if (shouldForget) {
|
|
|
|
|
+ forgetRememberedEmail();
|
|
|
|
|
+ }
|
|
|
leaveCompactStatus();
|
|
leaveCompactStatus();
|
|
|
|
|
+ hideOtpSection();
|
|
|
|
|
+ setDisclaimerError('');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function stopAutosave() {
|
|
function stopAutosave() {
|
|
@@ -270,16 +404,23 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function markUserActivity() {
|
|
|
|
|
+ state.lastUserActivityAt = Math.floor(Date.now() / 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function startAutosave() {
|
|
function startAutosave() {
|
|
|
stopAutosave();
|
|
stopAutosave();
|
|
|
state.autosaveId = setInterval(async () => {
|
|
state.autosaveId = setInterval(async () => {
|
|
|
- if (!state.email || wizardSection.classList.contains('hidden')) {
|
|
|
|
|
|
|
+ if (!state.email || !state.isVerified || wizardSection.classList.contains('hidden')) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
await saveDraft(false);
|
|
await saveDraft(false);
|
|
|
- } catch (_err) {
|
|
|
|
|
- // visible on next manual action
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const payload = (err && err.payload) || {};
|
|
|
|
|
+ if (isAuthFailurePayload(payload)) {
|
|
|
|
|
+ handleProtectedAuthFailure(payload);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}, 15000);
|
|
}, 15000);
|
|
|
}
|
|
}
|
|
@@ -1078,6 +1219,7 @@
|
|
|
params.set('email', state.email);
|
|
params.set('email', state.email);
|
|
|
params.set('field', fieldKey);
|
|
params.set('field', fieldKey);
|
|
|
params.set('index', String(index));
|
|
params.set('index', String(index));
|
|
|
|
|
+ params.set('last_user_activity_at', String(state.lastUserActivityAt));
|
|
|
return appUrl('api/upload-preview.php') + '?' + params.toString();
|
|
return appUrl('api/upload-preview.php') + '?' + params.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1088,6 +1230,7 @@
|
|
|
fd.append('website', '');
|
|
fd.append('website', '');
|
|
|
fd.append('field', fieldKey);
|
|
fd.append('field', fieldKey);
|
|
|
fd.append('index', String(index));
|
|
fd.append('index', String(index));
|
|
|
|
|
+ fd.append('last_user_activity_at', String(state.lastUserActivityAt));
|
|
|
|
|
|
|
|
const response = await postForm(appUrl('api/delete-upload.php'), fd);
|
|
const response = await postForm(appUrl('api/delete-upload.php'), fd);
|
|
|
renderUploadInfo(response.uploads || {});
|
|
renderUploadInfo(response.uploads || {});
|
|
@@ -1133,6 +1276,10 @@
|
|
|
previewLink.target = '_blank';
|
|
previewLink.target = '_blank';
|
|
|
previewLink.rel = 'noopener noreferrer';
|
|
previewLink.rel = 'noopener noreferrer';
|
|
|
previewLink.textContent = 'Vorschau';
|
|
previewLink.textContent = 'Vorschau';
|
|
|
|
|
+ previewLink.addEventListener('click', () => {
|
|
|
|
|
+ markUserActivity();
|
|
|
|
|
+ previewLink.href = buildUploadPreviewUrl(field, index);
|
|
|
|
|
+ });
|
|
|
actions.appendChild(previewLink);
|
|
actions.appendChild(previewLink);
|
|
|
|
|
|
|
|
const deleteBtn = document.createElement('button');
|
|
const deleteBtn = document.createElement('button');
|
|
@@ -1140,6 +1287,7 @@
|
|
|
deleteBtn.className = 'upload-item-btn upload-item-btn-danger';
|
|
deleteBtn.className = 'upload-item-btn upload-item-btn-danger';
|
|
|
deleteBtn.textContent = 'Löschen';
|
|
deleteBtn.textContent = 'Löschen';
|
|
|
deleteBtn.addEventListener('click', async () => {
|
|
deleteBtn.addEventListener('click', async () => {
|
|
|
|
|
+ markUserActivity();
|
|
|
const confirmed = window.confirm('Diesen Upload wirklich löschen?');
|
|
const confirmed = window.confirm('Diesen Upload wirklich löschen?');
|
|
|
if (!confirmed) {
|
|
if (!confirmed) {
|
|
|
return;
|
|
return;
|
|
@@ -1150,8 +1298,7 @@
|
|
|
await deleteUploadedFile(field, index);
|
|
await deleteUploadedFile(field, index);
|
|
|
setFeedback('Upload gelöscht.', false);
|
|
setFeedback('Upload gelöscht.', false);
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const msg = (err.payload && err.payload.message) || err.message || 'Löschen fehlgeschlagen.';
|
|
|
|
|
- setFeedback(msg, true);
|
|
|
|
|
|
|
+ handleProtectedError(err, 'Loeschen fehlgeschlagen.', 'wizard');
|
|
|
} finally {
|
|
} finally {
|
|
|
deleteBtn.disabled = false;
|
|
deleteBtn.disabled = false;
|
|
|
}
|
|
}
|
|
@@ -1201,7 +1348,12 @@
|
|
|
renderSummary();
|
|
renderSummary();
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const msg = (err.payload && err.payload.message) || err.message || 'Upload fehlgeschlagen.';
|
|
|
|
|
|
|
+ const wasAuth = handleProtectedError(err, 'Upload fehlgeschlagen.', 'wizard');
|
|
|
|
|
+ if (wasAuth) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const payload = (err && err.payload) || {};
|
|
|
|
|
+ const msg = payload.message || err.message || 'Upload fehlgeschlagen.';
|
|
|
setDraftStatus('Upload fehlgeschlagen', true);
|
|
setDraftStatus('Upload fehlgeschlagen', true);
|
|
|
setFeedback(msg, true);
|
|
setFeedback(msg, true);
|
|
|
}
|
|
}
|
|
@@ -1255,7 +1407,13 @@
|
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const payload = await response.json();
|
|
|
|
|
|
|
+ let payload = {};
|
|
|
|
|
+ try {
|
|
|
|
|
+ payload = await response.json();
|
|
|
|
|
+ } catch (_err) {
|
|
|
|
|
+ payload = {};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (!response.ok || payload.ok === false) {
|
|
if (!response.ok || payload.ok === false) {
|
|
|
const err = new Error(payload.message || 'Anfrage fehlgeschlagen');
|
|
const err = new Error(payload.message || 'Anfrage fehlgeschlagen');
|
|
|
err.payload = payload;
|
|
err.payload = payload;
|
|
@@ -1265,12 +1423,82 @@
|
|
|
return payload;
|
|
return payload;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function isAuthFailurePayload(payload) {
|
|
|
|
|
+ return Boolean(payload && (payload.auth_required || payload.auth_expired));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function inactivityInfoText() {
|
|
|
|
|
+ const inactivitySeconds = Math.max(60, Number(verificationConfig.inactivity_seconds || 3600));
|
|
|
|
|
+ const inactivityMinutes = Math.round(inactivitySeconds / 60);
|
|
|
|
|
+ if (inactivityMinutes % 60 === 0) {
|
|
|
|
|
+ const hours = inactivityMinutes / 60;
|
|
|
|
|
+ return 'nach ' + String(hours) + ' Stunde' + (hours === 1 ? '' : 'n') + ' Inaktivitaet';
|
|
|
|
|
+ }
|
|
|
|
|
+ return 'nach ' + String(inactivityMinutes) + ' Minuten Inaktivitaet';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleProtectedAuthFailure(payload) {
|
|
|
|
|
+ const email = normalizeEmail(state.email || startEmailInput.value || '');
|
|
|
|
|
+ stopAutosave();
|
|
|
|
|
+ wizardSection.classList.add('hidden');
|
|
|
|
|
+ disclaimerSection.classList.add('hidden');
|
|
|
|
|
+ clearWizardData();
|
|
|
|
|
+ unlockEmail(false, false);
|
|
|
|
|
+ setResetActionVisible(false);
|
|
|
|
|
+
|
|
|
|
|
+ if (email !== '') {
|
|
|
|
|
+ startEmailInput.value = email;
|
|
|
|
|
+ updateStartEmailRequiredMarker();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const isExpired = Boolean(payload && payload.auth_expired);
|
|
|
|
|
+ const defaultMessage = isExpired
|
|
|
|
|
+ ? 'Ihre Sitzung ist ' + inactivityInfoText() + ' abgelaufen. Bitte erneut verifizieren.'
|
|
|
|
|
+ : 'Bitte zuerst E-Mail und Sicherheitscode bestaetigen.';
|
|
|
|
|
+
|
|
|
|
|
+ setFeedback((payload && payload.message) || defaultMessage, true, 'start');
|
|
|
|
|
+ setDraftStatus(isExpired ? 'Sitzung abgelaufen' : 'Verifizierung erforderlich', true);
|
|
|
|
|
+ hideOtpSection();
|
|
|
|
|
+ startEmailInput.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleProtectedError(err, fallbackMessage, scope) {
|
|
|
|
|
+ const payload = err && err.payload ? err.payload : {};
|
|
|
|
|
+ if (isAuthFailurePayload(payload)) {
|
|
|
|
|
+ handleProtectedAuthFailure(payload);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const msg = payload.message || err.message || fallbackMessage;
|
|
|
|
|
+ setFeedback(msg, true, scope || 'wizard');
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function requestOtpCode(email, autoStart) {
|
|
|
|
|
+ const fd = new FormData();
|
|
|
|
|
+ fd.append('csrf', boot.csrf);
|
|
|
|
|
+ fd.append('website', '');
|
|
|
|
|
+ fd.append('email', email);
|
|
|
|
|
+ fd.append('auto_start', autoStart ? '1' : '0');
|
|
|
|
|
+ return postForm(appUrl('api/request-otp.php'), fd);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function verifyOtpCode(email, code) {
|
|
|
|
|
+ const fd = new FormData();
|
|
|
|
|
+ fd.append('csrf', boot.csrf);
|
|
|
|
|
+ fd.append('website', '');
|
|
|
|
|
+ fd.append('email', email);
|
|
|
|
|
+ fd.append('otp_code', code);
|
|
|
|
|
+ return postForm(appUrl('api/verify-otp.php'), fd);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function collectPayload(includeFiles) {
|
|
function collectPayload(includeFiles) {
|
|
|
const fd = new FormData();
|
|
const fd = new FormData();
|
|
|
fd.append('csrf', boot.csrf);
|
|
fd.append('csrf', boot.csrf);
|
|
|
fd.append('email', state.email);
|
|
fd.append('email', state.email);
|
|
|
fd.append('step', String(Math.min(state.currentStep, state.totalSteps)));
|
|
fd.append('step', String(Math.min(state.currentStep, state.totalSteps)));
|
|
|
fd.append('website', '');
|
|
fd.append('website', '');
|
|
|
|
|
+ fd.append('last_user_activity_at', String(state.lastUserActivityAt));
|
|
|
|
|
|
|
|
Array.from(applicationForm.elements).forEach((el) => {
|
|
Array.from(applicationForm.elements).forEach((el) => {
|
|
|
if (!el.name) {
|
|
if (!el.name) {
|
|
@@ -1309,6 +1537,7 @@
|
|
|
fd.append('csrf', boot.csrf);
|
|
fd.append('csrf', boot.csrf);
|
|
|
fd.append('email', email);
|
|
fd.append('email', email);
|
|
|
fd.append('website', '');
|
|
fd.append('website', '');
|
|
|
|
|
+ fd.append('last_user_activity_at', String(state.lastUserActivityAt));
|
|
|
return postForm(appUrl('api/load-draft.php'), fd);
|
|
return postForm(appUrl('api/load-draft.php'), fd);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1317,6 +1546,7 @@
|
|
|
fd.append('csrf', boot.csrf);
|
|
fd.append('csrf', boot.csrf);
|
|
|
fd.append('email', email);
|
|
fd.append('email', email);
|
|
|
fd.append('website', '');
|
|
fd.append('website', '');
|
|
|
|
|
+ fd.append('last_user_activity_at', String(state.lastUserActivityAt));
|
|
|
return postForm(appUrl('api/reset.php'), fd);
|
|
return postForm(appUrl('api/reset.php'), fd);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1412,12 +1642,29 @@
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async function startProcess(rawEmail) {
|
|
|
|
|
|
|
+ function validateOtpCode(showError) {
|
|
|
|
|
+ const code = normalizeOtpCode(startOtpInput.value || '');
|
|
|
|
|
+ startOtpInput.value = code;
|
|
|
|
|
+ if (/^\d{6}$/.test(code)) {
|
|
|
|
|
+ setStartOtpError('');
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (showError) {
|
|
|
|
|
+ setStartOtpError('Bitte einen 6-stelligen Code eingeben.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function requestOtpFlow(rawEmail, options) {
|
|
|
|
|
+ const opts = options || {};
|
|
|
|
|
+ const autoStart = Boolean(opts.autoStart);
|
|
|
const email = normalizeEmail(rawEmail);
|
|
const email = normalizeEmail(rawEmail);
|
|
|
if (!isValidEmail(email)) {
|
|
if (!isValidEmail(email)) {
|
|
|
const message = 'Bitte eine gültige E-Mail-Adresse eingeben.';
|
|
const message = 'Bitte eine gültige E-Mail-Adresse eingeben.';
|
|
|
setStartEmailError(message);
|
|
setStartEmailError(message);
|
|
|
- setFeedback(message, true);
|
|
|
|
|
|
|
+ setFeedback(message, true, 'start');
|
|
|
startEmailInput.focus();
|
|
startEmailInput.focus();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -1430,14 +1677,109 @@
|
|
|
if (startSubmitBtn) {
|
|
if (startSubmitBtn) {
|
|
|
startSubmitBtn.disabled = true;
|
|
startSubmitBtn.disabled = true;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (resendOtpBtn) {
|
|
|
|
|
+ resendOtpBtn.disabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await requestOtpCode(email, autoStart);
|
|
|
|
|
+ if (autoStart) {
|
|
|
|
|
+ markAutoOtpSessionFlag(email);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (result.auto_skipped) {
|
|
|
|
|
+ showOtpSection(email, 'Bitte Sicherheitscode eingeben oder einen neuen Code anfordern.');
|
|
|
|
|
+ setFeedback('', false, 'start');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showOtpSection(email, result.message || ('Sicherheitscode wurde an ' + email + ' gesendet.'));
|
|
|
|
|
+ setFeedback(result.message || 'Sicherheitscode wurde versendet.', false, 'start');
|
|
|
|
|
+ setOtpCooldown(Number(result.resend_available_in || 0));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const payload = (err && err.payload) || {};
|
|
|
|
|
+ const retryAfter = Number(payload.retry_after || 0);
|
|
|
|
|
+ if (retryAfter > 0) {
|
|
|
|
|
+ setOtpCooldown(retryAfter);
|
|
|
|
|
+ }
|
|
|
|
|
+ setFeedback(payload.message || err.message || 'Code konnte nicht gesendet werden.', true, 'start');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (startSubmitBtn) {
|
|
|
|
|
+ startSubmitBtn.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (resendOtpBtn && state.otpCooldownRemaining <= 0) {
|
|
|
|
|
+ resendOtpBtn.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function verifyOtpFlow() {
|
|
|
|
|
+ const email = normalizeEmail(startEmailInput.value || '');
|
|
|
|
|
+ if (!isValidEmail(email)) {
|
|
|
|
|
+ setStartEmailError('Bitte eine gültige E-Mail-Adresse eingeben.');
|
|
|
|
|
+ startEmailInput.focus();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!validateOtpCode(true)) {
|
|
|
|
|
+ startOtpInput.focus();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (verifyOtpBtn) {
|
|
|
|
|
+ verifyOtpBtn.disabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (resendOtpBtn) {
|
|
|
|
|
+ resendOtpBtn.disabled = true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- const result = await loadDraft(email);
|
|
|
|
|
|
|
+ const result = await verifyOtpCode(email, normalizeOtpCode(startOtpInput.value || ''));
|
|
|
lockEmail(email);
|
|
lockEmail(email);
|
|
|
setResetActionVisible(true);
|
|
setResetActionVisible(true);
|
|
|
|
|
+ hideOtpSection();
|
|
|
|
|
+ setFeedback(result.message || 'E-Mail erfolgreich bestaetigt.', false, 'start');
|
|
|
|
|
+ markUserActivity();
|
|
|
|
|
+ disclaimerSection.classList.remove('hidden');
|
|
|
|
|
+ wizardSection.classList.add('hidden');
|
|
|
|
|
+ if (disclaimerReadCheckbox) {
|
|
|
|
|
+ disclaimerReadCheckbox.checked = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ updateDisclaimerAcceptanceState();
|
|
|
|
|
+ disclaimerSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const payload = (err && err.payload) || {};
|
|
|
|
|
+ const attemptsLeft = Number(payload.attempts_left);
|
|
|
|
|
+ if (Number.isFinite(attemptsLeft) && attemptsLeft >= 0 && attemptsLeft < 5) {
|
|
|
|
|
+ setStartOtpError((payload.message || 'Code ungueltig.') + ' Verbleibende Versuche: ' + String(attemptsLeft));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setStartOtpError(payload.message || err.message || 'Code konnte nicht bestaetigt werden.');
|
|
|
|
|
+ }
|
|
|
|
|
+ setFeedback(payload.message || err.message || 'Code konnte nicht bestaetigt werden.', true, 'start');
|
|
|
|
|
+ startOtpInput.focus();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (verifyOtpBtn) {
|
|
|
|
|
+ verifyOtpBtn.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (resendOtpBtn && state.otpCooldownRemaining <= 0) {
|
|
|
|
|
+ resendOtpBtn.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function openWizardForVerifiedEmail() {
|
|
|
|
|
+ if (!state.email || !state.isVerified) {
|
|
|
|
|
+ setFeedback('Bitte zuerst E-Mail und Sicherheitscode bestaetigen.', true, 'start');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ markUserActivity();
|
|
|
|
|
+ const result = await loadDraft(state.email);
|
|
|
|
|
+ setResetActionVisible(true);
|
|
|
|
|
|
|
|
if (result.already_submitted) {
|
|
if (result.already_submitted) {
|
|
|
wizardSection.classList.add('hidden');
|
|
wizardSection.classList.add('hidden');
|
|
|
|
|
+ disclaimerSection.classList.add('hidden');
|
|
|
setDraftStatus('Antrag bereits abgeschlossen', false);
|
|
setDraftStatus('Antrag bereits abgeschlossen', false);
|
|
|
setFeedback(
|
|
setFeedback(
|
|
|
result.message || 'Für diese E-Mail liegt bereits ein abgeschlossener Antrag vor.',
|
|
result.message || 'Für diese E-Mail liegt bereits ein abgeschlossener Antrag vor.',
|
|
@@ -1449,6 +1791,7 @@
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ disclaimerSection.classList.add('hidden');
|
|
|
wizardSection.classList.remove('hidden');
|
|
wizardSection.classList.remove('hidden');
|
|
|
fillFormData(result.data || {});
|
|
fillFormData(result.data || {});
|
|
|
renderUploadInfo(result.uploads || {});
|
|
renderUploadInfo(result.uploads || {});
|
|
@@ -1464,24 +1807,36 @@
|
|
|
} else {
|
|
} else {
|
|
|
setDraftStatus('Neuer Entwurf gestartet', false);
|
|
setDraftStatus('Neuer Entwurf gestartet', false);
|
|
|
}
|
|
}
|
|
|
- setFeedback('', false);
|
|
|
|
|
|
|
+ setFeedback('', false, 'wizard');
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const msg = (err.payload && err.payload.message) || err.message || 'Laden fehlgeschlagen.';
|
|
|
|
|
- setFeedback(msg, true);
|
|
|
|
|
- } finally {
|
|
|
|
|
- if (startSubmitBtn) {
|
|
|
|
|
- startSubmitBtn.disabled = false;
|
|
|
|
|
|
|
+ const handled = handleProtectedError(err, 'Laden fehlgeschlagen.', 'start');
|
|
|
|
|
+ if (!handled) {
|
|
|
|
|
+ // keep default message already set
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function initAutoOtpForRememberedEmail() {
|
|
|
|
|
+ const rememberedEmail = normalizeEmail(getRememberedEmail());
|
|
|
|
|
+ if (rememberedEmail === '' || !isValidEmail(rememberedEmail)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ startEmailInput.value = rememberedEmail;
|
|
|
|
|
+ updateStartEmailRequiredMarker();
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasAutoOtpSessionFlag(rememberedEmail)) {
|
|
|
|
|
+ requestOtpFlow(rememberedEmail, { autoStart: true });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
startForm.addEventListener('submit', async (event) => {
|
|
startForm.addEventListener('submit', async (event) => {
|
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
|
if (!validateStartEmail(true)) {
|
|
if (!validateStartEmail(true)) {
|
|
|
startEmailInput.focus();
|
|
startEmailInput.focus();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- await startProcess(startEmailInput.value || '');
|
|
|
|
|
|
|
+ await requestOtpFlow(startEmailInput.value || '', { autoStart: false });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
startEmailInput.addEventListener('input', () => {
|
|
startEmailInput.addEventListener('input', () => {
|
|
@@ -1494,6 +1849,9 @@
|
|
|
updateStartEmailRequiredMarker();
|
|
updateStartEmailRequiredMarker();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (state.otpEmail !== '' && state.otpEmail !== normalizeEmail(startEmailInput.value || '')) {
|
|
|
|
|
+ hideOtpSection();
|
|
|
|
|
+ }
|
|
|
validateStartEmail(false);
|
|
validateStartEmail(false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -1520,16 +1878,17 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
|
|
+ markUserActivity();
|
|
|
await resetSavedData(state.email);
|
|
await resetSavedData(state.email);
|
|
|
stopAutosave();
|
|
stopAutosave();
|
|
|
wizardSection.classList.add('hidden');
|
|
wizardSection.classList.add('hidden');
|
|
|
|
|
+ disclaimerSection.classList.add('hidden');
|
|
|
clearWizardData();
|
|
clearWizardData();
|
|
|
unlockEmail(true);
|
|
unlockEmail(true);
|
|
|
setFeedback('Alle gespeicherten Daten wurden gelöscht. Sie können neu starten.', false);
|
|
setFeedback('Alle gespeicherten Daten wurden gelöscht. Sie können neu starten.', false);
|
|
|
startEmailInput.focus();
|
|
startEmailInput.focus();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const msg = (err.payload && err.payload.message) || err.message || 'Löschen fehlgeschlagen.';
|
|
|
|
|
- setFeedback(msg, true);
|
|
|
|
|
|
|
+ handleProtectedError(err, 'Loeschen fehlgeschlagen.', 'start');
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -1539,6 +1898,7 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
|
|
+ markUserActivity();
|
|
|
if (state.currentStep <= state.totalSteps) {
|
|
if (state.currentStep <= state.totalSteps) {
|
|
|
await saveDraft(false);
|
|
await saveDraft(false);
|
|
|
}
|
|
}
|
|
@@ -1546,8 +1906,7 @@
|
|
|
updateProgress();
|
|
updateProgress();
|
|
|
scrollWizardToTop();
|
|
scrollWizardToTop();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const msg = (err.payload && err.payload.message) || err.message;
|
|
|
|
|
- setFeedback(msg, true);
|
|
|
|
|
|
|
+ handleProtectedError(err, 'Speichern fehlgeschlagen.', 'wizard');
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -1557,13 +1916,13 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
|
|
+ markUserActivity();
|
|
|
await saveDraft(false);
|
|
await saveDraft(false);
|
|
|
state.currentStep += 1;
|
|
state.currentStep += 1;
|
|
|
updateProgress();
|
|
updateProgress();
|
|
|
scrollWizardToTop();
|
|
scrollWizardToTop();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const msg = (err.payload && err.payload.message) || err.message;
|
|
|
|
|
- setFeedback(msg, true);
|
|
|
|
|
|
|
+ handleProtectedError(err, 'Speichern fehlgeschlagen.', 'wizard');
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -1579,9 +1938,14 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
|
|
+ markUserActivity();
|
|
|
await submitApplication();
|
|
await submitApplication();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
const payload = err.payload || {};
|
|
const payload = err.payload || {};
|
|
|
|
|
+ if (isAuthFailurePayload(payload)) {
|
|
|
|
|
+ handleProtectedAuthFailure(payload);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (payload.errors) {
|
|
if (payload.errors) {
|
|
|
showErrors(payload.errors);
|
|
showErrors(payload.errors);
|
|
|
}
|
|
}
|
|
@@ -1595,36 +1959,62 @@
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- function initializeAfterDisclaimer() {
|
|
|
|
|
- disclaimerSection.classList.add('hidden');
|
|
|
|
|
- startSection.classList.remove('hidden');
|
|
|
|
|
-
|
|
|
|
|
- const rememberedEmail = normalizeEmail(getRememberedEmail());
|
|
|
|
|
- if (rememberedEmail !== '' && isValidEmail(rememberedEmail)) {
|
|
|
|
|
- startEmailInput.value = rememberedEmail;
|
|
|
|
|
- setFeedback('', false);
|
|
|
|
|
- startProcess(rememberedEmail);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
if (disclaimerReadCheckbox) {
|
|
if (disclaimerReadCheckbox) {
|
|
|
disclaimerReadCheckbox.addEventListener('change', updateDisclaimerAcceptanceState);
|
|
disclaimerReadCheckbox.addEventListener('change', updateDisclaimerAcceptanceState);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (acceptDisclaimerBtn) {
|
|
if (acceptDisclaimerBtn) {
|
|
|
- acceptDisclaimerBtn.addEventListener('click', () => {
|
|
|
|
|
|
|
+ acceptDisclaimerBtn.addEventListener('click', async () => {
|
|
|
if (!disclaimerReadCheckbox || !disclaimerReadCheckbox.checked) {
|
|
if (!disclaimerReadCheckbox || !disclaimerReadCheckbox.checked) {
|
|
|
setDisclaimerError('Bitte lesen und bestätigen Sie den Hinweis.');
|
|
setDisclaimerError('Bitte lesen und bestätigen Sie den Hinweis.');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
setDisclaimerError('');
|
|
setDisclaimerError('');
|
|
|
- initializeAfterDisclaimer();
|
|
|
|
|
|
|
+ markUserActivity();
|
|
|
|
|
+ await openWizardForVerifiedEmail();
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (verifyOtpBtn) {
|
|
|
|
|
+ verifyOtpBtn.addEventListener('click', async () => {
|
|
|
|
|
+ await verifyOtpFlow();
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (resendOtpBtn) {
|
|
|
|
|
+ resendOtpBtn.addEventListener('click', async () => {
|
|
|
|
|
+ await requestOtpFlow(startEmailInput.value || '', { autoStart: false });
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (startOtpInput) {
|
|
|
|
|
+ startOtpInput.addEventListener('input', () => {
|
|
|
|
|
+ startOtpInput.value = normalizeOtpCode(startOtpInput.value || '');
|
|
|
|
|
+ setStartOtpError('');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ startOtpInput.addEventListener('keydown', async (event) => {
|
|
|
|
|
+ if (event.key !== 'Enter') {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ await verifyOtpFlow();
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ['input', 'change', 'click', 'keydown'].forEach((eventName) => {
|
|
|
|
|
+ applicationForm.addEventListener(eventName, () => {
|
|
|
|
|
+ if (wizardSection.classList.contains('hidden')) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ markUserActivity();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
updateDisclaimerAcceptanceState();
|
|
updateDisclaimerAcceptanceState();
|
|
|
- disclaimerSection.classList.remove('hidden');
|
|
|
|
|
- startSection.classList.add('hidden');
|
|
|
|
|
|
|
+ disclaimerSection.classList.add('hidden');
|
|
|
|
|
+ startSection.classList.remove('hidden');
|
|
|
|
|
+ wizardSection.classList.add('hidden');
|
|
|
|
|
|
|
|
initTableFields();
|
|
initTableFields();
|
|
|
initUploadControls();
|
|
initUploadControls();
|
|
@@ -1633,4 +2023,5 @@
|
|
|
refreshRequiredMarkers();
|
|
refreshRequiredMarkers();
|
|
|
updateStartEmailRequiredMarker();
|
|
updateStartEmailRequiredMarker();
|
|
|
updateProgress();
|
|
updateProgress();
|
|
|
|
|
+ initAutoOtpForRememberedEmail();
|
|
|
})();
|
|
})();
|