From e315160975f003f59b1c523dc4e8f2d0fed71b39 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Tue, 17 Mar 2026 17:04:45 +0100 Subject: [PATCH] feat: Add password confirmation and profile editing to user management Frontend: - Add password confirmation field when creating sub-users - Add password mismatch validation - Add 'Meine Stammdaten' form for primary users to edit their profile - Extend session context with updateUser() function - Disable autocomplete for sensitive fields --- frontend/src/lib/session.tsx | 7 + frontend/src/pages/UserManagementPage.tsx | 228 +++++++++++++++++++++- 2 files changed, 230 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/session.tsx b/frontend/src/lib/session.tsx index a062667..ec09664 100644 --- a/frontend/src/lib/session.tsx +++ b/frontend/src/lib/session.tsx @@ -14,12 +14,14 @@ interface SessionContextValue { user: UserOption | null; ready: boolean; setSession: (session: SessionResponse | null) => void; + updateUser: (user: UserOption) => void; } const SessionContext = createContext({ user: null, ready: false, setSession: () => undefined, + updateUser: () => undefined, }); function loadStoredUser(): UserOption | null { @@ -91,11 +93,16 @@ export function SessionProvider({ children }: PropsWithChildren) { setReady(true); } + function updateUser(updatedUser: UserOption) { + setUserState(updatedUser); + } + const value = useMemo( () => ({ user, ready, setSession, + updateUser, }), [ready, user], ); diff --git a/frontend/src/pages/UserManagementPage.tsx b/frontend/src/pages/UserManagementPage.tsx index ae3fdce..b568d80 100644 --- a/frontend/src/pages/UserManagementPage.tsx +++ b/frontend/src/pages/UserManagementPage.tsx @@ -12,7 +12,7 @@ interface SubUserRow { } export default function UserManagementPage() { - const { user } = useSession(); + const { user, updateUser } = useSession(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [message, setMessage] = useState(null); @@ -25,8 +25,39 @@ export default function UserManagementPage() { const [newUserName, setNewUserName] = useState(""); const [newUserEmail, setNewUserEmail] = useState(""); const [newUserPassword, setNewUserPassword] = useState(""); + const [newUserPasswordConfirm, setNewUserPasswordConfirm] = useState(""); const [creating, setCreating] = useState(false); + // Form state for editing own profile + const [showProfileForm, setShowProfileForm] = useState(false); + const [profileData, setProfileData] = useState({ + displayName: "", + companyName: "", + street: "", + houseNumber: "", + postalCode: "", + city: "", + email: "", + phoneNumber: "", + }); + const [savingProfile, setSavingProfile] = useState(false); + + // Initialize profile data from session user + useEffect(() => { + if (user) { + setProfileData({ + displayName: user.displayName || "", + companyName: user.companyName || "", + street: user.street || "", + houseNumber: user.houseNumber || "", + postalCode: user.postalCode || "", + city: user.city || "", + email: user.email || "", + phoneNumber: user.phoneNumber || "", + }); + } + }, [user]); + useEffect(() => { async function loadUsers() { try { @@ -98,6 +129,10 @@ export default function UserManagementPage() { setMessage("Bitte alle Felder ausfüllen."); return; } + if (newUserPassword !== newUserPasswordConfirm) { + setMessage("Die Passwörter stimmen nicht überein."); + return; + } setCreating(true); try { @@ -124,6 +159,7 @@ export default function UserManagementPage() { setNewUserName(""); setNewUserEmail(""); setNewUserPassword(""); + setNewUserPasswordConfirm(""); setShowCreateForm(false); setMessage(`Benutzer "${newUserName}" wurde erstellt.`); setTimeout(() => setMessage(null), 3000); @@ -134,6 +170,52 @@ export default function UserManagementPage() { } } + async function handleSaveProfile(e: React.FormEvent) { + e.preventDefault(); + if (!profileData.displayName.trim()) { + setMessage("Name ist erforderlich."); + return; + } + + setSavingProfile(true); + try { + const response = await apiPost("/portal/users", { + id: user?.id, + displayName: profileData.displayName.trim(), + companyName: profileData.companyName.trim() || null, + street: profileData.street.trim() || null, + houseNumber: profileData.houseNumber.trim() || null, + postalCode: profileData.postalCode.trim() || null, + city: profileData.city.trim() || null, + email: profileData.email.trim() || null, + phoneNumber: profileData.phoneNumber.trim() || null, + }); + + // Update session user + if (updateUser && user) { + updateUser({ + ...user, + displayName: response.displayName, + companyName: response.companyName, + street: response.street, + houseNumber: response.houseNumber, + postalCode: response.postalCode, + city: response.city, + email: response.email, + phoneNumber: response.phoneNumber, + }); + } + + setShowProfileForm(false); + setMessage("Ihre Stammdaten wurden aktualisiert."); + setTimeout(() => setMessage(null), 3000); + } catch (error) { + setMessage((error as Error).message); + } finally { + setSavingProfile(false); + } + } + function formatDate(value: string) { return new Intl.DateTimeFormat("de-DE", { dateStyle: "medium", @@ -174,7 +256,8 @@ export default function UserManagementPage() { className={ message.includes("freigegeben") || message.includes("gesperrt") || - message.includes("erstellt") + message.includes("erstellt") || + message.includes("aktualisiert") ? "alert alert--success" : "alert alert--error" } @@ -183,6 +266,127 @@ export default function UserManagementPage() { ) : null} + {/* Own Profile Form (nur für Hauptbenutzer) */} + {isPrimaryUser && !isAdmin && ( +
+ {!showProfileForm ? ( +
+
+

Meine Stammdaten

+

{user?.displayName}

+
+ +
+ ) : ( + <> +
+
+

Meine Stammdaten

+

Stammdaten bearbeiten

+
+ +
+
+ + + + + + + + +
+ +
+
+ + )} +
+ )} + {/* Create Sub-user Form (nur für Hauptnutzer) */} {isPrimaryUser && !isAdmin && (
@@ -217,32 +421,46 @@ export default function UserManagementPage() {
+