diff --git a/FrontendV2/src/components/Header.tsx b/FrontendV2/src/components/Header.tsx index 767b1b3..10789f2 100644 --- a/FrontendV2/src/components/Header.tsx +++ b/FrontendV2/src/components/Header.tsx @@ -1,4 +1,15 @@ -import { Badge, Button, Flex, Heading, Stack, Text } from "@chakra-ui/react"; +import { + Badge, + Button, + Flex, + Heading, + Stack, + Text, + CloseButton, + Dialog, + Portal, +} from "@chakra-ui/react"; +import { PasswordInput } from "@/components/ui/password-input"; import Cookies from "js-cookie"; import { useAtom } from "jotai"; import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms"; @@ -12,15 +23,64 @@ import { CalendarPlus, } from "lucide-react"; import { useUserContext } from "@/states/Context"; +import { useState } from "react"; +import MyAlert from "./myChakra/MyAlert"; + +const API_BASE = + (import.meta as any).env?.VITE_BACKEND_URL || + import.meta.env.VITE_BACKEND_URL || + "http://localhost:8002"; export const Header = () => { const navigate = useNavigate(); const userData = useUserContext(); + // Error handling states + const [isMsg, setIsMsg] = useState(false); + const [msgStatus, setMsgStatus] = useState<"error" | "success">("error"); + const [msgTitle, setMsgTitle] = useState(""); + const [msgDescription, setMsgDescription] = useState(""); + + const [oldPassword, setOldPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [, setTriggerLogout] = useAtom(triggerLogoutAtom); const [, setIsLoggedIn] = useAtom(setIsLoggedInAtom); + const changePassword = async () => { + if (newPassword !== confirmPassword) { + setMsgTitle("Passwortänderung fehlgeschlagen"); + setMsgDescription("Passwörter stimmen nicht überein"); + setMsgStatus("error"); + setIsMsg(true); + return; + } + + const response = await fetch(`${API_BASE}/api/changePassword`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token")}`, + }, + body: JSON.stringify({ oldPassword, newPassword }), + }); + + if (!response.ok) { + setMsgTitle("Passwortänderung fehlgeschlagen"); + setMsgDescription("Bitte überprüfen Sie Ihre Eingaben"); + setMsgStatus("error"); + setIsMsg(true); + return; + } + + setMsgTitle("Passwort erfolgreich geändert"); + setMsgDescription("Ihr Passwort wurde erfolgreich geändert"); + setMsgStatus("success"); + setIsMsg(true); + }; + return ( { - + + + + + + + + + + Passwort ändern + +
{ + e.preventDefault(); + changePassword(); + }} + > + + setOldPassword(e.target.value)} + placeholder="Altes Passwort" + /> + setNewPassword(e.target.value)} + placeholder="Neues Passwort" + /> + setConfirmPassword(e.target.value)} + placeholder="Neues Passwort wiederholen" + /> + + + {isMsg && ( + + )} + + + + + +
+ + + +
+
+
+
void + /** + * Custom icons for the visibility toggle button. + */ + visibilityIcon?: { on: React.ReactNode; off: React.ReactNode } +} + +export interface PasswordInputProps + extends InputProps, + PasswordVisibilityProps { + rootProps?: GroupProps +} + +export const PasswordInput = React.forwardRef< + HTMLInputElement, + PasswordInputProps +>(function PasswordInput(props, ref) { + const { + rootProps, + defaultVisible, + visible: visibleProp, + onVisibleChange, + visibilityIcon = { on: , off: }, + ...rest + } = props + + const [visible, setVisible] = useControllableState({ + value: visibleProp, + defaultValue: defaultVisible || false, + onChange: onVisibleChange, + }) + + const inputRef = React.useRef(null) + + return ( + { + if (rest.disabled) return + if (e.button !== 0) return + e.preventDefault() + setVisible(!visible) + }} + > + {visible ? visibilityIcon.off : visibilityIcon.on} + + } + {...rootProps} + > + + + ) +}) + +const VisibilityTrigger = React.forwardRef( + function VisibilityTrigger(props, ref) { + return ( + + ) + }, +) + +interface PasswordStrengthMeterProps extends StackProps { + max?: number + value: number +} + +export const PasswordStrengthMeter = React.forwardRef< + HTMLDivElement, + PasswordStrengthMeterProps +>(function PasswordStrengthMeter(props, ref) { + const { max = 4, value, ...rest } = props + + const percent = (value / max) * 100 + const { label, colorPalette } = getColorPalette(percent) + + return ( + + + {Array.from({ length: max }).map((_, index) => ( + + ))} + + {label && {label}} + + ) +}) + +function getColorPalette(percent: number) { + switch (true) { + case percent < 33: + return { label: "Low", colorPalette: "red" } + case percent < 66: + return { label: "Medium", colorPalette: "orange" } + default: + return { label: "High", colorPalette: "green" } + } +} diff --git a/FrontendV2/src/pages/LoginPage.tsx b/FrontendV2/src/pages/LoginPage.tsx index 1ab9f46..318d83b 100644 --- a/FrontendV2/src/pages/LoginPage.tsx +++ b/FrontendV2/src/pages/LoginPage.tsx @@ -5,6 +5,7 @@ import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms"; import { useAtom } from "jotai"; import Cookies from "js-cookie"; import { Navigate, useNavigate } from "react-router-dom"; +import { PasswordInput } from "@/components/ui/password-input"; const API_BASE = (import.meta as any).env?.VITE_BACKEND_URL || @@ -79,16 +80,15 @@ export const LoginPage = () => { - username + Benutzername setUsername(e.target.value)} /> - password - Passwort + setPassword(e.target.value)} />