mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-14 21:10:34 +01:00
Refactored user components.
This commit is contained in:
parent
91af593ff5
commit
cdd54df8f6
@ -9,6 +9,7 @@ import Logo from 'assets/logo.svg';
|
|||||||
import Moon from 'assets/moon.svg';
|
import Moon from 'assets/moon.svg';
|
||||||
import Profile from 'assets/profile.svg';
|
import Profile from 'assets/profile.svg';
|
||||||
import Sun from 'assets/sun.svg';
|
import Sun from 'assets/sun.svg';
|
||||||
|
import Trash from 'assets/trash.svg';
|
||||||
import User from 'assets/user.svg';
|
import User from 'assets/user.svg';
|
||||||
import Users from 'assets/users.svg';
|
import Users from 'assets/users.svg';
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ export {
|
|||||||
Moon,
|
Moon,
|
||||||
Profile,
|
Profile,
|
||||||
Sun,
|
Sun,
|
||||||
|
Trash,
|
||||||
User,
|
User,
|
||||||
Users,
|
Users,
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,14 @@ import { defineMessages } from 'react-intl';
|
|||||||
|
|
||||||
export const labels = defineMessages({
|
export const labels = defineMessages({
|
||||||
unknown: { id: 'label.unknown', defaultMessage: 'Unknown' },
|
unknown: { id: 'label.unknown', defaultMessage: 'Unknown' },
|
||||||
|
required: { id: 'label.required', defaultMessage: 'Required' },
|
||||||
|
save: { id: 'label.save', defaultMessage: 'Save' },
|
||||||
|
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
error: { id: 'message.error', defaultMessage: 'Something went wrong.' },
|
||||||
|
saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const devices = defineMessages({
|
export const devices = defineMessages({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Button, Icon, Modal, useToast } from 'react-basics';
|
import { Button, Icon, Text, Modal, useToast } from 'react-basics';
|
||||||
import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm';
|
import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm';
|
||||||
import { Lock } from 'components/icons';
|
import { Lock } from 'components/icons';
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ const messages = defineMessages({
|
|||||||
saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
|
saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function ChangePasswordButton() {
|
export default function PasswordChangeButton() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [edit, setEdit] = useState(false);
|
const [edit, setEdit] = useState(false);
|
||||||
const { toast, showToast } = useToast();
|
const { toast, showToast } = useToast();
|
||||||
@ -19,7 +19,7 @@ export default function ChangePasswordButton() {
|
|||||||
setEdit(false);
|
setEdit(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleEdit = () => {
|
||||||
setEdit(true);
|
setEdit(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,11 +30,11 @@ export default function ChangePasswordButton() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{toast}
|
{toast}
|
||||||
<Button onClick={handleAdd}>
|
<Button onClick={handleEdit}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Lock />
|
<Lock />
|
||||||
</Icon>
|
</Icon>
|
||||||
Change Password
|
<Text>{formatMessage(messages.changePassword)}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
{edit && (
|
{edit && (
|
||||||
<Modal title={formatMessage(messages.changePassword)} onClose={handleClose}>
|
<Modal title={formatMessage(messages.changePassword)} onClose={handleClose}>
|
@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import PageHeader from 'components/layout/PageHeader';
|
import PageHeader from 'components/layout/PageHeader';
|
||||||
import ProfileDetails from './ProfileDetails';
|
import ProfileDetails from './ProfileDetails';
|
||||||
import ChangePasswordButton from './ChangePasswordButton';
|
import PasswordChangeButton from './PasswordChangeButton';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
profile: { id: 'label.profile', defaultMessage: 'Profile' },
|
profile: { id: 'label.profile', defaultMessage: 'Profile' },
|
||||||
@ -18,7 +18,7 @@ export default function ProfileSettings() {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Item>{formatMessage(messages.profile)}</Item>
|
<Item>{formatMessage(messages.profile)}</Item>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<ChangePasswordButton />
|
<PasswordChangeButton />
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<ProfileDetails />
|
<ProfileDetails />
|
||||||
</Page>
|
</Page>
|
||||||
|
47
components/pages/settings/users/UserAddButton.js
Normal file
47
components/pages/settings/users/UserAddButton.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Button, Icon, Text, Modal, useToast, Icons } from 'react-basics';
|
||||||
|
import UserAddForm from './UserAddForm';
|
||||||
|
|
||||||
|
const { Plus } = Icons;
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
createUser: { id: 'label.create-user', defaultMessage: 'Create user' },
|
||||||
|
saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function UserAddButton() {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const [edit, setEdit] = useState(false);
|
||||||
|
const { toast, showToast } = useToast();
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||||
|
setEdit(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setEdit(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setEdit(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{toast}
|
||||||
|
<Button variant="primary" onClick={handleAdd}>
|
||||||
|
<Icon>
|
||||||
|
<Plus />
|
||||||
|
</Icon>
|
||||||
|
<Text>{formatMessage(messages.createUser)}</Text>
|
||||||
|
</Button>
|
||||||
|
{edit && (
|
||||||
|
<Modal title={formatMessage(messages.createUser)} onClose={handleClose}>
|
||||||
|
{() => <UserAddForm onSave={handleSave} onClose={handleClose} />}
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -8,10 +8,12 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
PasswordField,
|
PasswordField,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
|
Button,
|
||||||
} from 'react-basics';
|
} from 'react-basics';
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
|
import { labels } from 'components/messages';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
username: { id: 'label.username', defaultMessage: 'Username' },
|
username: { id: 'label.username', defaultMessage: 'Username' },
|
||||||
@ -19,14 +21,11 @@ const messages = defineMessages({
|
|||||||
role: { id: 'label.role', defaultMessage: 'Role' },
|
role: { id: 'label.role', defaultMessage: 'Role' },
|
||||||
user: { id: 'label.user', defaultMessage: 'User' },
|
user: { id: 'label.user', defaultMessage: 'User' },
|
||||||
admin: { id: 'label.admin', defaultMessage: 'Admin' },
|
admin: { id: 'label.admin', defaultMessage: 'Admin' },
|
||||||
save: { id: 'label.save', defaultMessage: 'Save' },
|
|
||||||
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
|
|
||||||
required: { id: 'label.required', defaultMessage: 'Required' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function UserAddForm({ onSave }) {
|
export default function UserAddForm({ onSave, onClose }) {
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(data => post(`/users`, data));
|
const { mutate, error, isLoading } = useMutation(data => post(`/users`, data));
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async data => {
|
||||||
@ -37,28 +36,42 @@ export default function UserAddForm({ onSave }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderValue = value => {
|
||||||
|
if (value === ROLES.user) {
|
||||||
|
return formatMessage(messages.user);
|
||||||
|
}
|
||||||
|
if (value === ROLES.admin) {
|
||||||
|
return formatMessage(messages.admin);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error}>
|
||||||
<FormRow label={formatMessage(messages.username)}>
|
<FormRow label={formatMessage(messages.username)}>
|
||||||
<FormInput name="username" rules={{ required: formatMessage(messages.required) }}>
|
<FormInput name="username" rules={{ required: formatMessage(labels.required) }}>
|
||||||
<TextField />
|
<TextField autoComplete="new-username" />
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow label={formatMessage(messages.password)}>
|
<FormRow label={formatMessage(messages.password)}>
|
||||||
<FormInput name="password" rules={{ required: formatMessage(messages.required) }}>
|
<FormInput name="password" rules={{ required: formatMessage(labels.required) }}>
|
||||||
<PasswordField />
|
<PasswordField autoComplete="new-password" />
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow label={formatMessage(messages.role)}>
|
<FormRow label={formatMessage(messages.role)}>
|
||||||
<FormInput name="role" rules={{ required: formatMessage(messages.required) }}>
|
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
|
||||||
<Dropdown style={{ width: 200 }}>
|
<Dropdown renderValue={renderValue} style={{ width: 200 }}>
|
||||||
<Item key={ROLES.user}>{formatMessage(messages.user)}</Item>
|
<Item key={ROLES.user}>{formatMessage(messages.user)}</Item>
|
||||||
<Item key={ROLES.admin}>{formatMessage(messages.admin)}</Item>
|
<Item key={ROLES.admin}>{formatMessage(messages.admin)}</Item>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormButtons>
|
<FormButtons flex>
|
||||||
<SubmitButton variant="primary">{formatMessage(messages.save)}</SubmitButton>
|
<SubmitButton variant="primary" disabled={false}>
|
||||||
|
{formatMessage(labels.save)}
|
||||||
|
</SubmitButton>
|
||||||
|
<Button disabled={isLoading} onClick={onClose}>
|
||||||
|
{formatMessage(labels.cancel)}
|
||||||
|
</Button>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import UserDeleteForm from 'components/pages/settings/users/UserDeleteForm';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Button, Form, FormRow, Modal } from 'react-basics';
|
|
||||||
|
|
||||||
export default function UserDelete({ userId, onSave }) {
|
|
||||||
const [modal, setModal] = useState(null);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
onSave();
|
|
||||||
await router.push('/users');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => setModal(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<FormRow label="Delete user">
|
|
||||||
<p>All user data will be deleted.</p>
|
|
||||||
<Button onClick={() => setModal('delete')}>Delete</Button>
|
|
||||||
</FormRow>
|
|
||||||
{modal === 'delete' && (
|
|
||||||
<Modal title="Delete user" onClose={handleClose}>
|
|
||||||
{close => <UserDeleteForm userId={userId} onSave={handleDelete} onClose={close} />}
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,18 +1,19 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import {
|
import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
|
||||||
Button,
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
Form,
|
import { labels } from 'components/messages';
|
||||||
FormRow,
|
|
||||||
FormButtons,
|
|
||||||
FormInput,
|
|
||||||
SubmitButton,
|
|
||||||
TextField,
|
|
||||||
} from 'react-basics';
|
|
||||||
|
|
||||||
const CONFIRM_VALUE = 'DELETE';
|
const messages = defineMessages({
|
||||||
|
confirm: { id: 'label.confirm', defaultMessage: 'Confirm' },
|
||||||
|
warning: {
|
||||||
|
id: 'message.confirm-delete-user',
|
||||||
|
defaultMessage: 'Are you sure you want to delete this user?',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default function UserDeleteForm({ userId, onSave, onClose }) {
|
export default function UserDeleteForm({ userId, onSave, onClose }) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
const { del } = useApi();
|
const { del } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => del(`/users/${userId}`, data));
|
const { mutate, error, isLoading } = useMutation(data => del(`/users/${userId}`, data));
|
||||||
|
|
||||||
@ -26,20 +27,13 @@ export default function UserDeleteForm({ userId, onSave, onClose }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error}>
|
||||||
<p>
|
<p>{formatMessage(messages.warning)}</p>
|
||||||
To delete this user, type <b>{CONFIRM_VALUE}</b> in the box below to confirm.
|
|
||||||
</p>
|
|
||||||
<FormRow label="Confirm">
|
|
||||||
<FormInput name="confirmation" rules={{ validate: value => value === CONFIRM_VALUE }}>
|
|
||||||
<TextField autoComplete="off" />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormButtons flex>
|
<FormButtons flex>
|
||||||
<SubmitButton variant="primary" disabled={isLoading}>
|
<SubmitButton variant="primary" disabled={isLoading}>
|
||||||
Save
|
{formatMessage(labels.save)}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
<Button disabled={isLoading} onClick={onClose}>
|
<Button disabled={isLoading} onClick={onClose}>
|
||||||
Cancel
|
{formatMessage(labels.cancel)}
|
||||||
</Button>
|
</Button>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -7,53 +7,75 @@ import {
|
|||||||
FormInput,
|
FormInput,
|
||||||
TextField,
|
TextField,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
|
PasswordField,
|
||||||
} from 'react-basics';
|
} from 'react-basics';
|
||||||
import { useRef } from 'react';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
|
import { labels } from 'components/messages';
|
||||||
|
|
||||||
const items = [
|
const messages = defineMessages({
|
||||||
{
|
username: { id: 'label.username', defaultMessage: 'Username' },
|
||||||
value: ROLES.user,
|
|
||||||
label: 'User',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ROLES.admin,
|
|
||||||
label: 'Admin',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function UserEditForm({ data, onSave }) {
|
password: { id: 'label.password', defaultMessage: 'Password' },
|
||||||
const { id } = data;
|
role: { id: 'label.role', defaultMessage: 'Role' },
|
||||||
|
user: { id: 'label.user', defaultMessage: 'User' },
|
||||||
|
admin: { id: 'label.admin', defaultMessage: 'Admin' },
|
||||||
|
minLength: {
|
||||||
|
id: 'message.min-password-length',
|
||||||
|
defaultMessage: 'Minimum length of 8 characters',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function UserEditForm({ userId, data, onSave }) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(({ username }) => post(`/user/${id}`, { username }));
|
const { mutate, error } = useMutation(({ username }) => post(`/users/${userId}`, { username }));
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async data => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
onSave(data);
|
onSave(data);
|
||||||
ref.current.reset(data);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderValue = value => {
|
||||||
|
if (value === ROLES.user) {
|
||||||
|
return formatMessage(messages.user);
|
||||||
|
}
|
||||||
|
if (value === ROLES.admin) {
|
||||||
|
return formatMessage(messages.admin);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form key={id} ref={ref} onSubmit={handleSubmit} error={error} values={data}>
|
<Form onSubmit={handleSubmit} error={error} values={data} style={{ width: 600 }}>
|
||||||
<FormRow label="Username">
|
<FormRow label={formatMessage(messages.username)}>
|
||||||
<FormInput name="username">
|
<FormInput name="username">
|
||||||
<TextField />
|
<TextField />
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow label="Role">
|
<FormRow label={formatMessage(messages.password)}>
|
||||||
<FormInput name="role">
|
<FormInput
|
||||||
<Dropdown items={items} style={{ width: 200 }}>
|
name="newPassword"
|
||||||
{({ value, label }) => <Item key={value}>{label}</Item>}
|
rules={{
|
||||||
|
minLength: { value: 8, message: formatMessage(messages.minLength) },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PasswordField autoComplete="new-password" />
|
||||||
|
</FormInput>
|
||||||
|
</FormRow>
|
||||||
|
<FormRow label={formatMessage(messages.role)}>
|
||||||
|
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
|
||||||
|
<Dropdown renderValue={renderValue} style={{ width: 200 }}>
|
||||||
|
<Item key={ROLES.user}>{formatMessage(messages.user)}</Item>
|
||||||
|
<Item key={ROLES.admin}>{formatMessage(messages.admin)}</Item>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton variant="primary">Save</SubmitButton>
|
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 30px;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
import { useRef } from 'react';
|
|
||||||
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
|
|
||||||
import useApi from 'hooks/useApi';
|
|
||||||
import useUser from 'hooks/useUser';
|
|
||||||
|
|
||||||
export default function UserPasswordForm({ onSave, onClose, userId }) {
|
|
||||||
const user = useUser();
|
|
||||||
const isCurrentUser = !userId || user?.id === userId;
|
|
||||||
const url = isCurrentUser ? `/users/${user?.id}/password` : `/users/${user?.id}`;
|
|
||||||
const { post, useMutation } = useApi();
|
|
||||||
const { mutate, error, isLoading } = useMutation(data => post(url, data));
|
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
|
||||||
const payload = isCurrentUser
|
|
||||||
? data
|
|
||||||
: {
|
|
||||||
password: data.newPassword,
|
|
||||||
};
|
|
||||||
|
|
||||||
mutate(payload, {
|
|
||||||
onSuccess: async () => {
|
|
||||||
onSave();
|
|
||||||
ref.current.reset();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const samePassword = value => {
|
|
||||||
if (value !== ref?.current?.getValues('newPassword')) {
|
|
||||||
return "Passwords don't match";
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form ref={ref} onSubmit={handleSubmit} error={error}>
|
|
||||||
{isCurrentUser && (
|
|
||||||
<FormRow label="Current password">
|
|
||||||
<FormInput name="currentPassword" rules={{ required: 'Required' }}>
|
|
||||||
<PasswordField autoComplete="off" />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
)}
|
|
||||||
<FormRow label="New password">
|
|
||||||
<FormInput
|
|
||||||
name="newPassword"
|
|
||||||
rules={{
|
|
||||||
required: 'Required',
|
|
||||||
minLength: { value: 8, message: 'Minimum length 8 characters' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PasswordField autoComplete="off" />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow label="Confirm password">
|
|
||||||
<FormInput
|
|
||||||
name="confirmPassword"
|
|
||||||
rules={{
|
|
||||||
required: 'Required',
|
|
||||||
minLength: { value: 8, message: 'Minimum length 8 characters' },
|
|
||||||
validate: samePassword,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PasswordField autoComplete="off" />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormButtons flex>
|
|
||||||
<Button type="submit" variant="primary" disabled={isLoading}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onClose}>Close</Button>
|
|
||||||
</FormButtons>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -3,17 +3,17 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics';
|
import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import UserDelete from 'components/pages/settings/users/UserDelete';
|
import UserDeleteForm from 'components/pages/settings/users/UserDeleteForm';
|
||||||
import UserEditForm from 'components/pages/settings/users//UserEditForm';
|
import UserEditForm from 'components/pages/settings/users//UserEditForm';
|
||||||
import UserPasswordForm from 'components/pages/settings/users/UserPasswordForm';
|
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import PageHeader from 'components/layout/PageHeader';
|
import PageHeader from 'components/layout/PageHeader';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
|
import WebsitesTable from '../websites/WebsitesTable';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
users: { id: 'label.users', defaultMessage: 'Users' },
|
users: { id: 'label.users', defaultMessage: 'Users' },
|
||||||
details: { id: 'label.details', defaultMessage: 'Details' },
|
details: { id: 'label.details', defaultMessage: 'Details' },
|
||||||
changePassword: { id: 'label.change-password', defaultMessage: 'Change password' },
|
websites: { id: 'label.websites', defaultMessage: 'Websites' },
|
||||||
actions: { id: 'label.actions', defaultMessage: 'Actions' },
|
actions: { id: 'label.actions', defaultMessage: 'Actions' },
|
||||||
saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
|
saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
|
||||||
delete: { id: 'message.delete-successfully', defaultMessage: 'Delete successfully.' },
|
delete: { id: 'message.delete-successfully', defaultMessage: 'Delete successfully.' },
|
||||||
@ -38,7 +38,7 @@ export default function UserSettings({ userId }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSave = data => {
|
const handleSave = data => {
|
||||||
showToast({ message: 'Saved successfully.', variant: 'success' });
|
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||||
if (data) {
|
if (data) {
|
||||||
setValues(state => ({ ...state, ...data }));
|
setValues(state => ({ ...state, ...data }));
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export default function UserSettings({ userId }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
showToast({ message: 'Deleted successfully.', variant: 'danger' });
|
showToast({ message: formatMessage(messages.delete), variant: 'danger' });
|
||||||
await router.push('/users');
|
await router.push('/users');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,12 +72,12 @@ export default function UserSettings({ userId }) {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
|
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
|
||||||
<Item key="details">{formatMessage(messages.details)}</Item>
|
<Item key="details">{formatMessage(messages.details)}</Item>
|
||||||
<Item key="password">{formatMessage(messages.changePassword)}</Item>
|
<Item key="websites">{formatMessage(messages.websites)}</Item>
|
||||||
<Item key="delete">{formatMessage(messages.actions)}</Item>
|
<Item key="delete">{formatMessage(messages.actions)}</Item>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{tab === 'details' && <UserEditForm userId={userId} data={values} onSave={handleSave} />}
|
{tab === 'details' && <UserEditForm userId={userId} data={values} onSave={handleSave} />}
|
||||||
{tab === 'password' && <UserPasswordForm userId={userId} onSave={handleSave} />}
|
{tab === 'websites' && <WebsitesTable />}
|
||||||
{tab === 'delete' && <UserDelete userId={userId} onSave={handleDelete} />}
|
{tab === 'delete' && <UserDeleteForm userId={userId} onSave={handleDelete} />}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { Button, Text, Icon, useToast, Icons, Modal } from 'react-basics';
|
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import PageHeader from 'components/layout/PageHeader';
|
import PageHeader from 'components/layout/PageHeader';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import UsersTable from 'components/pages/settings/users/UsersTable';
|
import UsersTable from './UsersTable';
|
||||||
import UserEditForm from 'components/pages/settings/users/UserEditForm';
|
import UserAddButton from './UserAddButton';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useUser from 'hooks/useUser';
|
import useUser from 'hooks/useUser';
|
||||||
|
|
||||||
const { Plus } = Icons;
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
saved: { id: 'messages.api-key-saved', defaultMessage: 'API key saved.' },
|
|
||||||
noUsers: {
|
noUsers: {
|
||||||
id: 'messages.no-useres',
|
id: 'messages.no-users',
|
||||||
defaultMessage: "You don't have any users.",
|
defaultMessage: "You don't have any users.",
|
||||||
},
|
},
|
||||||
users: { id: 'label.users', defaultMessage: 'Users' },
|
users: { id: 'label.users', defaultMessage: 'Users' },
|
||||||
@ -22,48 +17,23 @@ const messages = defineMessages({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default function UsersList() {
|
export default function UsersList() {
|
||||||
const [edit, setEdit] = useState(false);
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { toast, showToast } = useToast();
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { data, isLoading, error, refetch } = useQuery(['user'], () => get(`/users`), {
|
const { data, isLoading, error } = useQuery(['user'], () => get(`/users`), {
|
||||||
enabled: !!user,
|
enabled: !!user,
|
||||||
});
|
});
|
||||||
const hasData = data && data.length !== 0;
|
const hasData = data && data.length !== 0;
|
||||||
|
|
||||||
const handleSave = async () => {
|
const addButton = <UserAddButton />;
|
||||||
await refetch();
|
|
||||||
setEdit(false);
|
|
||||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAdd = () => setEdit(true);
|
|
||||||
|
|
||||||
const handleClose = () => setEdit(false);
|
|
||||||
|
|
||||||
const addButton = (
|
|
||||||
<Button variant="primary" onClick={handleAdd}>
|
|
||||||
<Icon>
|
|
||||||
<Plus />
|
|
||||||
</Icon>
|
|
||||||
<Text>{formatMessage(messages.createUser)}</Text>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page loading={isLoading} error={error}>
|
<Page loading={isLoading} error={error}>
|
||||||
{toast}
|
|
||||||
<PageHeader title={formatMessage(messages.users)}>{addButton}</PageHeader>
|
<PageHeader title={formatMessage(messages.users)}>{addButton}</PageHeader>
|
||||||
{hasData && <UsersTable data={data} />}
|
{hasData && <UsersTable data={data} />}
|
||||||
{!hasData && (
|
{!hasData && (
|
||||||
<EmptyPlaceholder message={formatMessage(messages.noUsers)}>{addButton}</EmptyPlaceholder>
|
<EmptyPlaceholder message={formatMessage(messages.noUsers)}>{addButton}</EmptyPlaceholder>
|
||||||
)}
|
)}
|
||||||
{edit && (
|
|
||||||
<Modal title={formatMessage(messages.createUser)} onClose={handleClose}>
|
|
||||||
{close => <UserEditForm onSave={handleSave} onClose={close} />}
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useRef } from 'react';
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormRow,
|
FormRow,
|
||||||
@ -8,13 +7,19 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
} from 'react-basics';
|
} from 'react-basics';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import { DOMAIN_REGEX } from 'lib/constants';
|
import { DOMAIN_REGEX } from 'lib/constants';
|
||||||
|
import { labels } from 'components/messages';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
invalidDomain: { id: 'label.invalid-domain', defaultMessage: 'Invalid domain' },
|
||||||
|
});
|
||||||
|
|
||||||
export default function WebsiteAddForm({ onSave, onClose }) {
|
export default function WebsiteAddForm({ onSave, onClose }) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
|
const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async data => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
@ -25,9 +30,9 @@ export default function WebsiteAddForm({ onSave, onClose }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form ref={ref} onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error}>
|
||||||
<FormRow label="Name">
|
<FormRow label="Name">
|
||||||
<FormInput name="name" rules={{ required: 'Required' }}>
|
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="off" />
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
@ -35,8 +40,8 @@ export default function WebsiteAddForm({ onSave, onClose }) {
|
|||||||
<FormInput
|
<FormInput
|
||||||
name="domain"
|
name="domain"
|
||||||
rules={{
|
rules={{
|
||||||
required: 'Required',
|
required: formatMessage(labels.required),
|
||||||
pattern: { value: DOMAIN_REGEX, message: 'Invalid domain' },
|
pattern: { value: DOMAIN_REGEX, message: formatMessage(messages.invalidDomain) },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="off" />
|
||||||
@ -44,10 +49,10 @@ export default function WebsiteAddForm({ onSave, onClose }) {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
<FormButtons flex>
|
<FormButtons flex>
|
||||||
<SubmitButton variant="primary" disabled={false}>
|
<SubmitButton variant="primary" disabled={false}>
|
||||||
Save
|
{formatMessage(labels.save)}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
<Button disabled={isLoading} onClick={onClose}>
|
<Button disabled={isLoading} onClick={onClose}>
|
||||||
Cancel
|
{formatMessage(labels.cancel)}
|
||||||
</Button>
|
</Button>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-basics": "^0.53.0",
|
"react-basics": "^0.55.0",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-intl": "^5.24.7",
|
"react-intl": "^5.24.7",
|
||||||
|
@ -6562,10 +6562,10 @@ rc@^1.2.7:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
strip-json-comments "~2.0.1"
|
||||||
|
|
||||||
react-basics@^0.53.0:
|
react-basics@^0.55.0:
|
||||||
version "0.53.0"
|
version "0.55.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.53.0.tgz#2215ca47bca99a316cd129875682960d6519611f"
|
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.55.0.tgz#b9c3dbba33a3ce118e63322ddcb822139a3ea547"
|
||||||
integrity sha512-XsG38JsumhHvqGaVnY7oRErs7Ub1Td1GjFE0VG4Uc7SlwRzK1idmtaZGW6dbPpHhmwkDNa7xnohRaKgRBmP2pg==
|
integrity sha512-30ygRxj7l0KjbmOGF2xcptCOSr8Bw85n6U7I4RzHI5VKvzq7CliKaXr+xvGrRFPvNQ6TnrY25uFdUd7BFDHvGA==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.3.1"
|
classnames "^2.3.1"
|
||||||
react "^18.2.0"
|
react "^18.2.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user