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) {
|
if (result) {
|
||||||
setActionSuccess(notification.id);
|
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
|
// Clear success state after animation
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setActionSuccess(null);
|
setActionSuccess(null);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,9 @@ export interface Invitation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InvitationCreate {
|
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;
|
email?: string;
|
||||||
roleIds: string[];
|
roleIds: string[];
|
||||||
featureInstanceId?: string;
|
featureInstanceId?: string;
|
||||||
|
|
@ -62,6 +64,7 @@ export interface InvitationValidation {
|
||||||
mandateId?: string;
|
mandateId?: string;
|
||||||
mandateName?: string;
|
mandateName?: string;
|
||||||
featureInstanceId?: string;
|
featureInstanceId?: string;
|
||||||
|
featureInstanceName?: string;
|
||||||
roleIds: string[];
|
roleIds: string[];
|
||||||
roleLabels?: string[];
|
roleLabels?: string[];
|
||||||
targetUsername?: string;
|
targetUsername?: string;
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,8 @@
|
||||||
.infoRow {
|
.infoRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 1rem;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,11 +135,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoLabel {
|
.infoLabel {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 12rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoValue {
|
.infoValue {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,11 @@ export const InvitePage: React.FC = () => {
|
||||||
if (result.valid && !isAuthenticated) {
|
if (result.valid && !isAuthenticated) {
|
||||||
localStorage.setItem(PENDING_INVITATION_KEY, token);
|
localStorage.setItem(PENDING_INVITATION_KEY, token);
|
||||||
|
|
||||||
// Check if the target username already has an account
|
// No targetUsername = new-user invitation (email only) -> only show "Neues Konto erstellen"
|
||||||
if (result.targetUsername) {
|
if (!result.targetUsername) {
|
||||||
|
setUserExists(false); // Treat as new user, show only register
|
||||||
|
} else {
|
||||||
|
// Check if the target username already has an account
|
||||||
try {
|
try {
|
||||||
const resp = await api.get(`/api/local/available`, {
|
const resp = await api.get(`/api/local/available`, {
|
||||||
params: { username: result.targetUsername }
|
params: { username: result.targetUsername }
|
||||||
|
|
@ -188,13 +191,22 @@ export const InvitePage: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already authenticated - show accept button
|
// 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) {
|
if (isAuthenticated) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h1>Einladung annehmen</h1>
|
<h1>Einladung annehmen</h1>
|
||||||
<p>Sie wurden eingeladen, einem Mandanten beizutreten.</p>
|
<p>{introText}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.inviteInfo}>
|
<div className={styles.inviteInfo}>
|
||||||
|
|
@ -214,10 +226,10 @@ export const InvitePage: React.FC = () => {
|
||||||
<span className={styles.infoLabel}>Status:</span>
|
<span className={styles.infoLabel}>Status:</span>
|
||||||
<span className={styles.infoValue}>Angemeldet</span>
|
<span className={styles.infoValue}>Angemeldet</span>
|
||||||
</div>
|
</div>
|
||||||
{validation.roleLabels && validation.roleLabels.length > 0 && (
|
{rolesValue && (
|
||||||
<div className={styles.infoRow}>
|
<div className={styles.infoRow}>
|
||||||
<span className={styles.infoLabel}>Zugewiesene Rollen:</span>
|
<span className={styles.infoLabel}>{rolesLabel}:</span>
|
||||||
<span className={styles.infoValue}>{validation.roleLabels.join(', ')}</span>
|
<span className={styles.infoValue}>{rolesValue}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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 (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h1>Einladung annehmen</h1>
|
<h1>Einladung annehmen</h1>
|
||||||
<p>Sie wurden eingeladen, einem Mandanten beizutreten.</p>
|
<p>{introText}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.inviteInfo}>
|
<div className={styles.inviteInfo}>
|
||||||
|
|
@ -273,21 +285,19 @@ export const InvitePage: React.FC = () => {
|
||||||
<span className={styles.infoValue}>{validation.mandateName}</span>
|
<span className={styles.infoValue}>{validation.mandateName}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{validation.roleLabels && validation.roleLabels.length > 0 && (
|
{rolesValue && (
|
||||||
<div className={styles.infoRow}>
|
<div className={styles.infoRow}>
|
||||||
<span className={styles.infoLabel}>Zugewiesene Rollen:</span>
|
<span className={styles.infoLabel}>{rolesLabel}:</span>
|
||||||
<span className={styles.infoValue}>{validation.roleLabels.join(', ')}</span>
|
<span className={styles.infoValue}>{rolesValue}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.authPrompt}>
|
<div className={styles.authPrompt}>
|
||||||
<p>
|
<p>
|
||||||
{userExists === true
|
{userExists === true && validation.targetUsername
|
||||||
? `Bitte melden Sie sich als "${validation.targetUsername}" an, um die Einladung anzunehmen.`
|
? `Sie haben bereits ein Konto (${validation.targetUsername}). Melden Sie sich an oder erstellen Sie ein neues Konto.`
|
||||||
: userExists === false
|
: 'Erstellen Sie ein neues Konto mit Ihrem Benutzernamen oder verlinken Sie die Einladung mit Ihrem bestehenden Account.'}
|
||||||
? 'Bitte erstellen Sie ein Konto, um die Einladung anzunehmen.'
|
|
||||||
: 'Bitte melden Sie sich an oder erstellen Sie ein Konto, um die Einladung anzunehmen.'}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -298,48 +308,26 @@ export const InvitePage: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.authActions}>
|
<div className={styles.authActions}>
|
||||||
{userExists === true ? (
|
<button
|
||||||
<button
|
className={styles.primaryButton}
|
||||||
className={styles.primaryButton}
|
onClick={handleRegisterRedirect}
|
||||||
onClick={handleLoginRedirect}
|
>
|
||||||
>
|
<FaUserPlus /> Neues Konto erstellen
|
||||||
<FaSignInAlt /> Anmelden
|
</button>
|
||||||
</button>
|
<div className={styles.divider}>
|
||||||
) : userExists === false ? (
|
<span>oder</span>
|
||||||
<button
|
</div>
|
||||||
className={styles.primaryButton}
|
<button
|
||||||
onClick={handleRegisterRedirect}
|
className={styles.secondaryButton}
|
||||||
>
|
onClick={handleLoginRedirect}
|
||||||
<FaUserPlus /> Konto erstellen
|
>
|
||||||
</button>
|
<FaSignInAlt /> Mit bestehendem Konto verlinken
|
||||||
) : (
|
</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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.authInfo}>
|
<div className={styles.authInfo}>
|
||||||
<p>
|
<p>
|
||||||
{userExists === true
|
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.
|
||||||
? '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.'}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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 React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
useUserMandates,
|
useUserMandates,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Ein User gehört keinem Mandanten direkt an, sondern hat Zugriff auf Feature-Instanzen.
|
* 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 {
|
import type {
|
||||||
Mandate,
|
Mandate,
|
||||||
MandateFeature,
|
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
|
* Holt einen Mandanten per ID
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue