Create PasswordDialog component

This commit is contained in:
Mathias Wagner
2026-01-20 20:44:33 +01:00
parent 25c58d2f44
commit e0bbd6cf7c
3 changed files with 244 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
import {Dialog, DialogHeader, DialogBody, DialogFooter} from "@/common/contexts/Dialog";
import {t} from "i18next";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faCheck,
faExclamationTriangle,
faEye,
faEyeSlash,
faKey,
faShieldHalved,
faLock,
faBookOpen
} from "@fortawesome/free-solid-svg-icons";
import "./styles.sass";
import React, {useContext, useState} from "react";
import {baseRequest, patchRequest} from "@/common/utils/RequestUtil";
import {ConfigContext} from "@/common/contexts/Config";
import {ToastNotificationContext} from "@/common/contexts/ToastNotification";
import {NodeContext} from "@/common/contexts/Node";
export const PasswordDialog = ({open, onClose}) => {
const [config, reloadConfig] = useContext(ConfigContext);
const updateToast = useContext(ToastNotificationContext);
const findNode = useContext(NodeContext)[4];
const updateNodes = useContext(NodeContext)[1];
const currentNode = useContext(NodeContext)[2];
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [accessLevel, setAccessLevel] = useState(config.passwordLevel || "none");
const resetState = () => {
setPassword("");
setShowPassword(false);
setAccessLevel(config.passwordLevel || "none");
};
const handleClose = (close) => {
resetState();
close();
};
const save = async (close) => {
try {
if (password) {
await patchRequest("/config/password", {value: password});
if (currentNode !== 0) {
await baseRequest("/nodes/" + currentNode + "/password", "PATCH", {password});
updateNodes();
} else {
localStorage.setItem("password", password);
}
}
if (accessLevel !== config.passwordLevel) {
await patchRequest("/config/passwordLevel", {value: accessLevel});
}
reloadConfig();
updateToast(t("dropdown.changes_applied"), "green", faCheck);
handleClose(close);
} catch (e) {
updateToast(t("dropdown.changes_unsaved"), "red", faExclamationTriangle);
}
};
return (
<Dialog open={open} onClose={onClose} className="password-dialog">
{({close}) => (
<>
<DialogHeader onClose={() => handleClose(close)}>{t("dropdown.password")}</DialogHeader>
<DialogBody>
<div className="password-content">
<div className="password-section">
<div className="password-label">
<FontAwesomeIcon icon={faKey}/>
<h3>{t("update.new_password")}</h3>
</div>
<div className="password-input-wrapper">
<input
type={showPassword ? "text" : "password"}
className="dialog-input"
placeholder={t("update.password_placeholder")}
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
type="button"
className="password-toggle"
onClick={() => setShowPassword(!showPassword)}
>
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye}/>
</button>
</div>
</div>
<div className="password-section">
<div className="password-label">
<FontAwesomeIcon icon={faShieldHalved}/>
<h3>{t("update.level_title")}</h3>
</div>
<div className="access-options">
<button
className={`access-option${accessLevel === "none" ? " access-active" : ""}`}
onClick={() => setAccessLevel("none")}
>
<FontAwesomeIcon icon={faLock}/>
<div className="access-text">
<span className="access-title">{t("options.level.no_access")}</span>
<span className="access-desc">{t("password.no_access_desc")}</span>
</div>
</button>
<button
className={`access-option${accessLevel === "read" ? " access-active" : ""}`}
onClick={() => setAccessLevel("read")}
>
<FontAwesomeIcon icon={faBookOpen}/>
<div className="access-text">
<span className="access-title">{t("options.level.read_access")}</span>
<span className="access-desc">{t("password.read_access_desc")}</span>
</div>
</button>
</div>
</div>
</div>
</DialogBody>
<DialogFooter>
<button className="dialog-btn" onClick={() => save(close)}>{t("dialog.update")}</button>
</DialogFooter>
</>
)}
</Dialog>
);
};

View File

@@ -0,0 +1 @@
export {PasswordDialog as default} from './PasswordDialog';

View File

@@ -0,0 +1,109 @@
@use "@/common/styles/colors" as *
.password-dialog
width: 22rem
.dialog-main
display: block
.password-content
margin: 1rem 0.5rem
display: flex
flex-direction: column
gap: 1.5rem
user-select: none
.password-section
display: flex
flex-direction: column
gap: 0.5rem
.password-label
display: flex
align-items: center
gap: 0.5rem
color: $accent-primary
svg
font-size: 0.9rem
h3
font-size: 0.9rem
font-weight: 600
margin: 0
.password-input-wrapper
position: relative
display: flex
align-items: center
.dialog-input
flex: 1
padding-right: 2.5rem
.password-toggle
position: absolute
right: 0.75rem
background: none
border: none
color: $subtext
cursor: pointer
padding: 0.5rem
display: flex
align-items: center
justify-content: center
transition: color 0.15s ease
&:hover
color: $white
.access-options
display: flex
flex-direction: column
gap: 0.5rem
.access-option
display: flex
align-items: center
gap: 0.8rem
padding: 0.6rem 1rem
background: transparent
border: 1px solid $light-gray
border-radius: 0.5rem
cursor: pointer
transition: all 0.15s ease
text-align: left
&:hover
background-color: $darker-gray
svg
font-size: 1.2rem
color: $subtext
width: 1.5rem
&.access-active
background-color: $light-gray
svg, .access-title
color: $white
&:hover
background-color: $light-gray
.access-text
display: flex
flex-direction: column
flex: 1
.access-title
font-size: 0.95rem
font-weight: 600
color: $subtext
.access-desc
font-size: 0.8rem
color: $subtext
opacity: 0.7
margin-top: 0.15rem