fix: 2x Portal-Bugs (Vertragsauswahl + Email-Sync)
Bug 1 — Support-Anfrage: ausgewaehlter Vertrag nicht erkennbar
Im Kundenportal beim Erstellen einer Support-Anfrage war der
Selected-State des Vertrags nur ein dezenter blau-grauer
Hintergrund + Border-Farbwechsel. Auf hellem Bildschirm / nicht-
perfekter Lichtsituation kaum zu sehen.
Fix: kraefigere Markierung mit linkem 4px-Akzent-Bar
(border-l-blue-600), kraefigerem Background (bg-blue-100),
Checkmark-Icon rechtsbuendig und blauer Titel-Text.
Bug 2 — Email-Sync im Portal: "Keine Berechtigung"
POST /api/stressfrei-emails/:id/sync hatte
requirePermission('customers:update') – die Portal-Kunden nicht
haben (nur customers:read fuer eigene Daten). Sie konnten ihr
eigenes Postfach nicht synchronisieren.
Fix: Perm-Middleware aus der Route raus, Mitarbeiter-Check +
Owner-Check in den Controller verlegt:
- isCustomerPortal: nur Owner-Check (canAccessStressfreiEmail)
- Mitarbeiter: muss customers:update haben
Trennung der Threat-Modelle – Portal-User darf sein Postfach
syncen, sonst aber nichts triggern; Mitarbeiter brauchen weiter
die Update-Perm.
Live-verifiziert:
- Portal-User 1 syncs eigenes Konto → Auth passiert (400 wegen
fehlender IMAP-Config in dev-DB, NICHT 403)
- Portal-User 1 syncs Customer-3-Konto → 403 "Kein Zugriff"
- Mitarbeiter ohne customers:update → weiter 403
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -660,21 +660,33 @@ function CreateSupportTicketModal({
|
||||
/>
|
||||
<div className="max-h-48 overflow-y-auto border rounded-lg">
|
||||
{filteredContracts.length > 0 ? (
|
||||
filteredContracts.map((contract) => (
|
||||
<div
|
||||
key={contract.id}
|
||||
onClick={() => setSelectedContractId(contract.id)}
|
||||
className={`p-3 cursor-pointer border-b last:border-b-0 hover:bg-gray-50 ${
|
||||
selectedContractId === contract.id ? 'bg-blue-50 border-blue-200' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium">{contract.contractNumber}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{contract.providerName || 'Kein Anbieter'}
|
||||
{contract.tariffName && ` - ${contract.tariffName}`}
|
||||
filteredContracts.map((contract) => {
|
||||
const isSelected = selectedContractId === contract.id;
|
||||
return (
|
||||
<div
|
||||
key={contract.id}
|
||||
onClick={() => setSelectedContractId(contract.id)}
|
||||
className={`p-3 cursor-pointer border-b last:border-b-0 transition-colors flex items-center gap-3 ${
|
||||
isSelected
|
||||
? 'bg-blue-100 border-l-4 border-l-blue-600 pl-2'
|
||||
: 'hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{isSelected && (
|
||||
<CheckCircle className="w-5 h-5 text-blue-600 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className={`font-medium ${isSelected ? 'text-blue-900' : ''}`}>
|
||||
{contract.contractNumber}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{contract.providerName || 'Kein Anbieter'}
|
||||
{contract.tariffName && ` - ${contract.tariffName}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="p-3 text-gray-500 text-center">
|
||||
Keine Verträge gefunden.
|
||||
|
||||
Reference in New Issue
Block a user