diff --git a/src/components/FolderTree/FolderTree.module.css b/src/components/FolderTree/FolderTree.module.css index deab4d3..1530585 100644 --- a/src/components/FolderTree/FolderTree.module.css +++ b/src/components/FolderTree/FolderTree.module.css @@ -25,7 +25,7 @@ .treeNode.multiSelected { background: var(--color-bg-multi-selected, rgba(25, 118, 210, 0.14)); - box-shadow: inset 3px 0 0 var(--color-primary, #1976d2); + box-shadow: inset 3px 0 0 var(--color-primary, #F25843); } .treeNode.multiSelected:hover { @@ -34,7 +34,7 @@ .treeNode.dropTarget { background: var(--color-bg-drop, rgba(25, 118, 210, 0.15)); - outline: 2px dashed var(--color-primary, #1976d2); + outline: 2px dashed var(--color-primary, #F25843); outline-offset: -2px; } @@ -77,7 +77,7 @@ .renameInput { flex: 1; - border: 1px solid var(--color-primary, #1976d2); + border: 1px solid var(--color-primary, #F25843); border-radius: 3px; padding: 1px 4px; font-size: inherit; diff --git a/src/components/UnifiedDataBar/FilesTab.tsx b/src/components/UnifiedDataBar/FilesTab.tsx index e38c991..93cbc21 100644 --- a/src/components/UnifiedDataBar/FilesTab.tsx +++ b/src/components/UnifiedDataBar/FilesTab.tsx @@ -243,9 +243,9 @@ const FilesTab: React.FC = ({ context, onFileSelect }) => {
Dateien hier ablegen
@@ -257,14 +257,14 @@ const FilesTab: React.FC = ({ context, onFileSelect }) => { diff --git a/src/components/UnifiedDataBar/SourcesTab.tsx b/src/components/UnifiedDataBar/SourcesTab.tsx index dc9b3f7..b1d1828 100644 --- a/src/components/UnifiedDataBar/SourcesTab.tsx +++ b/src/components/UnifiedDataBar/SourcesTab.tsx @@ -142,12 +142,12 @@ const _SOURCE_COLORS: Record = { ftpFolder: '#795548', files: '#795548', 'local:ftp': '#795548', - 'local:jira': '#1976d2', + 'local:jira': '#0052CC', clickup: '#7b68ee', }; function _getSourceColor(sourceType: string): string { - return _SOURCE_COLORS[sourceType] || '#1976d2'; + return _SOURCE_COLORS[sourceType] || '#F25843'; } const _SOURCE_ICONS: Record = { @@ -335,7 +335,7 @@ function _Spinner(): React.ReactElement { return ( @@ -876,7 +876,7 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged }) => @@ -1157,9 +1157,9 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({ onClick={e => { e.stopPropagation(); onAdd(node); }} disabled={isAdding} style={{ - background: 'none', border: '1px solid #1976d2', borderRadius: 3, + background: 'none', border: '1px solid var(--primary-color, #F25843)', borderRadius: 3, cursor: isAdding ? 'not-allowed' : 'pointer', - fontSize: 10, color: '#1976d2', padding: '1px 5px', + fontSize: 10, color: 'var(--primary-color, #F25843)', padding: '1px 5px', opacity: isAdding ? 0.5 : 1, flexShrink: 0, }} diff --git a/src/hooks/useConfirm.tsx b/src/hooks/useConfirm.tsx index ec190d7..f246ab4 100644 --- a/src/hooks/useConfirm.tsx +++ b/src/hooks/useConfirm.tsx @@ -116,7 +116,7 @@ export function useConfirm() { style={{ padding: '8px 18px', borderRadius: '6px', fontSize: '0.875rem', fontWeight: 600, border: 'none', - background: isDanger ? '#ef4444' : 'var(--color-primary, #3b82f6)', + background: isDanger ? '#ef4444' : 'var(--primary-color, #F25843)', color: '#fff', cursor: 'pointer', }} diff --git a/src/hooks/useInvitations.ts b/src/hooks/useInvitations.ts index 68f505a..caf9d41 100644 --- a/src/hooks/useInvitations.ts +++ b/src/hooks/useInvitations.ts @@ -49,7 +49,7 @@ export interface Invitation { export interface InvitationCreate { /** Username of the user to invite (optional when email is provided) */ targetUsername?: string; - /** Email address to send invitation link (required for new users) */ + /** Email to send invitation link; optional if targetUsername is set */ email?: string; roleIds: string[]; featureInstanceId?: string; diff --git a/src/hooks/usePrompt.tsx b/src/hooks/usePrompt.tsx index 38218d6..1f08086 100644 --- a/src/hooks/usePrompt.tsx +++ b/src/hooks/usePrompt.tsx @@ -144,7 +144,7 @@ export function usePrompt() { style={{ padding: '8px 18px', borderRadius: '6px', fontSize: '0.875rem', fontWeight: 600, border: 'none', - background: isDanger ? '#ef4444' : 'var(--color-primary, #3b82f6)', + background: isDanger ? '#ef4444' : 'var(--primary-color, #F25843)', color: '#fff', cursor: 'pointer', }} diff --git a/src/layouts/MainLayout.module.css b/src/layouts/MainLayout.module.css index faf2439..1bb1c9a 100644 --- a/src/layouts/MainLayout.module.css +++ b/src/layouts/MainLayout.module.css @@ -99,6 +99,7 @@ /* Let child components handle their own scrolling for sticky headers */ overflow: hidden; background: var(--bg-primary, #ffffff); + color: var(--text-primary, #1a1a1a); } /* Fills .content flex column so admin pages get a bounded height for inner scroll */ @@ -168,6 +169,7 @@ :global(.dark-theme) .content { background: var(--bg-dark, #0a0a0a); + color: var(--text-primary, #e5e7eb); } :global(.dark-theme) .mobileMenuButton { diff --git a/src/pages/admin/Admin.module.css b/src/pages/admin/Admin.module.css index 00af2c7..bf7c98b 100644 --- a/src/pages/admin/Admin.module.css +++ b/src/pages/admin/Admin.module.css @@ -550,8 +550,8 @@ .statusBadge.starting, .statusBadge.running { - background: #e3f2fd; - color: #1976d2; + background: var(--primary-dark-bg, rgba(242, 88, 67, 0.12)); + color: var(--primary-color, #F25843); } .statusBadge.completed { @@ -617,7 +617,7 @@ } .logStatus { - color: #1976d2; + color: var(--primary-color, #F25843); } .logEntryError .logStatus, diff --git a/src/pages/admin/wizards/AdminInvitationWizardPage.tsx b/src/pages/admin/wizards/AdminInvitationWizardPage.tsx index fffc5b5..392706b 100644 --- a/src/pages/admin/wizards/AdminInvitationWizardPage.tsx +++ b/src/pages/admin/wizards/AdminInvitationWizardPage.tsx @@ -4,7 +4,7 @@ * 4-step invitation wizard: * 1. Choose invite type: "Invite to mandate" OR "Invite to feature instance" * 2. Select mandate (and feature instance if applicable) - * 3. Add invitees (email required, username optional; existing users; role per invitee) + * 3. Add invitees (mindestens E-Mail oder Benutzername für neue Benutzer; bestehende Benutzer; Rolle pro Einladung) * 4. Summary and send */ @@ -167,13 +167,24 @@ export const AdminInvitationWizardPage: React.FC = () => { const addInviteeByEmail = () => { const email = inviteeForm.email.trim(); - if (!email) { - setError('E-Mail ist erforderlich'); + const username = inviteeForm.username.trim(); + if (!email && !username) { + setError('Bitte mindestens eine E-Mail-Adresse oder einen Benutzernamen angeben.'); + return; + } + const emailLower = email.toLowerCase(); + const userLower = username.toLowerCase(); + if (email && invitees.some(i => !i.isExisting && (i.email || '').toLowerCase() === emailLower)) { + setError('Diese E-Mail ist bereits in der Liste'); + return; + } + if (username && invitees.some(i => !i.isExisting && (i.username || '').toLowerCase() === userLower)) { + setError('Dieser Benutzername ist bereits in der Liste'); return; } setInvitees(prev => [...prev, { email, - username: undefined, + username: username || undefined, roleIds: [...inviteeForm.roleIds], isExisting: false, }]); @@ -189,10 +200,6 @@ export const AdminInvitationWizardPage: React.FC = () => { const user = allSystemUsers.find(u => u.id === selectedExistingUserId); if (!user) return; const email = (user.email || '').trim(); - if (!email) { - setError('Dieser Benutzer hat keine E-Mail-Adresse'); - return; - } if (invitees.some(i => i.userId === user.id)) { setError('Dieser Benutzer ist bereits in der Liste'); return; @@ -232,8 +239,9 @@ export const AdminInvitationWizardPage: React.FC = () => { const results: DispatchResult[] = []; try { for (const inv of invitees) { + const emailTrim = (inv.email || '').trim(); const payload = { - email: inv.email, + ...(emailTrim ? { email: emailTrim } : {}), targetUsername: inv.username || undefined, roleIds: inv.roleIds, expiresInHours: EXPIRES_IN_HOURS, @@ -244,14 +252,14 @@ export const AdminInvitationWizardPage: React.FC = () => { const result = await createInvitation(selectedMandate.id, payload); if (result.success) { results.push({ - email: inv.email, + email: emailTrim, username: inv.username, success: true, emailSent: result.data?.emailSent, }); } else { results.push({ - email: inv.email, + email: emailTrim, username: inv.username, success: false, error: result.error, @@ -452,7 +460,7 @@ export const AdminInvitationWizardPage: React.FC = () => {

Einladungen hinzufügen

- E-Mail ist erforderlich. Neue Benutzer legen ihren Benutzernamen beim Annehmen der Einladung selbst fest. Sie können neue Benutzer per E-Mail oder bestehende Benutzer hinzufügen. + Für neue Benutzer: mindestens eine E-Mail oder ein Benutzername (vorgegeben). Ohne E-Mail wird kein Link per Mail versendet — der Einladungslink kann manuell geteilt werden. Bestehende Benutzer wählen Sie im zweiten Tab.

{/* Add form: toggle email vs existing */} @@ -462,7 +470,7 @@ export const AdminInvitationWizardPage: React.FC = () => { style={{ fontSize: '12px', padding: '6px 12px' }} onClick={() => setAddMode('email')} > - Per E-Mail (neue Benutzer) + Neue Benutzer (E-Mail und/oder Benutzername) @@ -552,7 +574,7 @@ export const AdminInvitationWizardPage: React.FC = () => { display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 12px', borderRadius: '6px', background: inviteeForm.roleIds.includes(r.id) ? '#dbeafe' : 'var(--bg-secondary, #f8fafc)', - border: `1px solid ${inviteeForm.roleIds.includes(r.id) ? 'var(--primary-color, #3b82f6)' : 'var(--border-color, #e2e8f0)'}`, + border: `1px solid ${inviteeForm.roleIds.includes(r.id) ? 'var(--primary-color, #F25843)' : 'var(--border-color, #e2e8f0)'}`, fontSize: '12px', cursor: 'pointer', }}> { - + @@ -596,14 +618,16 @@ export const AdminInvitationWizardPage: React.FC = () => { {invitees.map((inv, idx) => ( - - + + - +
E-MailE-Mail / Benutzer Benutzername Rollen Typ
{inv.email}{inv.isExisting ? inv.username : ''}{inv.email || '—'} + {inv.username || ''} + {inv.roleIds.length > 0 ? roles.filter(r => inv.roleIds.includes(r.id)).map(r => r.roleLabel).join(', ') : '-'} {inv.isExisting ? 'Bestehend' : 'Neu'}{inv.isExisting ? 'Bestehend' : 'Neu (Einladung)'}