Session handling bugfix, profile frontend WIP

This commit is contained in:
2024-04-06 11:52:22 +02:00
parent b68ff85578
commit fe81e0f6fa
14 changed files with 422 additions and 470 deletions

View File

@@ -1,6 +1,5 @@
import React, {lazy, Suspense, useCallback, useState} from "react";
import {BrowserRouter, Route, Routes} from "react-router-dom";
import Header from "./elements/header";
import Sidebar from "./elements/sidebar";
import Dialog from "./elements/dialog";
import Footer from "./elements/footer";
@@ -23,6 +22,7 @@ const AccessControlList = lazy(() => import("./views/access-control-list"));
const RouteListView = lazy(() => import("./views/route/route-list"));
const RouteEditView = lazy(() => import("./views/route/route-edit"));
const SettingsView = lazy(() => import("./views/settings"));
const ProfileView = lazy(() => import("./views/profile/profile"));
export default function AdminDashboard(props) {
@@ -67,7 +67,6 @@ export default function AdminDashboard(props) {
}
return <BrowserRouter>
<Header {...controlObj} />
<Sidebar {...controlObj} />
<div className={"wrapper"}>
<div className={"content-wrapper p-2"}>
@@ -85,6 +84,7 @@ export default function AdminDashboard(props) {
<Route path={"/admin/routes"} element={<RouteListView {...controlObj} />}/>
<Route path={"/admin/routes/:routeId"} element={<RouteEditView {...controlObj} />}/>
<Route path={"/admin/settings"} element={<SettingsView {...controlObj} />}/>
<Route path={"/admin/profile"} element={<ProfileView {...controlObj} />}/>
<Route path={"*"} element={<View404 />} />
</Routes>
</Suspense>

View File

@@ -2,6 +2,22 @@ import React, {useCallback, useContext} from 'react';
import {Link, NavLink} from "react-router-dom";
import Icon from "shared/elements/icon";
import {LocaleContext} from "shared/locale";
import {Avatar, styled} from "@mui/material";
const ProfileLink = styled(Link)((props) => ({
"& > div": {
padding: 3,
width: 30,
height: 30,
},
marginLeft: props.theme.spacing(1),
marginTop: props.theme.spacing(1),
display: "grid",
gridTemplateColumns: "45px auto",
"& > span": {
alignSelf: "center"
}
}));
export default function Sidebar(props) {
@@ -100,9 +116,11 @@ export default function Sidebar(props) {
<div className={"os-content"} style={{padding: "0px 0px", height: "100%", width: "100%"}}>
<div className="user-panel mt-3 pb-3 mb-3 d-flex">
<div className="info">
<span className={"d-block text-light"}>{L("account.logged_in_as")}:&nbsp;
<Link to={"/admin/user/" + api.user.id}>{api.user.name}</Link>
</span>
<div className={"d-block text-light"}>{L("account.logged_in_as")}:</div>
<ProfileLink to={"/admin/profile"}>
<Avatar fontSize={"small"} />
<span>{api.user?.name || L("account.not_logged_in")}</span>
</ProfileLink>
</div>
</div>
<nav className={"mt-2"}>

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,141 @@
import {Link} from "react-router-dom";
import React, {useCallback, useContext, useEffect, useState} from "react";
import {LocaleContext} from "shared/locale";
import {Button, CircularProgress, FormControl, FormGroup, FormLabel, TextField} from "@mui/material";
import {Save} from "@mui/icons-material";
export default function ProfileView(props) {
// meta
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
const api = props.api;
const showDialog = props.showDialog;
useEffect(() => {
requestModules(props.api, ["general", "account"], currentLocale).then(data => {
if (!data.success) {
props.showDialog(data.msg, "Error fetching localization");
}
});
}, [currentLocale]);
// data
const [profile, setProfile] = useState({...api.user});
const [changePassword, setChangePassword] = useState({ old: "", new: "", confirm: "" });
// ui
const [isSaving, setSaving] = useState(false);
const onUpdateProfile = useCallback(() => {
if (!isSaving) {
let newUsername = (profile.name !== api.user.name ? profile.name : null);
let newFullName = (profile.fullName !== api.user.fullName ? profile.fullName : null);
let oldPassword = null;
let newPassword = null;
let confirmPassword = null;
if (changePassword.new || changePassword.confirm) {
if (changePassword.new !== changePassword.confirm) {
showDialog(L("account.passwords_do_not_match"), L("general.error"));
return;
} else {
oldPassword = changePassword.old;
newPassword = changePassword.new;
confirmPassword = changePassword.confirm;
}
}
setSaving(true);
api.updateProfile(newUsername, newFullName, newPassword, confirmPassword, oldPassword).then(data => {
setSaving(false);
if (!data.success) {
showDialog(data.msg, L("account.update_profile_error"));
} else {
setChangePassword({old: "", new: "", confirm: ""});
}
});
}
}, [profile, changePassword, api, showDialog, isSaving]);
return <>
<div className={"content-header"}>
<div className={"container-fluid"}>
<div className={"row mb-2"}>
<div className={"col-sm-6"}>
<h1 className={"m-0 text-dark"}>
{L("account.edit_profile")}
</h1>
</div>
<div className={"col-sm-6"}>
<ol className={"breadcrumb float-sm-right"}>
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active">{L("account.profile")}</li>
</ol>
</div>
</div>
</div>
</div>
<div className={"content"}>
<FormGroup>
<FormLabel>{L("account.username")}</FormLabel>
<FormControl>
<TextField variant={"outlined"}
size={"small"}
value={profile.name}
onChange={e => setProfile({...profile, name: e.target.value })} />
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>{L("account.full_name")}</FormLabel>
<FormControl>
<TextField variant={"outlined"}
size={"small"}
value={profile.fullName ?? ""}
onChange={e => setProfile({...profile, fullName: e.target.value })} />
</FormControl>
</FormGroup>
<h4>{L("account.change_password")}</h4>
<FormGroup>
<FormLabel>{L("account.old_password")}</FormLabel>
<FormControl>
<TextField variant={"outlined"}
size={"small"}
type={"password"}
placeholder={L("general.unchanged")}
value={changePassword.old}
onChange={e => setChangePassword({...changePassword, old: e.target.value })} />
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>{L("account.new_password")}</FormLabel>
<FormControl>
<TextField variant={"outlined"}
size={"small"}
type={"password"}
value={changePassword.new}
onChange={e => setChangePassword({...changePassword, new: e.target.value })} />
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>{L("account.confirm_password")}</FormLabel>
<FormControl>
<TextField variant={"outlined"}
size={"small"}
type={"password"}
placeholder={L("general.unchanged")}
value={changePassword.confirm}
onChange={e => setChangePassword({...changePassword, confirm: e.target.value })} />
</FormControl>
</FormGroup>
<Button variant={"outlined"} color={"primary"}
disabled={isSaving || !api.hasPermission("user/updateProfile")}
startIcon={isSaving ? <CircularProgress size={12} /> : <Save />}
onClick={onUpdateProfile}>
{isSaving ? L("general.saving") + "…" : L("general.save")}
</Button>
</div>
</>
}

View File

@@ -189,6 +189,12 @@ export default function SettingsView(props) {
}
}, [api, showDialog, testMailAddress, isSending]);
const onReset = useCallback(() => {
setFetchSettings(true);
setNewKey("");
setChanged(false);
}, []);
if (settings === null) {
return <CircularProgress />
}
@@ -406,7 +412,7 @@ export default function SettingsView(props) {
{isSaving ? L("general.saving") + "…" : (L("general.save") + (hasChanged ? " *" : ""))}
</Button>
<Button color={"secondary"}
onClick={() => { setFetchSettings(true); setNewKey(""); }}
onClick={onReset}
disabled={isSaving}
startIcon={<RestartAlt />}
variant={"outlined"} title={L("general.reset")}>