diff --git a/components/icons.js b/components/icons.js index af254424..277c5e0f 100644 --- a/components/icons.js +++ b/components/icons.js @@ -9,6 +9,7 @@ import Logo from 'assets/logo.svg'; import Moon from 'assets/moon.svg'; import Profile from 'assets/profile.svg'; import Sun from 'assets/sun.svg'; +import Trash from 'assets/trash.svg'; import User from 'assets/user.svg'; import Users from 'assets/users.svg'; @@ -24,6 +25,7 @@ export { Moon, Profile, Sun, + Trash, User, Users, }; diff --git a/components/messages.js b/components/messages.js index 1151752a..a87ed093 100644 --- a/components/messages.js +++ b/components/messages.js @@ -2,6 +2,14 @@ import { defineMessages } from 'react-intl'; export const labels = defineMessages({ 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({ diff --git a/components/pages/settings/profile/ChangePasswordButton.js b/components/pages/settings/profile/PasswordChangeButton.js similarity index 81% rename from components/pages/settings/profile/ChangePasswordButton.js rename to components/pages/settings/profile/PasswordChangeButton.js index d80e8d63..1cac0083 100644 --- a/components/pages/settings/profile/ChangePasswordButton.js +++ b/components/pages/settings/profile/PasswordChangeButton.js @@ -1,6 +1,6 @@ import { useState } from 'react'; 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 { Lock } from 'components/icons'; @@ -9,7 +9,7 @@ const messages = defineMessages({ saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' }, }); -export default function ChangePasswordButton() { +export default function PasswordChangeButton() { const { formatMessage } = useIntl(); const [edit, setEdit] = useState(false); const { toast, showToast } = useToast(); @@ -19,7 +19,7 @@ export default function ChangePasswordButton() { setEdit(false); }; - const handleAdd = () => { + const handleEdit = () => { setEdit(true); }; @@ -30,11 +30,11 @@ export default function ChangePasswordButton() { return ( <> {toast} - {edit && ( diff --git a/components/pages/settings/profile/ProfileSettings.js b/components/pages/settings/profile/ProfileSettings.js index 8a0323f8..700ae640 100644 --- a/components/pages/settings/profile/ProfileSettings.js +++ b/components/pages/settings/profile/ProfileSettings.js @@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import ProfileDetails from './ProfileDetails'; -import ChangePasswordButton from './ChangePasswordButton'; +import PasswordChangeButton from './PasswordChangeButton'; const messages = defineMessages({ profile: { id: 'label.profile', defaultMessage: 'Profile' }, @@ -18,7 +18,7 @@ export default function ProfileSettings() { {formatMessage(messages.profile)} - + diff --git a/components/pages/settings/users/UserAddButton.js b/components/pages/settings/users/UserAddButton.js new file mode 100644 index 00000000..88601065 --- /dev/null +++ b/components/pages/settings/users/UserAddButton.js @@ -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} + + {edit && ( + + {() => } + + )} + + ); +} diff --git a/components/pages/settings/users/UserAddForm.js b/components/pages/settings/users/UserAddForm.js index 64e43b60..f7d9ee81 100644 --- a/components/pages/settings/users/UserAddForm.js +++ b/components/pages/settings/users/UserAddForm.js @@ -8,10 +8,12 @@ import { TextField, PasswordField, SubmitButton, + Button, } from 'react-basics'; import { useIntl, defineMessages } from 'react-intl'; import useApi from 'hooks/useApi'; import { ROLES } from 'lib/constants'; +import { labels } from 'components/messages'; const messages = defineMessages({ username: { id: 'label.username', defaultMessage: 'Username' }, @@ -19,14 +21,11 @@ const messages = defineMessages({ role: { id: 'label.role', defaultMessage: 'Role' }, user: { id: 'label.user', defaultMessage: 'User' }, 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 { mutate, error } = useMutation(data => post(`/users`, data)); + const { mutate, error, isLoading } = useMutation(data => post(`/users`, data)); const { formatMessage } = useIntl(); 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 (
- - + + - - + + - - + + {formatMessage(messages.user)} {formatMessage(messages.admin)} - - {formatMessage(messages.save)} + + + {formatMessage(labels.save)} + + ); diff --git a/components/pages/settings/users/UserDelete.js b/components/pages/settings/users/UserDelete.js deleted file mode 100644 index 756dce9c..00000000 --- a/components/pages/settings/users/UserDelete.js +++ /dev/null @@ -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 ( -
- -

All user data will be deleted.

- -
- {modal === 'delete' && ( - - {close => } - - )} -
- ); -} diff --git a/components/pages/settings/users/UserDeleteForm.js b/components/pages/settings/users/UserDeleteForm.js index a63ee6cd..e7ca37e9 100644 --- a/components/pages/settings/users/UserDeleteForm.js +++ b/components/pages/settings/users/UserDeleteForm.js @@ -1,18 +1,19 @@ import { useMutation } from '@tanstack/react-query'; import useApi from 'hooks/useApi'; -import { - Button, - Form, - FormRow, - FormButtons, - FormInput, - SubmitButton, - TextField, -} from 'react-basics'; +import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; +import { defineMessages, useIntl } from 'react-intl'; +import { labels } from 'components/messages'; -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 }) { + const { formatMessage } = useIntl(); const { del } = useApi(); const { mutate, error, isLoading } = useMutation(data => del(`/users/${userId}`, data)); @@ -26,20 +27,13 @@ export default function UserDeleteForm({ userId, onSave, onClose }) { return (
-

- To delete this user, type {CONFIRM_VALUE} in the box below to confirm. -

- - value === CONFIRM_VALUE }}> - - - +

{formatMessage(messages.warning)}

- Save + {formatMessage(labels.save)}
diff --git a/components/pages/settings/users/UserEditForm.js b/components/pages/settings/users/UserEditForm.js index eb1d921f..41f88006 100644 --- a/components/pages/settings/users/UserEditForm.js +++ b/components/pages/settings/users/UserEditForm.js @@ -7,53 +7,75 @@ import { FormInput, TextField, SubmitButton, + PasswordField, } from 'react-basics'; -import { useRef } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import { ROLES } from 'lib/constants'; +import { labels } from 'components/messages'; -const items = [ - { - value: ROLES.user, - label: 'User', - }, - { - value: ROLES.admin, - label: 'Admin', - }, -]; +const messages = defineMessages({ + username: { id: 'label.username', defaultMessage: 'Username' }, -export default function UserEditForm({ data, onSave }) { - const { id } = data; + password: { id: 'label.password', defaultMessage: 'Password' }, + 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 { mutate, error } = useMutation(({ username }) => post(`/user/${id}`, { username })); - const ref = useRef(null); + const { mutate, error } = useMutation(({ username }) => post(`/users/${userId}`, { username })); const handleSubmit = async data => { mutate(data, { onSuccess: async () => { 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 ( -
- + + - - - - {({ value, label }) => {label}} + + + + + + + + + {formatMessage(messages.user)} + {formatMessage(messages.admin)} - Save + {formatMessage(labels.save)} ); diff --git a/components/pages/settings/users/UserForm.module.css b/components/pages/settings/users/UserForm.module.css deleted file mode 100644 index 793682e1..00000000 --- a/components/pages/settings/users/UserForm.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.form { - display: flex; - flex-direction: column; - gap: 30px; - width: 300px; -} diff --git a/components/pages/settings/users/UserPasswordForm.js b/components/pages/settings/users/UserPasswordForm.js deleted file mode 100644 index 218d8469..00000000 --- a/components/pages/settings/users/UserPasswordForm.js +++ /dev/null @@ -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 ( -
- {isCurrentUser && ( - - - - - - )} - - - - - - - - - - - - - - -
- ); -} diff --git a/components/pages/settings/users/UserSettings.js b/components/pages/settings/users/UserSettings.js index 993750c6..8d15070c 100644 --- a/components/pages/settings/users/UserSettings.js +++ b/components/pages/settings/users/UserSettings.js @@ -3,17 +3,17 @@ import { defineMessages, useIntl } from 'react-intl'; import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics'; import Link from 'next/link'; 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 UserPasswordForm from 'components/pages/settings/users/UserPasswordForm'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import useApi from 'hooks/useApi'; +import WebsitesTable from '../websites/WebsitesTable'; const messages = defineMessages({ users: { id: 'label.users', defaultMessage: 'Users' }, 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' }, saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' }, delete: { id: 'message.delete-successfully', defaultMessage: 'Delete successfully.' }, @@ -38,7 +38,7 @@ export default function UserSettings({ userId }) { ); const handleSave = data => { - showToast({ message: 'Saved successfully.', variant: 'success' }); + showToast({ message: formatMessage(messages.saved), variant: 'success' }); if (data) { setValues(state => ({ ...state, ...data })); } @@ -49,7 +49,7 @@ export default function UserSettings({ userId }) { }; const handleDelete = async () => { - showToast({ message: 'Deleted successfully.', variant: 'danger' }); + showToast({ message: formatMessage(messages.delete), variant: 'danger' }); await router.push('/users'); }; @@ -72,12 +72,12 @@ export default function UserSettings({ userId }) { {formatMessage(messages.details)} - {formatMessage(messages.changePassword)} + {formatMessage(messages.websites)} {formatMessage(messages.actions)} {tab === 'details' && } - {tab === 'password' && } - {tab === 'delete' && } + {tab === 'websites' && } + {tab === 'delete' && } ); } diff --git a/components/pages/settings/users/UsersList.js b/components/pages/settings/users/UsersList.js index b8d579b9..901a986f 100644 --- a/components/pages/settings/users/UsersList.js +++ b/components/pages/settings/users/UsersList.js @@ -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 Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; -import UsersTable from 'components/pages/settings/users/UsersTable'; -import UserEditForm from 'components/pages/settings/users/UserEditForm'; +import UsersTable from './UsersTable'; +import UserAddButton from './UserAddButton'; import useApi from 'hooks/useApi'; import useUser from 'hooks/useUser'; -const { Plus } = Icons; - const messages = defineMessages({ - saved: { id: 'messages.api-key-saved', defaultMessage: 'API key saved.' }, noUsers: { - id: 'messages.no-useres', + id: 'messages.no-users', defaultMessage: "You don't have any users.", }, users: { id: 'label.users', defaultMessage: 'Users' }, @@ -22,48 +17,23 @@ const messages = defineMessages({ }); export default function UsersList() { - const [edit, setEdit] = useState(false); const { formatMessage } = useIntl(); - const { toast, showToast } = useToast(); const { user } = useUser(); const { get, useQuery } = useApi(); - const { data, isLoading, error, refetch } = useQuery(['user'], () => get(`/users`), { + const { data, isLoading, error } = useQuery(['user'], () => get(`/users`), { enabled: !!user, }); const hasData = data && data.length !== 0; - const handleSave = async () => { - await refetch(); - setEdit(false); - showToast({ message: formatMessage(messages.saved), variant: 'success' }); - }; - - const handleAdd = () => setEdit(true); - - const handleClose = () => setEdit(false); - - const addButton = ( - - ); + const addButton = ; return ( - {toast} {addButton} {hasData && } {!hasData && ( {addButton} )} - {edit && ( - - {close => } - - )} ); } diff --git a/components/pages/settings/websites/WebsiteAddForm.js b/components/pages/settings/websites/WebsiteAddForm.js index 6669af93..5231e8ac 100644 --- a/components/pages/settings/websites/WebsiteAddForm.js +++ b/components/pages/settings/websites/WebsiteAddForm.js @@ -1,4 +1,3 @@ -import { useRef } from 'react'; import { Form, FormRow, @@ -8,13 +7,19 @@ import { Button, SubmitButton, } from 'react-basics'; +import { defineMessages, useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; 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 }) { + const { formatMessage } = useIntl(); const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post('/websites', data)); - const ref = useRef(null); const handleSubmit = async data => { mutate(data, { @@ -25,9 +30,9 @@ export default function WebsiteAddForm({ onSave, onClose }) { }; return ( -
+ - + @@ -35,8 +40,8 @@ export default function WebsiteAddForm({ onSave, onClose }) { @@ -44,10 +49,10 @@ export default function WebsiteAddForm({ onSave, onClose }) { - Save + {formatMessage(labels.save)} diff --git a/package.json b/package.json index 3e3d6c79..94095153 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "npm-run-all": "^4.1.5", "prop-types": "^15.7.2", "react": "^18.2.0", - "react-basics": "^0.53.0", + "react-basics": "^0.55.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-intl": "^5.24.7", diff --git a/yarn.lock b/yarn.lock index 84cf0654..6cb3a5b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6562,10 +6562,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.53.0: - version "0.53.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.53.0.tgz#2215ca47bca99a316cd129875682960d6519611f" - integrity sha512-XsG38JsumhHvqGaVnY7oRErs7Ub1Td1GjFE0VG4Uc7SlwRzK1idmtaZGW6dbPpHhmwkDNa7xnohRaKgRBmP2pg== +react-basics@^0.55.0: + version "0.55.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.55.0.tgz#b9c3dbba33a3ce118e63322ddcb822139a3ea547" + integrity sha512-30ygRxj7l0KjbmOGF2xcptCOSr8Bw85n6U7I4RzHI5VKvzq7CliKaXr+xvGrRFPvNQ6TnrY25uFdUd7BFDHvGA== dependencies: classnames "^2.3.1" react "^18.2.0"