Füge eine Benutzerverwaltung hinzu, damit "Manage Treatments" und "Manage Bookings" nur für den Shop Inhaber zugänglich ist.
This commit is contained in:
165
src/client/components/user-profile.tsx
Normal file
165
src/client/components/user-profile.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/client/components/auth-provider";
|
||||
import { queryClient } from "@/client/rpc-client";
|
||||
|
||||
export function UserProfile() {
|
||||
const { user, sessionId, logout } = useAuth();
|
||||
const [showPasswordChange, setShowPasswordChange] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [message, setMessage] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const { mutate: changePassword, isPending } = useMutation(
|
||||
queryClient.auth.changePassword.mutationOptions()
|
||||
);
|
||||
|
||||
const handlePasswordChange = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setMessage("");
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
setError("Neue Passwörter stimmen nicht überein");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
setError("Neues Passwort muss mindestens 6 Zeichen lang sein");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
setError("Keine gültige Sitzung");
|
||||
return;
|
||||
}
|
||||
|
||||
changePassword({
|
||||
sessionId,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
setMessage("Passwort erfolgreich geändert");
|
||||
setCurrentPassword("");
|
||||
setNewPassword("");
|
||||
setConfirmPassword("");
|
||||
setShowPasswordChange(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
setError(err instanceof Error ? err.message : "Fehler beim Ändern des Passworts");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 max-w-2xl mx-auto">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-2xl font-bold text-gray-900">Benutzerprofil</h2>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium"
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Benutzername</label>
|
||||
<p className="mt-1 text-lg text-gray-900">{user.username}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">E-Mail</label>
|
||||
<p className="mt-1 text-lg text-gray-900">{user.email}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Rolle</label>
|
||||
<p className="mt-1 text-lg text-gray-900 capitalize">{user.role === "owner" ? "Inhaber" : "Kunde"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
onClick={() => setShowPasswordChange(!showPasswordChange)}
|
||||
className="bg-pink-600 text-white px-4 py-2 rounded-md hover:bg-pink-700 font-medium"
|
||||
>
|
||||
{showPasswordChange ? "Abbrechen" : "Passwort ändern"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showPasswordChange && (
|
||||
<form onSubmit={handlePasswordChange} className="mt-6 space-y-4 border-t pt-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900">Passwort ändern</h3>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message && (
|
||||
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded">
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Aktuelles Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Neues Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
|
||||
minLength={6}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Neues Passwort bestätigen
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
|
||||
minLength={6}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="bg-pink-600 text-white px-4 py-2 rounded-md hover:bg-pink-700 disabled:opacity-50 font-medium"
|
||||
>
|
||||
{isPending ? "Ändern..." : "Passwort ändern"}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user