diff --git a/ui/src/Pages/SettingsPage/SettingsPage.js b/ui/src/Pages/SettingsPage/SettingsPage.js index abd73e05..2c815f2e 100644 --- a/ui/src/Pages/SettingsPage/SettingsPage.js +++ b/ui/src/Pages/SettingsPage/SettingsPage.js @@ -4,7 +4,7 @@ import styled from 'styled-components' import Layout from '../../Layout' import ScannerSection from './ScannerSection' -import UsersTable from './UsersTable' +import UsersTable from './Users/UsersTable' export const SectionTitle = styled.h2` margin-top: ${({ nospace }) => (nospace ? '0' : '1.4em')} !important; diff --git a/ui/src/Pages/SettingsPage/UserRow.js b/ui/src/Pages/SettingsPage/UserRow.js deleted file mode 100644 index 0c807e1d..00000000 --- a/ui/src/Pages/SettingsPage/UserRow.js +++ /dev/null @@ -1,298 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { gql, useMutation } from '@apollo/client' -import { - Button, - Checkbox, - Form, - Icon, - Input, - Modal, - Table, -} from 'semantic-ui-react' - -const updateUserMutation = gql` - mutation updateUser( - $id: ID! - $username: String - $rootPath: String - $admin: Boolean - ) { - updateUser( - id: $id - username: $username - rootPath: $rootPath - admin: $admin - ) { - id - username - rootPath - admin - } - } -` - -const deleteUserMutation = gql` - mutation deleteUser($id: ID!) { - deleteUser(id: $id) { - id - username - } - } -` - -const changeUserPasswordMutation = gql` - mutation changeUserPassword($userId: ID!, $password: String!) { - updateUser(id: $userId, password: $password) { - id - } - } -` - -const scanUserMutation = gql` - mutation scanUser($userId: ID!) { - scanUser(userId: $userId) { - success - } - } -` - -const ChangePasswordModal = ({ onClose, user, ...props }) => { - const [passwordInput, setPasswordInput] = useState('') - - const [changePassword] = useMutation(changeUserPasswordMutation, { - onCompleted: () => { - onClose && onClose() - }, - }) - - return ( - - Change password - -

- Change password for {user.username} -

-
- - - setPasswordInput(e.target.value)} - type="password" - /> - -
-
- - - - -
- ) -} - -ChangePasswordModal.propTypes = { - onClose: PropTypes.func, - user: PropTypes.object.isRequired, -} - -const UserRow = ({ user, refetchUsers }) => { - const [state, setState] = useState({ - ...user, - editing: false, - }) - - const [showConfirmDelete, setConfirmDelete] = useState(false) - const [showChangePassword, setChangePassword] = useState(false) - - function updateInput(event, key) { - setState({ - ...state, - [key]: event.target.value, - }) - } - - const [updateUser, { loading: updateUserLoading }] = useMutation( - updateUserMutation, - { - onCompleted: data => { - setState({ - ...data.updateUser, - editing: false, - }) - refetchUsers() - }, - } - ) - - const [deleteUser] = useMutation(deleteUserMutation, { - onCompleted: () => { - refetchUsers() - }, - }) - - const [scanUser, { called: scanUserCalled }] = useMutation(scanUserMutation, { - onCompleted: () => { - refetchUsers() - }, - }) - - if (state.editing) { - return ( - - - updateInput(e, 'username')} - /> - - - updateInput(e, 'rootPath')} - /> - - - { - setState({ - ...state, - admin: data.checked, - }) - }} - /> - - - - - - - - - ) - } - - return ( - - {user.username} - {user.rootPath} - - {user.admin ? : null} - - - - - - - setChangePassword(false)} - /> - - - Delete user - -

- {`Are you sure, you want to delete `} - {user.username}? -

-

{`This action cannot be undone`}

-
- - - - -
-
-
-
- ) -} - -UserRow.propTypes = { - user: PropTypes.object.isRequired, - refetchUsers: PropTypes.func.isRequired, -} - -export default UserRow diff --git a/ui/src/Pages/SettingsPage/AddUserRow.js b/ui/src/Pages/SettingsPage/Users/AddUserRow.js similarity index 100% rename from ui/src/Pages/SettingsPage/AddUserRow.js rename to ui/src/Pages/SettingsPage/Users/AddUserRow.js diff --git a/ui/src/Pages/SettingsPage/Users/EditUserRow.js b/ui/src/Pages/SettingsPage/Users/EditUserRow.js new file mode 100644 index 00000000..ac21e939 --- /dev/null +++ b/ui/src/Pages/SettingsPage/Users/EditUserRow.js @@ -0,0 +1,158 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styled from 'styled-components' +import { Button, Checkbox, Icon, Input, Table } from 'semantic-ui-react' +import { UserRowProps } from './UserRow' + +const RootPathListItem = styled.li` + display: flex; + justify-content: space-between; + align-items: center; +` + +const EditRootPath = ({ filePath, removePath }) => ( + + {filePath} + + +) + +EditRootPath.propTypes = { + filePath: PropTypes.string.isRequired, + removePath: PropTypes.func.isRequired, +} + +const NewRootPathInput = styled(Input)` + width: 100%; + margin-top: 24px; +` + +const EditNewRootPath = ({ state, updateInput }) => ( +
  • + updateInput(e, 'rootPath')} + action={{ + positive: true, + icon: 'add', + content: 'Add', + }} + /> +
  • +) + +EditNewRootPath.propTypes = { + state: PropTypes.object.isRequired, + updateInput: PropTypes.func.isRequired, +} + +const RootPathList = styled.ul` + margin: 0; + padding: 0; + list-style: none; +` + +const EditRootPaths = ({ user, state, updateInput }) => { + const editRows = user.rootAlbums.map(album => ( + {}} + /> + )) + + return ( + + {editRows} + + + ) +} + +EditRootPaths.propTypes = { + updateInput: PropTypes.func.isRequired, + user: PropTypes.object.isRequired, + state: PropTypes.object.isRequired, +} + +const EditUserRow = ({ + user, + state, + setState, + updateUser, + updateUserLoading, +}) => { + function updateInput(event, key) { + setState(state => ({ + ...state, + [key]: event.target.value, + })) + } + + return ( + + + updateInput(e, 'username')} + /> + + + + + + { + setState({ + ...state, + admin: data.checked, + }) + }} + /> + + + + + + + + + ) +} + +EditUserRow.propTypes = UserRowProps + +export default EditUserRow diff --git a/ui/src/Pages/SettingsPage/Users/UserChangePassword.js b/ui/src/Pages/SettingsPage/Users/UserChangePassword.js new file mode 100644 index 00000000..39d9b9a8 --- /dev/null +++ b/ui/src/Pages/SettingsPage/Users/UserChangePassword.js @@ -0,0 +1,66 @@ +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import { gql, useMutation } from '@apollo/client' +import { Button, Form, Input, Modal } from 'semantic-ui-react' + +const changeUserPasswordMutation = gql` + mutation changeUserPassword($userId: ID!, $password: String!) { + updateUser(id: $userId, password: $password) { + id + } + } +` + +const ChangePasswordModal = ({ onClose, user, ...props }) => { + const [passwordInput, setPasswordInput] = useState('') + + const [changePassword] = useMutation(changeUserPasswordMutation, { + onCompleted: () => { + onClose && onClose() + }, + }) + + return ( + + Change password + +

    + Change password for {user.username} +

    +
    + + + setPasswordInput(e.target.value)} + type="password" + /> + +
    +
    + + + + +
    + ) +} + +ChangePasswordModal.propTypes = { + onClose: PropTypes.func, + user: PropTypes.object.isRequired, +} + +export default ChangePasswordModal diff --git a/ui/src/Pages/SettingsPage/Users/UserRow.js b/ui/src/Pages/SettingsPage/Users/UserRow.js new file mode 100644 index 00000000..7a19b14d --- /dev/null +++ b/ui/src/Pages/SettingsPage/Users/UserRow.js @@ -0,0 +1,121 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { gql, useMutation } from '@apollo/client' +import EditUserRow from './EditUserRow' +import ViewUserRow from './ViewUserRow' + +const updateUserMutation = gql` + mutation updateUser( + $id: ID! + $username: String + $rootPath: String + $admin: Boolean + ) { + updateUser( + id: $id + username: $username + rootPath: $rootPath + admin: $admin + ) { + id + username + rootPath + admin + } + } +` + +const deleteUserMutation = gql` + mutation deleteUser($id: ID!) { + deleteUser(id: $id) { + id + username + } + } +` + +const scanUserMutation = gql` + mutation scanUser($userId: ID!) { + scanUser(userId: $userId) { + success + } + } +` + +const UserRow = ({ user, refetchUsers }) => { + const [state, setState] = useState({ + ...user, + editing: false, + }) + + const [showConfirmDelete, setConfirmDelete] = useState(false) + const [showChangePassword, setChangePassword] = useState(false) + + const [updateUser, { loading: updateUserLoading }] = useMutation( + updateUserMutation, + { + onCompleted: data => { + setState({ + ...data.updateUser, + editing: false, + }) + refetchUsers() + }, + } + ) + + const [deleteUser] = useMutation(deleteUserMutation, { + onCompleted: () => { + refetchUsers() + }, + }) + + const [scanUser, { called: scanUserCalled }] = useMutation(scanUserMutation, { + onCompleted: () => { + refetchUsers() + }, + }) + + const props = { + user, + state, + setState, + scanUser, + updateUser, + updateUserLoading, + deleteUser, + setChangePassword, + setConfirmDelete, + scanUserCalled, + showChangePassword, + showConfirmDelete, + } + + if (state.editing) { + return + } + + return +} + +UserRow.propTypes = { + user: PropTypes.object.isRequired, + refetchUsers: PropTypes.func.isRequired, +} + +export const UserRowProps = { + user: PropTypes.object.isRequired, + state: PropTypes.object.isRequired, + setState: PropTypes.func.isRequired, + scanUser: PropTypes.func.isRequired, + updateUser: PropTypes.func.isRequired, + updateUserLoading: PropTypes.bool.isRequired, + deleteUser: PropTypes.func.isRequired, + setChangePassword: PropTypes.func.isRequired, + setConfirmDelete: PropTypes.func.isRequired, + scanUserCalled: PropTypes.func.isRequired, + showChangePassword: PropTypes.func.isRequired, + showConfirmDelete: PropTypes.func.isRequired, +} + +export default UserRow diff --git a/ui/src/Pages/SettingsPage/UsersTable.js b/ui/src/Pages/SettingsPage/Users/UsersTable.js similarity index 96% rename from ui/src/Pages/SettingsPage/UsersTable.js rename to ui/src/Pages/SettingsPage/Users/UsersTable.js index 9c86aa09..fa628d99 100644 --- a/ui/src/Pages/SettingsPage/UsersTable.js +++ b/ui/src/Pages/SettingsPage/Users/UsersTable.js @@ -4,7 +4,7 @@ import { Table, Loader, Button, Icon } from 'semantic-ui-react' import { useQuery, gql } from '@apollo/client' import UserRow from './UserRow' import AddUserRow from './AddUserRow' -import { SectionTitle } from './SettingsPage' +import { SectionTitle } from '../SettingsPage' const USERS_QUERY = gql` query settingsUsersQuery { @@ -13,7 +13,7 @@ const USERS_QUERY = gql` username # rootPath admin - albums { + rootAlbums { id filePath } diff --git a/ui/src/Pages/SettingsPage/Users/ViewUserRow.js b/ui/src/Pages/SettingsPage/Users/ViewUserRow.js new file mode 100644 index 00000000..d90432ac --- /dev/null +++ b/ui/src/Pages/SettingsPage/Users/ViewUserRow.js @@ -0,0 +1,109 @@ +import React from 'react' +import { Button, Icon, Table, Modal } from 'semantic-ui-react' +import styled from 'styled-components' +import ChangePasswordModal from './UserChangePassword' +import { UserRowProps } from './UserRow' + +const PathList = styled.ul` + margin: 0; + padding: 0 0 0 12px; + list-style: none; +` + +const ViewUserRow = ({ + user, + state, + setState, + scanUser, + deleteUser, + setChangePassword, + setConfirmDelete, + scanUserCalled, + showChangePassword, + showConfirmDelete, +}) => { + const paths = ( + + {user.rootAlbums.map(album => ( +
  • {album.filePath}
  • + ))} +
    + ) + + return ( + + {user.username} + {paths} + + {user.admin ? : null} + + + + + + + setChangePassword(false)} + /> + + + Delete user + +

    + {`Are you sure, you want to delete `} + {user.username}? +

    +

    {`This action cannot be undone`}

    +
    + + + + +
    +
    +
    +
    + ) +} + +ViewUserRow.propTypes = UserRowProps + +export default ViewUserRow