1
Fork 0

Merge pull request #112 from stz184/component-class-to-function-migration

Migrate all class based react components to function based
This commit is contained in:
Viktor Strate Kløvedal 2020-11-17 16:26:46 +01:00 committed by GitHub
commit 514d9b1c2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 578 additions and 633 deletions

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React from 'react'
import { createGlobalStyle } from 'styled-components'
import { Helmet } from 'react-helmet'
import Routes from './components/routes/Routes'
@ -23,22 +23,20 @@ const GlobalStyle = createGlobalStyle`
import 'semantic-ui-css/semantic.min.css'
class App extends Component {
render() {
return (
<>
<Helmet>
<meta
name="description"
content="Simple and User-friendly Photo Gallery for Personal Servers"
/>
</Helmet>
<GlobalStyle />
<Routes />
<Messages />
</>
)
}
const App = () => {
return (
<>
<Helmet>
<meta
name="description"
content="Simple and User-friendly Photo Gallery for Personal Servers"
/>
</Helmet>
<GlobalStyle />
<Routes />
<Messages />
</>
)
}
export default App

View File

@ -8,6 +8,7 @@ import { useQuery, gql } from '@apollo/client'
import { Authorized } from './components/routes/AuthorizedRoute'
import { Helmet } from 'react-helmet'
import Header from './components/header/Header'
import { authToken } from './authentication'
export const ADMIN_QUERY = gql`
query adminQuery {
@ -94,13 +95,11 @@ const SideButtonLabel = styled.div`
`
export const SideMenu = () => {
const adminQuery = useQuery(ADMIN_QUERY)
const mapboxQuery = useQuery(MAPBOX_QUERY)
const adminQuery = authToken() ? useQuery(ADMIN_QUERY) : null
const mapboxQuery = authToken() ? useQuery(MAPBOX_QUERY) : null
const isAdmin =
adminQuery.data && adminQuery.data.myUser && adminQuery.data.myUser.admin
const mapboxEnabled = mapboxQuery.data && mapboxQuery.data.mapboxToken != null
const isAdmin = adminQuery?.data?.myUser?.admin
const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken
return (
<SideMenuContainer>

View File

@ -7,6 +7,10 @@ import { render, screen } from '@testing-library/react'
import Layout, { ADMIN_QUERY, MAPBOX_QUERY, SideMenu } from './Layout'
import { MemoryRouter } from 'react-router-dom'
import * as authentication from './authentication'
jest.mock('./authentication.js')
test('Layout component', async () => {
render(
<Layout>
@ -18,7 +22,13 @@ test('Layout component', async () => {
expect(screen.getByText('layout_content')).toBeInTheDocument()
})
afterEach(() => {
authentication.authToken.mockClear()
})
test('Layout sidebar component', async () => {
authentication.authToken.mockImplementation(() => true)
const mockedGraphql = [
{
request: {

View File

@ -1,8 +1,7 @@
import React, { useCallback, useState, useEffect } from 'react'
import ReactRouterPropTypes from 'react-router-prop-types'
import { useLocation } from 'react-router-dom'
import { gql } from '@apollo/client'
import { Query } from '@apollo/client/react/components'
import { useQuery, gql } from '@apollo/client'
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
import PropTypes from 'prop-types'
import Layout from '../../Layout'
@ -67,6 +66,15 @@ function AlbumPage({ match }) {
orderDirection: urlParams.get('orderDirection') || 'ASC',
})
const { loading, error, data, refetch } = useQuery(albumQuery, {
variables: {
id: albumId,
onlyFavorites,
mediaOrderBy: ordering.orderBy,
mediaOrderDirection: ordering.orderDirection,
},
})
const setOrderingCallback = useCallback(
ordering => {
setOrdering(prevState => {
@ -80,7 +88,7 @@ function AlbumPage({ match }) {
)
const toggleFavorites = useCallback(
(onlyFavorites, refetch) => {
onlyFavorites => {
if (
(refetchNeededAll && !onlyFavorites) ||
(refetchNeededFavorites && onlyFavorites)
@ -97,7 +105,7 @@ function AlbumPage({ match }) {
setOnlyFavorites(onlyFavorites)
}
},
[setOnlyFavorites]
[setOnlyFavorites, refetch]
)
useEffect(() => {
@ -107,44 +115,22 @@ function AlbumPage({ match }) {
history.replaceState({}, '', pathName + '?' + queryString)
}, [onlyFavorites, ordering])
return (
<Query
query={albumQuery}
variables={{
id: albumId,
onlyFavorites,
mediaOrderBy: ordering.orderBy,
mediaOrderDirection: ordering.orderDirection,
}}
>
{({ loading, error, data, refetch }) => {
const setOnlyFavorites = useCallback(
checked => {
toggleFavorites(checked, refetch)
},
[toggleFavorites, refetch]
)
if (error) return <div>Error</div>
if (error) return <div>Error</div>
return (
<Layout title={data ? data.album.title : 'Loading album'}>
<AlbumGallery
album={data && data.album}
loading={loading}
showFavoritesToggle
setOnlyFavorites={setOnlyFavorites}
onlyFavorites={onlyFavorites}
onFavorite={() =>
(refetchNeededAll = refetchNeededFavorites = true)
}
showFilter
setOrdering={setOrderingCallback}
ordering={ordering}
/>
</Layout>
)
}}
</Query>
return (
<Layout title={data ? data.album.title : 'Loading album'}>
<AlbumGallery
album={data && data.album}
loading={loading}
showFavoritesToggle
setOnlyFavorites={toggleFavorites}
onlyFavorites={onlyFavorites}
onFavorite={() => (refetchNeededAll = refetchNeededFavorites = true)}
showFilter
setOrdering={setOrderingCallback}
ordering={ordering}
/>
</Layout>
)
}

View File

@ -1,8 +1,7 @@
import React, { Component } from 'react'
import React from 'react'
import AlbumBoxes from '../../components/albumGallery/AlbumBoxes'
import Layout from '../../Layout'
import { gql } from '@apollo/client'
import { Query } from '@apollo/client/react/components'
import { useQuery, gql } from '@apollo/client'
const getAlbumsQuery = gql`
query getMyAlbums {
@ -18,23 +17,21 @@ const getAlbumsQuery = gql`
}
`
class AlbumsPage extends Component {
render() {
return (
<Layout title="Albums">
<h1>Albums</h1>
<Query query={getAlbumsQuery}>
{({ loading, error, data }) => (
<AlbumBoxes
loading={loading}
error={error}
albums={data && data.myAlbums}
/>
)}
</Query>
</Layout>
)
}
const AlbumsPage = () => {
const { loading, error, data } = useQuery(getAlbumsQuery)
return (
<Layout title="Albums">
<h1>Albums</h1>
{!loading && (
<AlbumBoxes
loading={loading}
error={error}
albums={data && data.myAlbums}
/>
)}
</Layout>
)
}
export default AlbumsPage

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react'
import { gql } from '@apollo/client'
import { Mutation, Query } from '@apollo/client/react/components'
import React, { useState } from 'react'
import { gql, useQuery, useMutation } from '@apollo/client'
import { Redirect } from 'react-router-dom'
import { Button, Form, Message, Header } from 'semantic-ui-react'
import { Container } from './loginUtilities'
@ -26,106 +25,101 @@ const initialSetupMutation = gql`
}
`
class InitialSetupPage extends Component {
constructor(props) {
super(props)
const InitialSetupPage = () => {
const [state, setState] = useState({
username: '',
password: '',
rootPath: '',
})
this.state = {
username: '',
password: '',
rootPath: '',
}
const handleChange = (event, key) => {
const value = event.target.value
setState(prevState => ({
...prevState,
[key]: value,
}))
}
handleChange(event, key) {
this.setState({ [key]: event.target.value })
}
signIn(event, authorize) {
const signIn = (event, authorize) => {
event.preventDefault()
authorize({
variables: {
username: this.state.username,
password: this.state.password,
rootPath: this.state.rootPath,
username: state.username,
password: state.password,
rootPath: state.rootPath,
},
})
}
render() {
if (authToken()) {
return <Redirect to="/" />
}
return (
<div>
<Container>
<Header as="h1" textAlign="center">
Initial Setup
</Header>
<Query query={checkInitialSetupQuery}>
{({ data }) => {
if (data && data.siteInfo && data.siteInfo.initialSetup) {
return null
}
return <Redirect to="/" />
}}
</Query>
<Mutation
mutation={initialSetupMutation}
onCompleted={data => {
const { success, token } = data.initialSetupWizard
if (success) {
login(token)
}
}}
>
{(authorize, { loading, data }) => {
let errorMessage = null
if (data) {
if (!data.initialSetupWizard.success)
errorMessage = data.initialSetupWizard.status
}
return (
<Form
style={{ width: 500, margin: 'auto' }}
error={!!errorMessage}
onSubmit={e => this.signIn(e, authorize)}
loading={loading || (data && data.initialSetupWizard.success)}
>
<Form.Field>
<label>Username</label>
<input onChange={e => this.handleChange(e, 'username')} />
</Form.Field>
<Form.Field>
<label>Password</label>
<input
type="password"
onChange={e => this.handleChange(e, 'password')}
/>
</Form.Field>
<Form.Field>
<label>Photo Path</label>
<input
placeholder="/path/to/photos"
type="text"
onChange={e => this.handleChange(e, 'rootPath')}
/>
</Form.Field>
<Message error content={errorMessage} />
<Button type="submit">Setup Photoview</Button>
</Form>
)
}}
</Mutation>
</Container>
</div>
)
if (authToken()) {
return <Redirect to="/" />
}
const { data: initialSetupData } = useQuery(checkInitialSetupQuery)
const initialSetupRedirect = initialSetupData?.siteInfo
?.initialSetup ? null : (
<Redirect to="/" />
)
const [
authorize,
{ loading: authorizeLoading, data: authorizationData },
] = useMutation(initialSetupMutation, {
onCompleted: data => {
const { success, token } = data.initialSetupWizard
if (success) {
login(token)
}
},
})
let errorMessage = null
if (authorizationData && !authorizationData.initialSetupWizard.success) {
errorMessage = authorizationData.initialSetupWizard.status
}
return (
<div>
{initialSetupRedirect}
<Container>
<Header as="h1" textAlign="center">
Initial Setup
</Header>
<Form
style={{ width: 500, margin: 'auto' }}
error={!!errorMessage}
onSubmit={e => signIn(e, authorize)}
loading={
authorizeLoading || authorizationData?.initialSetupWizard?.success
}
>
<Form.Field>
<label>Username</label>
<input onChange={e => handleChange(e, 'username')} />
</Form.Field>
<Form.Field>
<label>Password</label>
<input
type="password"
onChange={e => handleChange(e, 'password')}
/>
</Form.Field>
<Form.Field>
<label>Photo Path</label>
<input
placeholder="/path/to/photos"
type="text"
onChange={e => handleChange(e, 'rootPath')}
/>
</Form.Field>
<Message error content={errorMessage} />
<Button type="submit">Setup Photoview</Button>
</Form>
</Container>
</div>
)
}
export default InitialSetupPage

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react'
import { gql } from '@apollo/client'
import { Mutation, Query } from '@apollo/client/react/components'
import React, { useState, useCallback } from 'react'
import { useQuery, gql, useMutation } from '@apollo/client'
import { Redirect } from 'react-router-dom'
import styled from 'styled-components'
import { Button, Form, Message, Header } from 'semantic-ui-react'
@ -34,98 +33,92 @@ const LogoHeaderStyled = styled(LogoHeader)`
margin-bottom: 72px !important;
`
class LoginPage extends Component {
constructor(props) {
super(props)
const LoginPage = () => {
const [credentials, setCredentials] = useState({
username: '',
password: '',
})
this.state = {
username: '',
password: '',
}
const handleChange = useCallback(
(event, key) => {
const value = event.target.value
setCredentials(credentials => {
return {
...credentials,
[key]: value,
}
})
},
[setCredentials]
)
const signIn = useCallback(
(event, authorize) => {
event.preventDefault()
authorize({
variables: {
username: credentials.username,
password: credentials.password,
},
})
},
[credentials]
)
const { data: initialSetupData } = useQuery(checkInitialSetupQuery)
const [authorize, { loading, data }] = useMutation(authorizeMutation, {
onCompleted: data => {
const { success, token } = data.authorizeUser
if (success) {
login(token)
}
},
})
const errorMessage =
data && !data.authorizeUser.success ? data.authorizeUser.status : null
if (authToken()) {
return <Redirect to="/" />
}
handleChange(event, key) {
this.setState({ [key]: event.target.value })
}
signIn(event, authorize) {
event.preventDefault()
authorize({
variables: {
username: this.state.username,
password: this.state.password,
},
})
}
render() {
if (authToken()) {
return <Redirect to="/" />
}
return (
<div>
<Container>
<LogoHeaderStyled />
<Query query={checkInitialSetupQuery}>
{({ data }) => {
if (data && data.siteInfo && data.siteInfo.initialSetup) {
return <Redirect to="/initialSetup" />
}
return null
}}
</Query>
<Mutation
mutation={authorizeMutation}
onCompleted={data => {
const { success, token } = data.authorizeUser
if (success) {
login(token)
}
}}
>
{(authorize, { loading, data }) => {
let errorMessage = null
if (data) {
if (!data.authorizeUser.success)
errorMessage = data.authorizeUser.status
}
return (
<Form
style={{ width: 500, margin: 'auto' }}
error={!!errorMessage}
onSubmit={e => this.signIn(e, authorize)}
loading={loading || (data && data.authorizeUser.success)}
>
<Form.Field>
<label htmlFor="username_field">Username</label>
<input
id="username_field"
onChange={e => this.handleChange(e, 'username')}
/>
</Form.Field>
<Form.Field>
<label htmlFor="password_field">Password</label>
<input
type="password"
id="password_field"
onChange={e => this.handleChange(e, 'password')}
/>
</Form.Field>
<Message error content={errorMessage} />
<Button type="submit">Sign in</Button>
</Form>
)
}}
</Mutation>
</Container>
</div>
)
}
return (
<div>
<Container>
<LogoHeaderStyled />
{initialSetupData?.siteInfo?.initialSetup && (
<Redirect to="/initialSetup" />
)}
<Form
style={{ width: 500, margin: 'auto' }}
error={!!errorMessage}
onSubmit={e => signIn(e, authorize)}
loading={loading || (data && data.authorizeUser.success)}
>
<Form.Field>
<label htmlFor="username_field">Username</label>
<input
id="username_field"
onChange={e => handleChange(e, 'username')}
/>
</Form.Field>
<Form.Field>
<label htmlFor="password_field">Password</label>
<input
type="password"
id="password_field"
onChange={e => handleChange(e, 'password')}
/>
</Form.Field>
<Message error content={errorMessage} />
<Button type="submit">Sign in</Button>
</Form>
</Container>
</div>
)
}
export default LoginPage

View File

@ -1,7 +1,6 @@
import { gql } from '@apollo/client'
import { gql, useMutation } from '@apollo/client'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import { Mutation } from '@apollo/client'
import { Button, Checkbox, Input, Table } from 'semantic-ui-react'
const createUserMutation = gql`
@ -28,6 +27,12 @@ const initialState = {
const AddUserRow = ({ setShow, show, onUserAdded }) => {
const [state, setState] = useState(initialState)
const [createUser, { loading }] = useMutation(createUserMutation, {
onCompleted: () => {
onUserAdded()
},
})
function updateInput(event, key) {
setState({
...state,
@ -40,68 +45,59 @@ const AddUserRow = ({ setShow, show, onUserAdded }) => {
}
return (
<Mutation
mutation={createUserMutation}
onCompleted={() => {
onUserAdded()
}}
>
{(createUser, { loading }) => (
<Table.Row>
<Table.Cell>
<Input
placeholder="Username"
value={state.username}
onChange={e => updateInput(e, 'username')}
/>
</Table.Cell>
<Table.Cell>
<Input
placeholder="/path/to/photos"
value={state.rootPath}
onChange={e => updateInput(e, 'rootPath')}
/>
</Table.Cell>
<Table.Cell>
<Checkbox
toggle
checked={state.admin}
onChange={(e, data) => {
setState({
...state,
admin: data.checked,
})
}}
/>
</Table.Cell>
<Table.Cell>
<Button.Group>
<Button negative onClick={() => setShow(false)}>
Cancel
</Button>
<Button
type="submit"
loading={loading}
disabled={loading}
positive
onClick={() => {
createUser({
variables: {
username: state.username,
rootPath: state.rootPath,
admin: state.admin,
},
})
setState(initialState)
}}
>
Add User
</Button>
</Button.Group>
</Table.Cell>
</Table.Row>
)}
</Mutation>
<Table.Row>
<Table.Cell>
<Input
placeholder="Username"
value={state.username}
onChange={e => updateInput(e, 'username')}
/>
</Table.Cell>
<Table.Cell>
<Input
placeholder="/path/to/photos"
value={state.rootPath}
onChange={e => updateInput(e, 'rootPath')}
/>
</Table.Cell>
<Table.Cell>
<Checkbox
toggle
checked={state.admin}
onChange={(e, data) => {
setState({
...state,
admin: data.checked,
})
}}
/>
</Table.Cell>
<Table.Cell>
<Button.Group>
<Button negative onClick={() => setShow(false)}>
Cancel
</Button>
<Button
type="submit"
loading={loading}
disabled={loading}
positive
onClick={() => {
createUser({
variables: {
username: state.username,
rootPath: state.rootPath,
admin: state.admin,
},
})
setState(initialState)
}}
>
Add User
</Button>
</Button.Group>
</Table.Cell>
</Table.Row>
)
}

View File

@ -1,7 +1,6 @@
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import { Mutation } from '@apollo/client/react/components'
import { gql } from '@apollo/client'
import { gql, useMutation } from '@apollo/client'
import {
Button,
Checkbox,
@ -61,50 +60,47 @@ const scanUserMutation = gql`
const ChangePasswordModal = ({ onClose, user, ...props }) => {
const [passwordInput, setPasswordInput] = useState('')
const [changePassword] = useMutation(changeUserPasswordMutation, {
onCompleted: () => {
onClose && onClose()
},
})
return (
<Mutation
mutation={changeUserPasswordMutation}
onCompleted={() => {
onClose && onClose()
}}
>
{changePassword => (
<Modal {...props}>
<Modal.Header>Change password</Modal.Header>
<Modal.Content>
<p>
Change password for <b>{user.username}</b>
</p>
<Form>
<Form.Field>
<label>New password</label>
<Input
placeholder="password"
onChange={e => setPasswordInput(e.target.value)}
type="password"
/>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => onClose && onClose()}>Cancel</Button>
<Button
positive
onClick={() => {
changePassword({
variables: {
userId: user.id,
password: passwordInput,
},
})
}}
>
Change password
</Button>
</Modal.Actions>
</Modal>
)}
</Mutation>
<Modal {...props}>
<Modal.Header>Change password</Modal.Header>
<Modal.Content>
<p>
Change password for <b>{user.username}</b>
</p>
<Form>
<Form.Field>
<label>New password</label>
<Input
placeholder="password"
onChange={e => setPasswordInput(e.target.value)}
type="password"
/>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => onClose && onClose()}>Cancel</Button>
<Button
positive
onClick={() => {
changePassword({
variables: {
userId: user.id,
password: passwordInput,
},
})
}}
>
Change password
</Button>
</Modal.Actions>
</Modal>
)
}
@ -119,7 +115,7 @@ const UserRow = ({ user, refetchUsers }) => {
editing: false,
})
const [showComfirmDelete, setConfirmDelete] = useState(false)
const [showConfirmDelete, setConfirmDelete] = useState(false)
const [showChangePassword, setChangePassword] = useState(false)
function updateInput(event, key) {
@ -129,171 +125,168 @@ const UserRow = ({ user, refetchUsers }) => {
})
}
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 (
<Mutation
mutation={updateUserMutation}
onCompleted={data => {
setState({
...data.updateUser,
editing: false,
})
refetchUsers()
}}
>
{(updateUser, { loading }) => (
<Table.Row>
<Table.Cell>
<Input
style={{ width: '100%' }}
placeholder={user.username}
value={state.username}
onChange={e => updateInput(e, 'username')}
/>
</Table.Cell>
<Table.Cell>
<Input
style={{ width: '100%' }}
placeholder={user.rootPath}
value={state.rootPath}
onChange={e => updateInput(e, 'rootPath')}
/>
</Table.Cell>
<Table.Cell>
<Checkbox
toggle
checked={state.admin}
onChange={(_, data) => {
setState({
...state,
admin: data.checked,
})
}}
/>
</Table.Cell>
<Table.Cell>
<Button.Group>
<Button
negative
onClick={() =>
setState({
...state.oldState,
})
}
>
Cancel
</Button>
<Button
loading={loading}
disabled={loading}
positive
onClick={() =>
updateUser({
variables: {
id: user.id,
username: state.username,
rootPath: state.rootPath,
admin: state.admin,
},
})
}
>
Save
</Button>
</Button.Group>
</Table.Cell>
</Table.Row>
)}
</Mutation>
<Table.Row>
<Table.Cell>
<Input
style={{ width: '100%' }}
placeholder={user.username}
value={state.username}
onChange={e => updateInput(e, 'username')}
/>
</Table.Cell>
<Table.Cell>
<Input
style={{ width: '100%' }}
placeholder={user.rootPath}
value={state.rootPath}
onChange={e => updateInput(e, 'rootPath')}
/>
</Table.Cell>
<Table.Cell>
<Checkbox
toggle
checked={state.admin}
onChange={(_, data) => {
setState({
...state,
admin: data.checked,
})
}}
/>
</Table.Cell>
<Table.Cell>
<Button.Group>
<Button
negative
onClick={() =>
setState({
...state.oldState,
})
}
>
Cancel
</Button>
<Button
loading={updateUserLoading}
disabled={updateUserLoading}
positive
onClick={() =>
updateUser({
variables: {
id: user.id,
username: state.username,
rootPath: state.rootPath,
admin: state.admin,
},
})
}
>
Save
</Button>
</Button.Group>
</Table.Cell>
</Table.Row>
)
}
return (
<Mutation
mutation={deleteUserMutation}
onCompleted={() => {
refetchUsers()
}}
>
{deleteUser => (
<Table.Row>
<Table.Cell>{user.username}</Table.Cell>
<Table.Cell>{user.rootPath}</Table.Cell>
<Table.Cell>
{user.admin ? <Icon name="checkmark" size="large" /> : null}
</Table.Cell>
<Table.Cell>
<Button.Group>
<Button
onClick={() => {
setState({ ...state, editing: true, oldState: state })
}}
>
<Icon name="edit" />
Edit
</Button>
<Mutation mutation={scanUserMutation}>
{(scanUser, { called }) => (
<Button
disabled={called}
onClick={() => scanUser({ variables: { userId: user.id } })}
>
<Icon name="sync" />
Scan
</Button>
)}
</Mutation>
<Button onClick={() => setChangePassword(true)}>
<Icon name="key" />
Change password
</Button>
<ChangePasswordModal
user={user}
open={showChangePassword}
onClose={() => setChangePassword(false)}
/>
<Table.Row>
<Table.Cell>{user.username}</Table.Cell>
<Table.Cell>{user.rootPath}</Table.Cell>
<Table.Cell>
{user.admin ? <Icon name="checkmark" size="large" /> : null}
</Table.Cell>
<Table.Cell>
<Button.Group>
<Button
onClick={() => {
setState({ ...state, editing: true, oldState: state })
}}
>
<Icon name="edit" />
Edit
</Button>
<Button
disabled={scanUserCalled}
onClick={() => scanUser({ variables: { userId: user.id } })}
>
<Icon name="sync" />
Scan
</Button>
<Button onClick={() => setChangePassword(true)}>
<Icon name="key" />
Change password
</Button>
<ChangePasswordModal
user={user}
open={showChangePassword}
onClose={() => setChangePassword(false)}
/>
<Button
negative
onClick={() => {
setConfirmDelete(true)
}}
>
<Icon name="delete" />
Delete
</Button>
<Modal open={showConfirmDelete}>
<Modal.Header>Delete user</Modal.Header>
<Modal.Content>
<p>
{`Are you sure, you want to delete `}
<b>{user.username}</b>?
</p>
<p>{`This action cannot be undone`}</p>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => setConfirmDelete(false)}>Cancel</Button>
<Button
negative
onClick={() => {
setConfirmDelete(true)
setConfirmDelete(false)
deleteUser({
variables: {
id: user.id,
},
})
}}
>
<Icon name="delete" />
Delete
Delete {user.username}
</Button>
<Modal open={showComfirmDelete}>
<Modal.Header>Delete user</Modal.Header>
<Modal.Content>
<p>
{`Are you sure, you want to delete `}
<b>{user.username}</b>?
</p>
<p>{`This action cannot be undone`}</p>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => setConfirmDelete(false)}>
Cancel
</Button>
<Button
negative
onClick={() => {
setConfirmDelete(false)
deleteUser({
variables: {
id: user.id,
},
})
}}
>
Delete {user.username}
</Button>
</Modal.Actions>
</Modal>
</Button.Group>
</Table.Cell>
</Table.Row>
)}
</Mutation>
</Modal.Actions>
</Modal>
</Button.Group>
</Table.Cell>
</Table.Row>
)
}

View File

@ -49,19 +49,16 @@ const PhotoImg = photoProps => {
)
}
class LazyPhoto extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.src != this.props.src
}
render() {
const LazyPhoto = React.memo(
props => {
return (
<LazyLoad scrollContainer="#layout-content">
<PhotoImg {...this.props} />
<PhotoImg {...props} />
</LazyLoad>
)
}
}
},
(prevProps, nextProps) => prevProps.src === nextProps.src
)
LazyPhoto.propTypes = {
src: PropTypes.string,

View File

@ -1,8 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Route, Redirect } from 'react-router-dom'
import { gql } from '@apollo/client'
import { Query } from '@apollo/client/react/components'
import { useQuery, gql } from '@apollo/client'
import { authToken } from '../../authentication'
export const ADMIN_QUERY = gql`
@ -29,19 +28,13 @@ const AuthorizedRoute = ({ component: Component, admin = false, ...props }) => {
let adminRedirect = null
if (token && admin) {
adminRedirect = (
<Query query={ADMIN_QUERY}>
{({ error, data }) => {
if (error) alert(error)
const { error, data } = useQuery(ADMIN_QUERY)
if (data && data.myUser && !data.myUser.admin) {
return <Redirect to="/" />
}
if (error) alert(error)
return null
}}
</Query>
)
if (data && data.myUser && !data.myUser.admin) {
adminRedirect = <Redirect to="/" />
}
}
return (
@ -54,7 +47,7 @@ const AuthorizedRoute = ({ component: Component, admin = false, ...props }) => {
<Component {...routeProps} />
</>
)}
></Route>
/>
)
}

View File

@ -24,37 +24,35 @@ const SettingsPage = React.lazy(() =>
import('../../Pages/SettingsPage/SettingsPage')
)
class Routes extends React.Component {
render() {
return (
<React.Suspense
fallback={
<Layout>
<Loader active>Loading page</Loader>
</Layout>
}
>
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/logout">
{() => {
clearTokenCookie()
location.href = '/'
}}
</Route>
<Route path="/initialSetup" component={InitialSetupPage} />
<Route path="/share" component={SharePage} />
<AuthorizedRoute exact path="/albums" component={AlbumsPage} />
<AuthorizedRoute path="/album/:id/:subPage?" component={AlbumPage} />
<AuthorizedRoute path="/photos/:subPage?" component={PhotosPage} />
<AuthorizedRoute path="/places" component={PlacesPage} />
<AuthorizedRoute admin path="/settings" component={SettingsPage} />
<Route path="/" exact render={() => <Redirect to="/photos" />} />
<Route render={() => <div>Page not found</div>} />
</Switch>
</React.Suspense>
)
}
const Routes = () => {
return (
<React.Suspense
fallback={
<Layout>
<Loader active>Loading page</Loader>
</Layout>
}
>
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/logout">
{() => {
clearTokenCookie()
location.href = '/'
}}
</Route>
<Route path="/initialSetup" component={InitialSetupPage} />
<Route path="/share" component={SharePage} />
<AuthorizedRoute exact path="/albums" component={AlbumsPage} />
<AuthorizedRoute path="/album/:id/:subPage?" component={AlbumPage} />
<AuthorizedRoute path="/photos/:subPage?" component={PhotosPage} />
<AuthorizedRoute path="/places" component={PlacesPage} />
<AuthorizedRoute admin path="/settings" component={SettingsPage} />
<Route path="/" exact render={() => <Redirect to="/photos" />} />
<Route render={() => <div>Page not found</div>} />
</Switch>
</React.Suspense>
)
}
export default Routes

View File

@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Query } from '@apollo/client/react/components'
import { gql } from '@apollo/client'
import { useQuery, gql } from '@apollo/client'
import SidebarShare from './Sharing'
const albumQuery = gql`
@ -14,22 +13,20 @@ const albumQuery = gql`
`
const AlbumSidebar = ({ albumId }) => {
const { loading, error, data } = useQuery(albumQuery, {
variables: { id: albumId },
})
if (loading) return <div>Loading...</div>
if (error) return <div>{error.message}</div>
return (
<div>
<p>Album options</p>
<Query query={albumQuery} variables={{ id: albumId }}>
{({ loading, error, data }) => {
if (loading) return <div>Loading...</div>
if (error) return <div>{error.message}</div>
return (
<div>
<h1>{data.album.title}</h1>
<SidebarShare album={data.album} />
</div>
)
}}
</Query>
<div>
<h1>{data.album.title}</h1>
<SidebarShare album={data.album} />
</div>
</div>
)
}

View File

@ -1,4 +1,4 @@
import React, { createContext } from 'react'
import React, { createContext, useState } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Icon } from 'semantic-ui-react'
@ -41,42 +41,36 @@ const SidebarDismissButton = styled(Icon)`
export const SidebarContext = createContext()
SidebarContext.displayName = 'SidebarContext'
class Sidebar extends React.Component {
constructor(props) {
super(props)
const Sidebar = ({ children }) => {
const [state, setState] = useState({
content: null,
})
this.state = {
content: null,
}
this.update = content => {
this.setState({ content })
}
const update = content => {
setState({ content })
}
render() {
return (
<SidebarContext.Provider
value={{ updateSidebar: this.update, content: this.state.content }}
>
{this.props.children}
<SidebarContext.Consumer>
{value => (
<SidebarContainer highlighted={value.content != null}>
{value.content}
<SidebarDismissButton
name="angle double right"
size="big"
link
onClick={() => this.setState({ content: null })}
/>
<div style={{ height: 100 }}></div>
</SidebarContainer>
)}
</SidebarContext.Consumer>
</SidebarContext.Provider>
)
}
return (
<SidebarContext.Provider
value={{ updateSidebar: update, content: state.content }}
>
{children}
<SidebarContext.Consumer>
{value => (
<SidebarContainer highlighted={value.content != null}>
{value.content}
<SidebarDismissButton
name="angle double right"
size="big"
link
onClick={() => setState({ content: null })}
/>
<div style={{ height: 100 }}></div>
</SidebarContainer>
)}
</SidebarContext.Consumer>
</SidebarContext.Provider>
)
}
Sidebar.propTypes = {