fix: Invitation Wizard Anpassungen fertig
This commit is contained in:
parent
b5e9599ef0
commit
f312cd41b1
7 changed files with 579 additions and 548 deletions
|
|
@ -98,6 +98,10 @@ export const NotificationBell: React.FC<NotificationBellProps> = ({ className })
|
|||
|
||||
if (result) {
|
||||
setActionSuccess(notification.id);
|
||||
// Reload sidebar when accepting an invitation (grants new mandate/feature access)
|
||||
if (actionId === 'accept' && notification.referenceType === 'Invitation') {
|
||||
window.dispatchEvent(new CustomEvent('features-changed'));
|
||||
}
|
||||
// Clear success state after animation
|
||||
setTimeout(() => {
|
||||
setActionSuccess(null);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ export interface Invitation {
|
|||
}
|
||||
|
||||
export interface InvitationCreate {
|
||||
targetUsername: string;
|
||||
/** Username of the user to invite (optional when email is provided) */
|
||||
targetUsername?: string;
|
||||
/** Email address to send invitation link (required for new users) */
|
||||
email?: string;
|
||||
roleIds: string[];
|
||||
featureInstanceId?: string;
|
||||
|
|
@ -62,6 +64,7 @@ export interface InvitationValidation {
|
|||
mandateId?: string;
|
||||
mandateName?: string;
|
||||
featureInstanceId?: string;
|
||||
featureInstanceName?: string;
|
||||
roleIds: string[];
|
||||
roleLabels?: string[];
|
||||
targetUsername?: string;
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@
|
|||
.infoRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
|
|
@ -133,11 +135,15 @@
|
|||
}
|
||||
|
||||
.infoLabel {
|
||||
flex-shrink: 0;
|
||||
min-width: 12rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.infoValue {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
|
|
|
|||
|
|
@ -66,8 +66,11 @@ export const InvitePage: React.FC = () => {
|
|||
if (result.valid && !isAuthenticated) {
|
||||
localStorage.setItem(PENDING_INVITATION_KEY, token);
|
||||
|
||||
// Check if the target username already has an account
|
||||
if (result.targetUsername) {
|
||||
// No targetUsername = new-user invitation (email only) -> only show "Neues Konto erstellen"
|
||||
if (!result.targetUsername) {
|
||||
setUserExists(false); // Treat as new user, show only register
|
||||
} else {
|
||||
// Check if the target username already has an account
|
||||
try {
|
||||
const resp = await api.get(`/api/local/available`, {
|
||||
params: { username: result.targetUsername }
|
||||
|
|
@ -188,13 +191,22 @@ export const InvitePage: React.FC = () => {
|
|||
}
|
||||
|
||||
// Already authenticated - show accept button
|
||||
const isFeatureInvite = !!validation.featureInstanceId;
|
||||
const introText = isFeatureInvite
|
||||
? 'Sie wurden eingeladen, einem Mandanten und einem Feature beizutreten.'
|
||||
: 'Sie wurden eingeladen, einem Mandanten beizutreten.';
|
||||
const rolesLabel = isFeatureInvite ? 'Features mit zugewiesenen Rollen' : 'Zugewiesene Rollen';
|
||||
const rolesValue = validation.featureInstanceName && validation.roleLabels?.length
|
||||
? `${validation.featureInstanceName} (${validation.roleLabels.join(', ')})`
|
||||
: validation.roleLabels?.join(', ') || '';
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<h1>Einladung annehmen</h1>
|
||||
<p>Sie wurden eingeladen, einem Mandanten beizutreten.</p>
|
||||
<p>{introText}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.inviteInfo}>
|
||||
|
|
@ -214,10 +226,10 @@ export const InvitePage: React.FC = () => {
|
|||
<span className={styles.infoLabel}>Status:</span>
|
||||
<span className={styles.infoValue}>Angemeldet</span>
|
||||
</div>
|
||||
{validation.roleLabels && validation.roleLabels.length > 0 && (
|
||||
{rolesValue && (
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Zugewiesene Rollen:</span>
|
||||
<span className={styles.infoValue}>{validation.roleLabels.join(', ')}</span>
|
||||
<span className={styles.infoLabel}>{rolesLabel}:</span>
|
||||
<span className={styles.infoValue}>{rolesValue}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -251,13 +263,13 @@ export const InvitePage: React.FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Not authenticated - show appropriate options based on whether user account exists
|
||||
// Not authenticated - show create account / link to existing
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<h1>Einladung annehmen</h1>
|
||||
<p>Sie wurden eingeladen, einem Mandanten beizutreten.</p>
|
||||
<p>{introText}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.inviteInfo}>
|
||||
|
|
@ -273,21 +285,19 @@ export const InvitePage: React.FC = () => {
|
|||
<span className={styles.infoValue}>{validation.mandateName}</span>
|
||||
</div>
|
||||
)}
|
||||
{validation.roleLabels && validation.roleLabels.length > 0 && (
|
||||
{rolesValue && (
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Zugewiesene Rollen:</span>
|
||||
<span className={styles.infoValue}>{validation.roleLabels.join(', ')}</span>
|
||||
<span className={styles.infoLabel}>{rolesLabel}:</span>
|
||||
<span className={styles.infoValue}>{rolesValue}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.authPrompt}>
|
||||
<p>
|
||||
{userExists === true
|
||||
? `Bitte melden Sie sich als "${validation.targetUsername}" an, um die Einladung anzunehmen.`
|
||||
: userExists === false
|
||||
? 'Bitte erstellen Sie ein Konto, um die Einladung anzunehmen.'
|
||||
: 'Bitte melden Sie sich an oder erstellen Sie ein Konto, um die Einladung anzunehmen.'}
|
||||
{userExists === true && validation.targetUsername
|
||||
? `Sie haben bereits ein Konto (${validation.targetUsername}). Melden Sie sich an oder erstellen Sie ein neues Konto.`
|
||||
: 'Erstellen Sie ein neues Konto mit Ihrem Benutzernamen oder verlinken Sie die Einladung mit Ihrem bestehenden Account.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -298,48 +308,26 @@ export const InvitePage: React.FC = () => {
|
|||
)}
|
||||
|
||||
<div className={styles.authActions}>
|
||||
{userExists === true ? (
|
||||
<button
|
||||
className={styles.primaryButton}
|
||||
onClick={handleLoginRedirect}
|
||||
>
|
||||
<FaSignInAlt /> Anmelden
|
||||
</button>
|
||||
) : userExists === false ? (
|
||||
<button
|
||||
className={styles.primaryButton}
|
||||
onClick={handleRegisterRedirect}
|
||||
>
|
||||
<FaUserPlus /> Konto erstellen
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className={styles.primaryButton}
|
||||
onClick={handleLoginRedirect}
|
||||
>
|
||||
<FaSignInAlt /> Anmelden
|
||||
</button>
|
||||
<div className={styles.divider}>
|
||||
<span>oder</span>
|
||||
</div>
|
||||
<button
|
||||
className={styles.secondaryButton}
|
||||
onClick={handleRegisterRedirect}
|
||||
>
|
||||
<FaUserPlus /> Neues Konto erstellen
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
className={styles.primaryButton}
|
||||
onClick={handleRegisterRedirect}
|
||||
>
|
||||
<FaUserPlus /> Neues Konto erstellen
|
||||
</button>
|
||||
<div className={styles.divider}>
|
||||
<span>oder</span>
|
||||
</div>
|
||||
<button
|
||||
className={styles.secondaryButton}
|
||||
onClick={handleLoginRedirect}
|
||||
>
|
||||
<FaSignInAlt /> Mit bestehendem Konto verlinken
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.authInfo}>
|
||||
<p>
|
||||
{userExists === true
|
||||
? 'Melden Sie sich mit Ihrem bestehenden Konto an. Die Einladung wird automatisch nach der Anmeldung akzeptiert.'
|
||||
: userExists === false
|
||||
? 'Erstellen Sie ein neues Konto. Die Einladung wird automatisch nach der Registrierung akzeptiert.'
|
||||
: 'Die Einladung wird automatisch nach der Anmeldung akzeptiert.'}
|
||||
Neues Konto: E-Mail wird vorausgefüllt, Benutzername legen Sie selbst fest. Bestehendes Konto: Die Einladung wird nach der Anmeldung automatisch an Ihr Konto verknüpft.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,3 @@
|
|||
/**
|
||||
* AdminMandateWizardPage (v4.0 - poweron port)
|
||||
*
|
||||
* 4-step wizard for mandate management:
|
||||
* 1. Select/Create Mandate
|
||||
* 2. Manage Mandate Users (add/remove users to/from mandate)
|
||||
* 3. Manage Feature Instances (CRUD)
|
||||
* 4. Manage Users per Feature Instance (CRUD + Roles)
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
useUserMandates,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* Ein User gehört keinem Mandanten direkt an, sondern hat Zugriff auf Feature-Instanzen.
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, useRef, ReactNode } from 'react';
|
||||
import React, { createContext, useContext, useState, useCallback, useRef, useEffect, ReactNode } from 'react';
|
||||
import type {
|
||||
Mandate,
|
||||
MandateFeature,
|
||||
|
|
@ -169,6 +169,15 @@ export const FeatureProvider: React.FC<FeatureProviderProps> = ({ children }) =>
|
|||
});
|
||||
}, []);
|
||||
|
||||
// Reload features when access changes (e.g. after accepting an invitation)
|
||||
useEffect(() => {
|
||||
const onFeaturesChanged = () => {
|
||||
loadFeatures();
|
||||
};
|
||||
window.addEventListener('features-changed', onFeaturesChanged);
|
||||
return () => window.removeEventListener('features-changed', onFeaturesChanged);
|
||||
}, [loadFeatures]);
|
||||
|
||||
/**
|
||||
* Holt einen Mandanten per ID
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue