mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Account editing and change password.
This commit is contained in:
parent
b5cf9f8719
commit
b392a51676
23 changed files with 230 additions and 102 deletions
|
|
@ -1,16 +1,19 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PageHeader from './layout/PageHeader';
|
||||
import Button from './common/Button';
|
||||
import Table from './common/Table';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Button from 'components/common/Button';
|
||||
import Icon from 'components/common/Icon';
|
||||
import Table from 'components/common/Table';
|
||||
import Modal from 'components/common/Modal';
|
||||
import WebsiteEditForm from 'components/forms/WebsiteEditForm';
|
||||
import AccountEditForm from 'components/forms/AccountEditForm';
|
||||
import Pen from 'assets/pen.svg';
|
||||
import Plus from 'assets/plus.svg';
|
||||
import Trash from 'assets/trash.svg';
|
||||
import Check from 'assets/check.svg';
|
||||
import { get } from 'lib/web';
|
||||
import styles from './AccountSettings.module.css';
|
||||
import Modal from './common/Modal';
|
||||
import WebsiteEditForm from './forms/WebsiteEditForm';
|
||||
import AccountEditForm from './forms/AccountEditForm';
|
||||
import DeleteForm from './forms/DeleteForm';
|
||||
|
||||
export default function AccountSettings() {
|
||||
const user = useSelector(state => state.user);
|
||||
|
|
@ -23,16 +26,23 @@ export default function AccountSettings() {
|
|||
const columns = [
|
||||
{ key: 'username', label: 'Username' },
|
||||
{
|
||||
render: row => (
|
||||
<>
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditAccount(row)}>
|
||||
<div>Edit</div>
|
||||
</Button>
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteAccount(row)}>
|
||||
<div>Delete</div>
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
key: 'is_admin',
|
||||
label: 'Administrator',
|
||||
render: ({ is_admin }) => (is_admin ? <Icon icon={<Check />} size="medium" /> : null),
|
||||
},
|
||||
{
|
||||
className: styles.buttons,
|
||||
render: row =>
|
||||
row.username !== 'admin' ? (
|
||||
<>
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditAccount(row)}>
|
||||
<div>Edit</div>
|
||||
</Button>
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteAccount(row)}>
|
||||
<div>Delete</div>
|
||||
</Button>
|
||||
</>
|
||||
) : null,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -70,7 +80,11 @@ export default function AccountSettings() {
|
|||
<Table columns={columns} rows={data} />
|
||||
{editAccount && (
|
||||
<Modal title="Edit account">
|
||||
<AccountEditForm values={editAccount} onSave={handleSave} onClose={handleClose} />
|
||||
<AccountEditForm
|
||||
values={{ ...editAccount, password: '' }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{addAccount && (
|
||||
|
|
@ -78,6 +92,15 @@ export default function AccountSettings() {
|
|||
<AccountEditForm onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{deleteAccount && (
|
||||
<Modal title="Delete account">
|
||||
<DeleteForm
|
||||
values={{ type: 'account', id: deleteAccount.user_id, name: deleteAccount.username }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.label {
|
||||
font-size: var(--font-size-normal);
|
||||
font-weight: 600;
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import Modal from './common/Modal';
|
|||
export default function ProfileSettings() {
|
||||
const user = useSelector(state => state.user);
|
||||
const [changePassword, setChangePassword] = useState(false);
|
||||
const { user_id } = user;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -17,11 +18,17 @@ export default function ProfileSettings() {
|
|||
Change password
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<dt>Username</dt>
|
||||
<dd>{user.username}</dd>
|
||||
<dl>
|
||||
<dt>Username</dt>
|
||||
<dd>{user.username}</dd>
|
||||
</dl>
|
||||
{changePassword && (
|
||||
<Modal title="Change password">
|
||||
<ChangePasswordForm values={user} onClose={() => setChangePassword(false)} />
|
||||
<ChangePasswordForm
|
||||
values={{ user_id }}
|
||||
onSave={() => setChangePassword(false)}
|
||||
onClose={() => setChangePassword(false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import MenuLayout from 'components/layout/MenuLayout';
|
|||
import WebsiteSettings from './WebsiteSettings';
|
||||
import AccountSettings from './AccountSettings';
|
||||
import ProfileSettings from './ProfileSettings';
|
||||
|
||||
const menuOptions = ['Websites', 'Accounts', 'Profile'];
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
export default function Settings() {
|
||||
const user = useSelector(state => state.user);
|
||||
|
||||
const menuOptions = ['Websites', user.is_admin && 'Accounts', 'Profile'];
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<MenuLayout menu={menuOptions} selectedOption="Websites">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Code from 'assets/code.svg';
|
|||
import { get } from 'lib/web';
|
||||
import Modal from './common/Modal';
|
||||
import WebsiteEditForm from './forms/WebsiteEditForm';
|
||||
import WebsiteDeleteForm from './forms/WebsiteDeleteForm';
|
||||
import DeleteForm from './forms/DeleteForm';
|
||||
import WebsiteCodeForm from './forms/WebsiteCodeForm';
|
||||
import styles from './WebsiteSettings.module.css';
|
||||
|
||||
|
|
@ -88,7 +88,11 @@ export default function WebsiteSettings() {
|
|||
)}
|
||||
{deleteWebsite && (
|
||||
<Modal title="Delete website">
|
||||
<WebsiteDeleteForm values={deleteWebsite} onSave={handleSave} onClose={handleClose} />
|
||||
<DeleteForm
|
||||
values={{ type: 'website', id: deleteWebsite.website_id, name: deleteWebsite.name }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{showCode && (
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ const initialValues = {
|
|||
password: '',
|
||||
};
|
||||
|
||||
const validate = ({ username, password }) => {
|
||||
const validate = ({ user_id, username, password }) => {
|
||||
const errors = {};
|
||||
|
||||
if (!username) {
|
||||
errors.username = 'Required';
|
||||
}
|
||||
if (!password) {
|
||||
if (!user_id && !password) {
|
||||
errors.password = 'Required';
|
||||
}
|
||||
|
||||
|
|
@ -33,10 +33,10 @@ export default function AccountEditForm({ values, onSave, onClose }) {
|
|||
const handleSubmit = async values => {
|
||||
const response = await post(`/api/account`, values);
|
||||
|
||||
if (response) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage('Something went wrong.');
|
||||
setMessage(response || 'Something went wrong');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ export default function AccountEditForm({ values, onSave, onClose }) {
|
|||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="password">Password</label>
|
||||
<Field name="password" type="text" />
|
||||
<Field name="password" type="password" />
|
||||
<FormError name="password" />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
|
|
|
|||
|
|
@ -10,24 +10,24 @@ import FormLayout, {
|
|||
} from 'components/layout/FormLayout';
|
||||
|
||||
const initialValues = {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
defaultPassword: '',
|
||||
current_password: '',
|
||||
new_password: '',
|
||||
confirm_password: '',
|
||||
};
|
||||
|
||||
const validate = ({ password, newPassword, confirmPassword }) => {
|
||||
const validate = ({ current_password, new_password, confirm_password }) => {
|
||||
const errors = {};
|
||||
|
||||
if (!password) {
|
||||
errors.password = 'Required';
|
||||
if (!current_password) {
|
||||
errors.current_password = 'Required';
|
||||
}
|
||||
if (!newPassword) {
|
||||
errors.newPassword = 'Required';
|
||||
if (!new_password) {
|
||||
errors.new_password = 'Required';
|
||||
}
|
||||
if (!confirmPassword) {
|
||||
errors.confirmPassword = 'Required';
|
||||
} else if (newPassword !== confirmPassword) {
|
||||
errors.confirmPassword = `Passwords don't match`;
|
||||
if (!confirm_password) {
|
||||
errors.confirm_password = 'Required';
|
||||
} else if (new_password !== confirm_password) {
|
||||
errors.confirm_password = `Passwords don't match`;
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
|
@ -37,12 +37,12 @@ export default function ChangePasswordForm({ values, onSave, onClose }) {
|
|||
const [message, setMessage] = useState();
|
||||
|
||||
const handleSubmit = async values => {
|
||||
const response = await post(`/api/website`, values);
|
||||
const response = await post(`/api/account/password`, values);
|
||||
|
||||
if (response) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage('Something went wrong.');
|
||||
setMessage(response || 'Something went wrong');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -56,19 +56,19 @@ export default function ChangePasswordForm({ values, onSave, onClose }) {
|
|||
{() => (
|
||||
<Form>
|
||||
<FormRow>
|
||||
<label htmlFor="password">Current password</label>
|
||||
<Field name="password" type="password" />
|
||||
<FormError name="password" />
|
||||
<label htmlFor="current_password">Current password</label>
|
||||
<Field name="current_password" type="password" />
|
||||
<FormError name="current_password" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="newPassword">New password</label>
|
||||
<Field name="newPassword" type="password" />
|
||||
<FormError name="newPassword" />
|
||||
<label htmlFor="new_password">New password</label>
|
||||
<Field name="new_password" type="password" />
|
||||
<FormError name="new_password" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="confirmPassword">Confirm password</label>
|
||||
<Field name="confirmPassword" type="password" />
|
||||
<FormError name="confirmPassword" />
|
||||
<label htmlFor="confirm_password">Confirm password</label>
|
||||
<Field name="confirm_password" type="password" />
|
||||
<FormError name="confirm_password" />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="action">
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ const validate = ({ confirmation }) => {
|
|||
return errors;
|
||||
};
|
||||
|
||||
export default function WebsiteDeleteForm({ values, onSave, onClose }) {
|
||||
export default function DeleteForm({ values, onSave, onClose }) {
|
||||
const [message, setMessage] = useState();
|
||||
|
||||
const handleSubmit = async ({ website_id }) => {
|
||||
const response = await del(`/api/website/${website_id}`);
|
||||
const handleSubmit = async ({ type, id }) => {
|
||||
const response = await del(`/api/${type}/${id}`);
|
||||
|
||||
if (response) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage('Something went wrong.');
|
||||
setMessage('Something went wrong');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ export default function LoginForm() {
|
|||
if (response?.token) {
|
||||
await Router.push('/');
|
||||
} else {
|
||||
setMessage('Incorrect username/password.');
|
||||
setMessage('Incorrect username/password');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
|
|||
const handleSubmit = async values => {
|
||||
const response = await post(`/api/website`, values);
|
||||
|
||||
if (response) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage('Something went wrong.');
|
||||
setMessage('Something went wrong');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const ErrorTag = ({ msg }) => {
|
|||
|
||||
return (
|
||||
<animated.div className={styles.error} style={props}>
|
||||
{msg}
|
||||
<div className={styles.msg}>{msg}</div>
|
||||
</animated.div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@
|
|||
}
|
||||
|
||||
.error {
|
||||
color: var(--gray50);
|
||||
background: var(--red400);
|
||||
font-size: var(--font-size-small);
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
@ -35,7 +32,13 @@
|
|||
left: 100%;
|
||||
bottom: 0;
|
||||
margin-left: 16px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.msg {
|
||||
color: var(--gray50);
|
||||
background: var(--red400);
|
||||
font-size: var(--font-size-small);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@ export default function MenuLayout({ menu, selectedOption, onMenuSelect, childre
|
|||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.menu}>
|
||||
{menu.map(item => (
|
||||
<div
|
||||
className={classNames(styles.option, { [styles.active]: option === item })}
|
||||
onClick={() => setOption(item)}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
{menu.map(item =>
|
||||
item ? (
|
||||
<div
|
||||
className={classNames(styles.option, { [styles.active]: option === item })}
|
||||
onClick={() => setOption(item)}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
{typeof children === 'function' ? children(option) : children}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
}
|
||||
|
||||
.option {
|
||||
padding: 8px 20px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
min-width: 140px;
|
||||
margin-right: 30px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue