Session handling bugfix, profile frontend WIP
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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")}:
|
||||
<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"}>
|
||||
|
||||
1
react/admin-panel/src/res/select2.min.css
vendored
1
react/admin-panel/src/res/select2.min.css
vendored
File diff suppressed because one or more lines are too long
141
react/admin-panel/src/views/profile/profile.js
Normal file
141
react/admin-panel/src/views/profile/profile.js
Normal 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>
|
||||
</>
|
||||
}
|
||||
@@ -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")}>
|
||||
|
||||
Reference in New Issue
Block a user