diff --git a/backend/routes/clickTtHttpPageRoutes.js b/backend/routes/clickTtHttpPageRoutes.js index 73e84be1..f2b88f91 100644 --- a/backend/routes/clickTtHttpPageRoutes.js +++ b/backend/routes/clickTtHttpPageRoutes.js @@ -368,13 +368,40 @@ function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) { "console.log('[ClickTT Proxy] inline confirm elements json',JSON.stringify(elements));", '}catch(e){}', '}', + 'function bindInlineConfirmElements(){', + 'try{', + "var elements=[].slice.call(document.querySelectorAll('[onclick*=\"confirm(\"]'));", + 'elements.forEach(function(el,index){', + "if(el.__clickttConfirmBound)return;", + 'el.__clickttConfirmBound=true;', + "el.addEventListener('click',function(event){", + "try{console.log('[ClickTT Proxy] direct confirm element click',{index:index,tag:el.tagName,name:el.getAttribute('name'),value:el.getAttribute('value'),href:el.getAttribute('href'),onclick:el.getAttribute('onclick'),text:(el.textContent||'').trim().slice(0,120)});}catch(e){}", + 'var allowed=shouldAllowInlineConfirm(el);', + 'if(!allowed){', + 'event.preventDefault();', + 'event.stopPropagation();', + 'return false;', + '}', + "var href=el.getAttribute&&el.getAttribute('href');", + 'var targetUrl=normalizeUrl(href,PAGE_BASE_URL);', + 'if(targetUrl&&shouldProxyUrl(targetUrl)){', + 'event.preventDefault();', + 'event.stopPropagation();', + 'navigateViaProxy(targetUrl);', + 'return false;', + '}', + 'return true;', + '},true);', + '});', + '}catch(e){}', + '}', 'function getSubmitTarget(form, submitter){', "var action=submitter&&submitter.getAttribute?submitter.getAttribute('formaction'):null;", 'if(action)return normalizeUrl(action,PAGE_BASE_URL);', "return normalizeUrl((form&&form.getAttribute?form.getAttribute('action'):null)||PAGE_BASE_URL,PAGE_BASE_URL);", '}', 'var lastSubmitter=null;', - 'if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",logInlineConfirmElements,{once:true});}else{logInlineConfirmElements();}', + 'if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",function(){logInlineConfirmElements();bindInlineConfirmElements();},{once:true});}else{logInlineConfirmElements();bindInlineConfirmElements();}', "document.addEventListener('click',function(event){", "var anchor=event.target&&event.target.closest?event.target.closest('a[href]'):null;", "var submitControl=event.target&&event.target.closest?event.target.closest('button, input[type=\"submit\"], input[type=\"image\"]'):null;", diff --git a/backend/services/clickTtPlayerRegistrationService.js b/backend/services/clickTtPlayerRegistrationService.js index 051e573b..402a0ec2 100644 --- a/backend/services/clickTtPlayerRegistrationService.js +++ b/backend/services/clickTtPlayerRegistrationService.js @@ -86,7 +86,7 @@ class ClickTtPlayerRegistrationService { await this._clickByText(page, 'Personen suchen', trace); await page.waitForLoadState('domcontentloaded'); - await this._abortIfConfirmFlow(page); + await this._openApplicationAfterSearch(page, trace); await this._fillApplicationForm(page, memberJson, primaryEmail); await this._clickByText(page, 'Weiter >>', trace); await page.waitForLoadState('domcontentloaded'); @@ -288,11 +288,41 @@ class ClickTtPlayerRegistrationService { await fillIfEmpty(13, email); } - async _abortIfConfirmFlow(page) { + async _openApplicationAfterSearch(page, trace) { + const explicitApplicationLink = page.getByText(/Spielberechtigung beantragen/i).first(); + if (await explicitApplicationLink.count()) { + let dialogSeen = false; + page.once('dialog', async (dialog) => { + dialogSeen = true; + this._trace(trace, 'dialog', { + kind: dialog.type(), + message: sanitizePageText(dialog.message()) + }); + await dialog.accept(); + }); + + this._trace(trace, 'step', { + name: 'click', + label: 'Spielberechtigung beantragen', + selector: 'text=/Spielberechtigung beantragen/i' + }); + await explicitApplicationLink.click(); + await page.waitForLoadState('domcontentloaded'); + + this._trace(trace, 'step', { + name: 'search-result-application-opened', + dialogSeen, + url: page.url() + }); + return; + } + const confirmLocator = page.locator('[onclick*="confirm("]').first(); if (await confirmLocator.count()) { - const text = sanitizePageText(await confirmLocator.innerText().catch(() => '') || await confirmLocator.getAttribute('value').catch(() => '') || ''); - throw new HttpError(`Der Antrag befindet sich im noch nicht automatisierten click-TT-Confirm-/Neuanlage-Flow${text ? `: ${text}` : ''}`, 409); + const text = sanitizePageText( + await confirmLocator.innerText().catch(() => '') || await confirmLocator.getAttribute('value').catch(() => '') || '' + ); + throw new HttpError(`Click-TT-Suchergebnis mit Confirm gefunden, aber kein klickbares Antragselement per Label erkannt${text ? `: ${text}` : ''}`, 409); } } diff --git a/frontend/src/views/MembersView.vue b/frontend/src/views/MembersView.vue index 74749779..5cad259e 100644 --- a/frontend/src/views/MembersView.vue +++ b/frontend/src/views/MembersView.vue @@ -256,7 +256,7 @@ ⛔ id !== member.id); } }, + formatClickTtTrace(trace) { + if (!Array.isArray(trace) || trace.length === 0) { + return ''; + } + + return trace + .slice(-12) + .map((entry) => { + const type = entry?.type || 'trace'; + const parts = [type]; + + if (entry?.name) parts.push(entry.name); + if (entry?.label) parts.push(`label=${entry.label}`); + if (entry?.status) parts.push(`status=${entry.status}`); + if (entry?.url) parts.push(`url=${entry.url}`); + if (entry?.message) parts.push(`message=${entry.message}`); + + return parts.join(' | '); + }) + .join('\n'); + }, toggleNewMember() { this.memberFormIsOpen = !this.memberFormIsOpen; },