15 KiB
Admin-Panel ins Ticketsystem – Etappe 4 (Abschalten) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: project.webklar.com ist nur noch Kundenoberfläche: Admin-Panel, Rechnungsanalyse, /api/portal-admin, Projekt-Admin-Wechsel und der Mitarbeiter-Login im Portal entfallen; Team-Mails verlinken ins Ticketsystem.
Architecture: Reiner Rückbau plus drei kleine Umstellungen: Login lehnt Mitarbeiter mit Hinweis ab, /me beendet alte Mitarbeiter- und Alt-Ansichts-Sitzungen mit Weiterleitung ins WOMS, Mail-Vorlagen zeigen auf WOMS-Seiten. Alles, was das WOMS über /api/team nutzt (Router, Chat-Baustein, Kundenansicht, Preview-Links), bleibt.
Tech Stack: Portal Express 4 (ESM, Node 20), node:test + test/support/*, Playwright-UI-Checks. WOMS React 18.
Spec: docs/superpowers/specs/2026-10-01-admin-panel-ins-ticketsystem-design.md (Abschnitt 4 „Was in Etappe 4 wegfällt“, Etappe 4 in Abschnitt 5; E1, E4, E10, E11)
Global Constraints
- Arbeitsorte wie Etappe 3 (Portal Stand
40f0a04, WOMSfeda436); vorhergit fetch+git merge --ff-only. - Livegang-Reihenfolge: WOMS zuerst (Knopf „Projekt-Admin“ ruft
/api/admin/switch-link), dann Portal. Nutzer pusht. - Nie
git add -A(Löschungen mitgit rm <datei>), nichts pushen.command grep -a. - Bleibt:
public/chat-composer.js/.css(WOMS lädt sie),GET /api/auth/staff-previewund/kundenansicht*,isAdminSessionin/preview-access,services/staffMfaAdmin.js+scripts/mitarbeiter-2fa.mjs(WOMS-2FA),services/customerActivity.js,services/invoiceNinja.js,services/staffAccount.js,middleware/appwriteStaff.js, alle Router unter/api/team. - Fester Text Login-Sperre:
Mitarbeiter arbeiten im Ticketsystem.+ Link „Zum Ticketsystem“ aufconfig.womsBaseUrl. - Mail-Texte und Design bleiben; nur Links, Linktext „Admin-Bereich“ → „Ticketsystem“ und Sätze, die den Admin-Bereich nennen.
Review Focus
- Mitarbeiter mit alter Portal-Sitzung (Cookie bis 7 Tage): öffnet
/dashboard.htmloder/login.html. Erwartung: Weiterleitung ins Ticketsystem bzw. normale Login-Seite, keine Fehlerseite, kein Zugriff auf Kundendaten (Task 1). - Alte „Als Kunde ansehen“-Sitzung ohne Kennung: Erwartung: wie abgelaufen behandelt (Cookie weg, Hinweis), keine Lese- oder Schreibzugriffe mehr (Task 2).
- Mitarbeiter mit 2FA meldet sich im Portal an: Erwartung: Hinweis „Mitarbeiter arbeiten im Ticketsystem.“ ohne Code-Mail (Task 1).
- Team-Mail-Links: jeder Link in T01–T18 öffnet eine bestehende WOMS-Seite; kein
admin.htmlmehr (Task 3). - Kunden-Login und Kundenansicht unverändert (Task 1/2: bestehende Tests grün, UI-Check Kundenansicht).
Portal
Task 1: Mitarbeiter-Login sperren, alte Mitarbeiter-Sitzungen beenden
Files:
- Modify:
server/routes/auth.js(POST /login:163-250: Mitarbeiter → Appwrite-Sitzung best effort beenden,403 { error: 'Mitarbeiter arbeiten im Ticketsystem.', ticketUrl: config.womsBaseUrl }; entfernen:/login/mfa,/login/mfa/resend,startStaffPortalSession,mfaCookieOptions,MFA_GONE,GET /staff-switch, ImportstaffMfa;GET /me:445-475: Rolle admin ohne Kundenansicht-Kennung → Portal- und Preview-Cookie löschen,{ authenticated: false, reason: 'staff', redirect: config.womsBaseUrl }) - Delete:
server/services/staffMfa.js,test/staffMfa.test.js - Modify:
server/services/appwriteClient.js(sessionNeedsSecondFactorentfernen;appwriteSessionFetchbleibt fürDELETE /account/sessions/current) - Modify:
public/login.html(MFA-Formular :75-99 raus; unter#login-errorein verstecktes<a id="login-staff-link">Zum Ticketsystem</a>),public/login.css(MFA-Stile :658-671 raus),public/app.js(showMfaStep,adminLoginTarget,mfaRequired-Zweig raus; Login: bei 403 mitticketUrlText + Link zeigen; schon angemeldet:me.reason === 'staff'bleibt auf der Login-Seite; Dashboard:me.reason === 'staff'→location.href = me.redirect;api()reichtticketUrlam Fehler weiter) - Modify:
test/support/appwriteMock.js(DELETE /v1/account/sessions/currentmit Sitzungs-Cookie → 204, gezählt insessionDeletes()) - Test:
test/staffLogin.test.js(umschreiben),test/kundenansichtEnde.test.js(Alt-Test/memitviewAsCookie→ neues Verhalten)
Interfaces:
-
Produces:
POST /api/auth/loginfür Mitarbeiter (employees-Eintrag oder Label admin, mit oder ohne 2FA) → 403{ error, ticketUrl }, keine Code-Mail, keine Portal-Sitzung. -
Produces:
GET /api/auth/mefür Sitzungenrole: 'admin'ohneviewId(reine Mitarbeiter-Sitzung und Alt-Ansicht) →{ authenticated: false, reason: 'staff', redirect }+ Cookies gelöscht. -
Step 1: Failing tests
test('Kunde meldet sich weiter normal an', async () => { /* bestehender Test :31-36 bleibt */ })
test('Mitarbeiter ohne 2FA: 403 mit Link ins Ticketsystem, keine Sitzung, Appwrite-Sitzung beendet', async () => {
const res = await post('/api/auth/login', { email: 'andrej@webklar.com', password: 'richtig' })
assert.equal(res.status, 403)
assert.deepEqual(res.body, { error: 'Mitarbeiter arbeiten im Ticketsystem.', ticketUrl: 'https://ticket.webklar.com' })
assert.equal(setCookies(res).filter((c) => c.startsWith('webklar_portal_session=') && !/Expires=Thu, 01 Jan 1970/.test(c)).length, 0)
assert.equal(aw.sessionDeletes(), 1)
})
test('Mitarbeiter mit 2FA: dieselbe Antwort, keine Code-Mail', async () => { /* aw.addUser mfa: true; mailsSent(userId) === 0 */ })
test('/me mit alter Mitarbeiter-Sitzung: reason staff, Weiterleitung, Cookies geloescht', async () => {
const res = await rawGet('/api/auth/me', { cookie: sessionCookie({ role: 'admin', appwriteUserId: 'u_admin', name: 'Kenso', email: 'kenso@webklar.com' }) })
assert.deepEqual(res.body, { authenticated: false, reason: 'staff', redirect: 'https://ticket.webklar.com' })
})
test('/me mit Alt-Ansicht (viewAs ohne viewId): ebenso reason staff', async () => {})
test('/login/mfa und /staff-switch gibt es nicht mehr (404)', async () => {})
- Step 2: Run
node --test test/staffLogin.test.js test/kundenansichtEnde.test.js→ FAIL - Step 3: Implementieren
- Step 4: UI-Check
test/ui/kundenansicht.ui.mjs: Szenario „alt“ ersetzen durch „staff“ (/me→reason: 'staff') mit Erwartung Weiterleitung aufredirect; neuer Check Login-Seite: 403 mitticketUrlzeigt Text und sichtbaren Link „Zum Ticketsystem“ mithref=ticketUrl. Run → alle ok - Step 5: Run
npm test→ PASS - Step 6: Commit — „Portal: Mitarbeiter-Login gesperrt (Hinweis aufs Ticketsystem), alte Mitarbeiter-Sitzungen enden“
Task 2: Admin-Panel, Rechnungsanalyse und alte Admin-Schnittstellen entfernen
Files:
- Delete (
git rm):public/admin.html,public/admin.js,public/kalkulation.html,public/kalkulation.js,public/kalkulation.css,server/routes/portalAdmin.js,server/routes/admin/switchLink.js,server/services/appointments.js,test/staffSwitch.test.js,test/ui/admin-funktionen.ui.mjs - Create:
server/services/staffPreviewLink.js(buildStaffPreviewLinksamtsafePreviewUrl/previewUrlForSubdomainausserver/routes/admin/previewOpen.jsverschoben); Deleteserver/routes/admin/previewOpen.js; Modifyserver/routes/team/preview.js(Import) - Modify:
server/index.js(Imports/Mounts/api/portal-admin,/api/admin/preview-open,/api/admin/switch-linkraus),server/middleware/session.js(requireAdminSessionraus),server/services/staffPreviewTokens.js(Doku ohneswitch),server/middleware/kundenansichtGuard.js(LEGACY_ALLOWEDund Präfix/api/portal-admin/raus; Sitzung mitviewAsohneviewId→ Cookies löschen, 401view_expiredwie abgelaufene Kundenansicht),public/app.js(Alt-Zweig inrenderViewAsBannerraus) - Modify Kommentare/Texte, die das Admin-Panel nennen (Liste: Bestandsaufnahme Abschnitt 6): Server-Meldungen „Im "Als Kunde ansehen"-Modus …“ → „In der Kundenansicht …“; Kommentare auf „Ticketsystem“ bzw. „früheres Admin-Panel“.
- Modify Tests:
test/storageTasks.test.js(auf/api/team/storage-tasksmitBearer jwt-staff),test/funktionenRoutes.test.js(Admin-Teil auf/api/team/funktionen),test/teamChat.test.js(Admin-Panel-Test raus),test/kundenansichtGuard.test.js(Alt-Ansicht: jetzt 401view_expired; Untergrenze geprüfter Schreib-Routen>= 18),test/newsRoutes.test.js(Alt-Ansicht → 401),test/support/fixtures.js(adminCookie/viewAsCookienur noch, wo Tests sie als „alte Sitzung“ brauchen; sonstkundenansichtCookie),test/ui/kundenansicht.ui.mjs(Alt-Banner-Szenario bereits in Task 1 ersetzt)
Interfaces:
-
Consumes: Task 1.
-
Produces:
buildStaffPreviewLink({ userId, email, name, subdomain, previewUrl })unverändert, neuer Ortserver/services/staffPreviewLink.js. -
Produces: 404 für
/admin.html,/kalkulation.html,/api/portal-admin/*,/api/admin/switch-link,/api/admin/preview-open. -
Step 1: Failing test
test/adminPanelGone.test.js
test('Admin-Panel und alte Admin-Schnittstellen sind weg', async () => {
// App wie server/index.js aufbauen ist zu gross: stattdessen Dateien und Router-Mounts pruefen
for (const f of ['public/admin.html', 'public/admin.js', 'public/kalkulation.html', 'public/kalkulation.js', 'public/kalkulation.css', 'server/routes/portalAdmin.js', 'server/routes/admin/switchLink.js', 'server/routes/admin/previewOpen.js']) {
assert.equal(existsSync(new URL(`../${f}`, import.meta.url)), false, f)
}
const index = readFileSync(new URL('../server/index.js', import.meta.url), 'utf8')
for (const mount of ['/api/portal-admin', '/api/admin/switch-link', '/api/admin/preview-open']) assert.ok(!index.includes(`'${mount}'`), mount)
for (const f of ['public/chat-composer.js', 'public/chat-composer.css', 'public/kundenansicht.js']) assert.ok(existsSync(new URL(`../${f}`, import.meta.url)), `${f} muss bleiben`)
})
test('keine Verweise mehr auf admin.html, kalkulation.html oder /api/portal-admin in server/ und public/', () => {
// rekursiv lesen (node:fs), Fundstellen auflisten -> []
})
test('Alt-Ansicht ohne Kennung: Lesen und Schreiben 401 view_expired, Cookies geloescht', async () => {})
- Step 2: Run → FAIL
- Step 3: Löschen, verschieben, umstellen; Tests anpassen
- Step 4: Run
npm test→ PASS;node test/ui/kundenansicht.ui.mjsundnode test/ui/composer-headers.ui.mjs→ alle ok - Step 5: Commit — „Portal: Admin-Panel, Rechnungsanalyse und alte Admin-Schnittstellen entfernt“
Task 3: Team-Mails verlinken ins Ticketsystem
Files:
- Modify: 15 Vorlagen mit Fußzeilen-Link (
server/mail-templates/T01,T02,T03,T04,T05,T06,T07,T08,T09,T10,T11,T12,T13,T14,T16 *.html):href="https://project.webklar.com/admin.html"→https://ticket.webklar.com/customers, Linktext „Admin-Bereich“ → „Ticketsystem“ - Modify:
T01 Neue Chatnachricht.html(Knopf „Im Ticketsystem antworten →“ auf{{team_link}}, Platzhalter im Kopfkommentar ergänzen, Satz :39 anpassen),T10/T11(Knopftexte bleiben,{{admin_link}}bleibt als Name), Sätze mit „Admin-Bereich“ inT09:64,T13:41,45,53→ „Ticketsystem (Kunden → …)“ - Modify:
server/services/mailer.js(sendChatNotification:team_link: ${config.womsBaseUrl}/customers/chat?kunde=<customer.$id>),server/services/takeoverCredits.js:460,:489(admin_link: ${config.womsBaseUrl}/customers/<customerId>?tab=gutschriften&antrag=<id>) - Test:
test/mailTemplates.test.js(ergänzen)
Interfaces:
-
Consumes: WOMS-Routen
/customers,/customers/chat?kunde=,/customers/<id>?tab=gutschriften&antrag=(live seit Etappe 2/3). -
Step 1: Failing tests
test('keine Vorlage verlinkt admin.html oder nennt den Admin-Bereich', () => {
for (const t of allTemplates()) {
assert.ok(!t.html.includes('admin.html'), t.id)
assert.ok(!t.html.includes('Admin-Bereich'), t.id)
}
})
test('Fusszeile der Team-Mails: Ticketsystem', () => { /* die 15 Vorlagen enthalten href="https://ticket.webklar.com/customers" und den Text "Ticketsystem" */ })
test('T01 antwortet im Ticketsystem beim Kunden', async () => {
// sendChatNotification({ $id: 'c1', name: 'Berta' }, 'Hallo') ueber mailSink: html enthaelt href="https://ticket.webklar.com/customers/chat?kunde=c1" und "Im Ticketsystem antworten"
})
test('T10/T11 oeffnen den Antrag im Kunden-Reiter Gutschriften', async () => {
// Antrag anlegen bzw. beantworten wie in test/takeoverCredits*: Link https://ticket.webklar.com/customers/c1?tab=gutschriften&antrag=<id>
})
- Step 2: Run
node --test test/mailTemplates.test.js→ FAIL - Step 3: Implementieren (Platzhalter-Prüfung
declared == usedausmailTemplates.test.jsmuss grün bleiben) - Step 4: Run
npm test→ PASS - Step 5: Commit — „Team-Mails: Links ins Ticketsystem statt Admin-Bereich“
WOMS
Task 4: Knopf „Projekt-Admin“ entfernen
Files:
-
Modify:
src/components/Navbar.jsx(Knopf :142-151,handleProjectAdmin,switching, ImportopenProjectAdmin,IconExternalLink),src/lib/employeeAdminApi.js(openProjectAdminraus),src/styles/global.css(.side-link-buttonraus), Kommentare mitpublic/admin.js-Zeilen → „wie im früheren Admin-Panel“ (storageFormat.js,creditFormat.js,offerFormat.js,OfferForm.jsx,CreditDetail.jsx) -
Test:
tests/unit/navLinks.test.mjs(unverändert grün), Harnesstest-e3.mjsSidebar-Check um „kein Projekt-Admin“ ergänzen -
Step 1: Harness-Check
sidebar_ohne_projekt_adminergänzen → Run → FAIL -
Step 2: Implementieren;
npm test→ PASS; Vite-Build → „built in“; Harness → alle ok -
Step 3: Commit — „Sidebar: Projekt-Admin entfernt (Admin-Panel abgeschaltet)“
Abschluss
Task 5: Gesamtprüfung und Übergabe
- Portal
npm testgrün, neue/geänderte Tests unter Node 20 grün; UI-Checks grün. WOMSnpm test, Build, Harnesses (Etappe 2 und 3) grün. - Abschlussreview beider Branches (frischer Reviewer, sonnet).
- Übergabe, WOMS zuerst: 1.
git -C /home/webklar/apps/ticket.webklar.com/dev-admin-umzug push origin feature/admin-umzug:test; 2. Live-Check (ich): Bundle ohne „Projekt-Admin“; 3.git -C /home/webklar/apps/kundenbereich.webklar.com/dev-admin-umzug push origin feature/admin-umzug:main; 4. Live-Check:/admin.html404,/api/portal-admin/chats404, Mitarbeiter-Login 403 mit Hinweis (ohne echtes Passwort: nur Routen),/dashboard.htmlund Kundenansicht weiter ok,chat-composer.js200; 5. Nutzer-Test: eine Team-Mail (z. B. Chat) öffnen und Link prüfen.