diff --git a/frontend/src/components/PortalTokenCard.tsx b/frontend/src/components/PortalTokenCard.tsx new file mode 100644 index 0000000..4c19008 --- /dev/null +++ b/frontend/src/components/PortalTokenCard.tsx @@ -0,0 +1,146 @@ +import { useEffect, useState } from 'react' +import { api } from '../api/client' + +interface Status { configured: boolean; created_at: string | null } + +export function PortalTokenCard() { + const [status, setStatus] = useState(null) + const [plaintext, setPlaintext] = useState(null) + const [busy, setBusy] = useState(false) + const [copied, setCopied] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + api.get('/companies/me/portal-token').then(setStatus).catch(() => {}) + }, []) + + async function rotate() { + if (status?.configured && !confirm('Neuen Token generieren? Der bisherige Token wird sofort ungültig und das Buchhaltungsportal muss den neuen eintragen.')) return + setBusy(true) + setError(null) + try { + const res = await api.post<{ token: string; created_at: string }>('/companies/me/portal-token/rotate', {}) + setPlaintext(res.token) + setStatus({ configured: true, created_at: res.created_at }) + setCopied(false) + } catch (e: unknown) { + setError(e instanceof Error ? e.message : 'Fehler beim Generieren des Tokens') + } finally { + setBusy(false) + } + } + + async function revoke() { + if (!confirm('Token wirklich deaktivieren? Das Buchhaltungsportal kann danach keine Summen mehr abrufen.')) return + setBusy(true) + setError(null) + try { + await api.del('/companies/me/portal-token') + setStatus({ configured: false, created_at: null }) + setPlaintext(null) + } catch (e: unknown) { + setError(e instanceof Error ? e.message : 'Fehler beim Deaktivieren') + } finally { + setBusy(false) + } + } + + async function copy() { + if (!plaintext) return + try { + await navigator.clipboard.writeText(plaintext) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { + /* clipboard nicht verfügbar – User kann manuell markieren */ + } + } + + return ( +
+
+

Buchhaltungsportal

+

+ Das Buchhaltungsportal ruft Firmensummen zu Resturlaub und Überstunden via Bearer-Token ab (nur lesen, keine + Einzelwerte, keine Krankheitsdaten). Endpoint:{' '} + GET /api/v1/portal/summary. +

+
+ +
+ + Test-Befehl anzeigen (curl) + +
+ {`curl -H "Authorization: Bearer " \\\n ${window.location.origin}/api/v1/portal/summary`} +
+
+ +
+ {status?.configured ? ( +
+ + + Token aktiv + {status.created_at && ( + + (seit {new Date(status.created_at).toLocaleString('de-DE')}) + + )} + +
+ ) : ( +
+ + Kein Token konfiguriert +
+ )} +
+ + {plaintext && ( +
+

⚠ Token jetzt kopieren – wird nur einmal angezeigt.

+
+ e.currentTarget.select()} + className="flex-1 font-mono text-xs px-3 py-2 border border-amber-300 rounded bg-white" + /> + +
+

+ Im Buchhaltungsportal unter timemaster eintragen (Adresse der Firma plus dieser Token). +

+
+ )} + + {error &&

{error}

} + +
+ + {status?.configured && ( + + )} +
+
+ ) +} diff --git a/frontend/src/pages/CompanySettingsPage.tsx b/frontend/src/pages/CompanySettingsPage.tsx index 8ed5bb2..775174d 100644 --- a/frontend/src/pages/CompanySettingsPage.tsx +++ b/frontend/src/pages/CompanySettingsPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react' import { api } from '../api/client' import { Layout } from '../components/Layout' +import { PortalTokenCard } from '../components/PortalTokenCard' interface UserOut { id: string; first_name: string; last_name: string; email: string; role: string @@ -643,6 +644,8 @@ export function CompanySettingsPage() { )} + {isAdmin && } + {/* QR-Stempeln wurde in eine eigene Seite ausgelagert: /settings/qr-stamp */} {/* Mobile-Einstellungen */}