Update go and node dependencies.

- Refactor UI to work with v6 of react-router
This commit is contained in:
viktorstrate
2022-01-31 22:32:17 +01:00
parent e509124510
commit 722e639bb1
22 changed files with 14263 additions and 12245 deletions

25838
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,20 +9,20 @@
"license": "GPL-3.0",
"description": "UI app for Photoview",
"dependencies": {
"@apollo/client": "^3.3.21",
"@babel/preset-typescript": "^7.14.5",
"@craco/craco": "^6.2.0",
"@headlessui/react": "^1.4.1",
"@react-aria/focus": "^3.4.0",
"@apollo/client": "^3.5.8",
"@babel/preset-typescript": "^7.16.7",
"@craco/craco": "^6.4.3",
"@headlessui/react": "^1.4.3",
"@react-aria/focus": "^3.5.0",
"@rollup/plugin-babel": "^5.3.0",
"@types/geojson": "^7946.0.8",
"@types/jest": "^26.0.24",
"@types/mapbox-gl": "^2.3.3",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.9",
"@types/react-helmet": "^6.1.2",
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.11",
"@types/jest": "^27.4.0",
"@types/mapbox-gl": "^2.6.0",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-helmet": "^6.1.5",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.21",
"@types/url-join": "^4.0.1",
"autoprefixer": "^9.8.6",
"babel-plugin-graphql-tag": "^3.3.0",
@@ -30,28 +30,28 @@
"connect-history-api-fallback": "^1.6.0",
"copy-to-clipboard": "^3.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest-dom": "^3.9.0",
"eslint-plugin-jest-dom": "^4.0.1",
"fs-extra": "^10.0.0",
"i18next": "^20.3.2",
"mapbox-gl": "^2.3.1",
"i18next": "^21.6.10",
"mapbox-gl": "^2.6.1",
"postcss": "^7.0.36",
"prettier": "^2.3.2",
"prop-types": "^15.7.2",
"prettier": "^2.5.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.11.0",
"react-i18next": "^11.11.1",
"react-router-dom": "^5.2.0",
"react-hook-form": "^7.25.3",
"react-i18next": "^11.15.3",
"react-router-dom": "^6.2.1",
"react-router-prop-types": "^1.0.5",
"react-scripts": "^4.0.3",
"react-spring": "^8.0.27",
"react-test-renderer": "^17.0.2",
"styled-components": "^5.3.0",
"subscriptions-transport-ws": "^0.9.19",
"tailwind-override": "^0.2.3",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4",
"typescript": "^4.3.5",
"styled-components": "^5.3.3",
"subscriptions-transport-ws": "^0.11.0",
"tailwind-override": "^0.6.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
"typescript": "^4.5.5",
"url-join": "^4.0.1"
},
"scripts": {
@@ -69,15 +69,15 @@
"prepare": "(cd .. && npx husky install)"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.1.9",
"apollo": "2.33.4",
"apollo-language-server": "1.26.3",
"husky": "^6.0.0",
"i18next-parser": "^4.2.0",
"lint-staged": "^11.0.1",
"tsc-files": "^1.1.2"
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"apollo": "2.33.9",
"apollo-language-server": "1.26.7",
"husky": "^7.0.4",
"i18next-parser": "^5.4.0",
"lint-staged": "^12.3.2",
"tsc-files": "1.1.2"
},
"prettier": {
"trailingComma": "es5",

View File

@@ -8,6 +8,8 @@ import PaginateLoader from '../../components/PaginateLoader'
import { useTranslation } from 'react-i18next'
import { albumQuery, albumQueryVariables } from './__generated__/albumQuery'
import useOrderingParams from '../../hooks/useOrderingParams'
import { useParams } from 'react-router-dom'
import { isNil } from '../../helpers/utils'
const ALBUM_QUERY = gql`
query albumQuery(
@@ -61,17 +63,10 @@ const ALBUM_QUERY = gql`
let refetchNeededAll = false
let refetchNeededFavorites = false
type AlbumPageProps = {
match: {
params: {
id: string
subPage: string
}
}
}
function AlbumPage({ match }: AlbumPageProps) {
const albumId = match.params.id
function AlbumPage() {
const { id: albumId } = useParams()
if (isNil(albumId))
throw new Error('Expected parameter `id` to be defined for AlbumPage')
const { t } = useTranslation()

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { gql, useQuery, useMutation } from '@apollo/client'
import { Redirect } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import { Container } from './loginUtilities'
import { checkInitialSetupQuery, login } from './loginUtilities'
@@ -37,6 +37,7 @@ type InitialSetupFormData = {
const InitialSetupPage = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const {
register,
@@ -45,16 +46,18 @@ const InitialSetupPage = () => {
} = useForm<InitialSetupFormData>()
if (authToken()) {
return <Redirect to="/" />
navigate('/')
return null
}
const { data: initialSetupData } = useQuery<CheckInitialSetup>(
checkInitialSetupQuery
)
const initialSetupRedirect = initialSetupData?.siteInfo
?.initialSetup ? null : (
<Redirect to="/" />
)
if (initialSetupData?.siteInfo?.initialSetup) {
navigate('/')
return null
}
const [authorize, { loading: authorizeLoading, data: authorizationData }] =
useMutation(initialSetupMutation, {
@@ -84,7 +87,6 @@ const InitialSetupPage = () => {
return (
<div>
{initialSetupRedirect}
<Container>
<h1 className="text-center text-xl">
{t('login_page.initial_setup.title', 'Initial Setup')}

View File

@@ -1,12 +1,12 @@
import React from 'react'
import { useQuery, gql, useMutation } from '@apollo/client'
import { useForm, SubmitHandler } from 'react-hook-form'
import { useForm } from 'react-hook-form'
import { checkInitialSetupQuery, login } from './loginUtilities'
import { authToken } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next'
import { Helmet } from 'react-helmet'
import { Redirect } from 'react-router'
import { useNavigate } from 'react-router'
import { TextField } from '../../primitives/form/Input'
import MessageBox from '../../primitives/form/MessageBox'
import { CheckInitialSetup } from './__generated__/CheckInitialSetup'
@@ -45,7 +45,7 @@ const LoginForm = () => {
register,
handleSubmit,
formState: { errors: formErrors },
} = useForm()
} = useForm<LoginInputs>()
const [authorize, { loading, data }] = useMutation<
Authorize,
@@ -60,7 +60,7 @@ const LoginForm = () => {
},
})
const onSubmit: SubmitHandler<LoginInputs> = data => {
const onSubmit = (data: LoginInputs) => {
authorize({
variables: {
username: data.username,
@@ -127,13 +127,20 @@ type LoginInputs = {
const LoginPage = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const { data: initialSetupData } = useQuery<CheckInitialSetup>(
checkInitialSetupQuery
)
if (authToken()) {
return <Redirect to="/" />
navigate('/')
return null
}
if (initialSetupData?.siteInfo?.initialSetup) {
navigate('initialSetup')
return null
}
return (
@@ -141,9 +148,6 @@ const LoginPage = () => {
<Helmet>
<title>{t('title.login', 'Login')} - Photoview</title>
</Helmet>
{initialSetupData?.siteInfo?.initialSetup && (
<Redirect to="/initialSetup" />
)}
<div>
<LogoHeader />
<LoginForm />

View File

@@ -2,7 +2,7 @@ import React, { createRef, useEffect, useState } from 'react'
import { gql, useMutation, useQuery } from '@apollo/client'
import Layout from '../../components/layout/Layout'
import styled from 'styled-components'
import { Link } from 'react-router-dom'
import { Link, useParams } from 'react-router-dom'
import SingleFaceGroup from './SingleFaceGroup/SingleFaceGroup'
import { Button, TextField } from '../../primitives/form/Input'
import FaceCircleImage from './FaceCircleImage'
@@ -19,7 +19,7 @@ import {
myFaces_myFaceGroups,
} from './__generated__/myFaces'
import { recognizeUnlabeledFaces } from './__generated__/recognizeUnlabeledFaces'
import { tailwindClassNames } from '../../helpers/utils'
import { isNil, tailwindClassNames } from '../../helpers/utils'
export const MY_FACES_QUERY = gql`
query myFaces($limit: Int, $offset: Int) {
@@ -219,7 +219,10 @@ const FaceGroupsWrapper = styled.div`
margin-top: 24px;
`
const PeopleGallery = () => {
/**
* Renders a page that shows a gallery of all people
*/
export const PeoplePage = () => {
const { t } = useTranslation()
const { data, error, loading, fetchMore } = useQuery<
myFaces,
@@ -275,26 +278,18 @@ const PeopleGallery = () => {
)
}
type PeoplePageProps = {
match: {
params: {
person?: string
}
}
}
const PeoplePage = ({ match }: PeoplePageProps) => {
/**
* Renders a page for an individual person
*/
export const PersonPage = () => {
const { t } = useTranslation()
const faceGroup = match.params.person
if (faceGroup) {
return (
<Layout title={t('title.people', 'People')}>
<SingleFaceGroup faceGroupID={faceGroup} />
</Layout>
)
} else {
return <PeopleGallery />
}
}
const { person } = useParams()
if (isNil(person))
throw new Error('Expected `person` parameter to be defined')
export default PeoplePage
return (
<Layout title={t('title.people', 'People')}>
<SingleFaceGroup faceGroupID={person} />
</Layout>
)
}

View File

@@ -1,7 +1,7 @@
import { BaseMutationOptions, gql, useMutation } from '@apollo/client'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import { isNil } from '../../../helpers/utils'
import Modal from '../../../primitives/Modal'
import { MY_FACES_QUERY } from '../PeoplePage'
@@ -78,7 +78,7 @@ const DetachImageFacesModal = ({
const [selectedImageFaces, setSelectedImageFaces] = useState<
(myFaces_myFaceGroups_imageFaces | singleFaceGroup_faceGroup_imageFaces)[]
>([])
const history = useHistory()
const navigate = useNavigate()
const detachImageFacesMutation = useDetachImageFaces({
refetchQueries: [
@@ -92,7 +92,7 @@ const DetachImageFacesModal = ({
detachImageFacesMutation(selectedImageFaces).then(({ data }) => {
if (isNil(data)) throw new Error('Expected data not to be null')
setOpen(false)
history.push(`/people/${data.detachImageFaces.id}`)
navigate(`/people/${data.detachImageFaces.id}`)
})
}

View File

@@ -1,7 +1,7 @@
import { gql, PureQueryOptions, useMutation, useQuery } from '@apollo/client'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import { isNil } from '../../../helpers/utils'
import Modal from '../../../primitives/Modal'
import { MY_FACES_QUERY } from '../PeoplePage'
@@ -50,7 +50,7 @@ const MergeFaceGroupsModal = ({
myFaces_myFaceGroups | singleFaceGroup_faceGroup | null
>(null)
const history = useHistory()
const navigate = useNavigate()
const { data } = useQuery<myFaces, myFacesVariables>(MY_FACES_QUERY)
const [combineFacesMutation] = useMutation<
combineFaces,
@@ -74,7 +74,7 @@ const MergeFaceGroupsModal = ({
},
}).then(() => {
setOpen(false)
history.push(`/people/${selectedFaceGroup.id}`)
navigate(`/people/${selectedFaceGroup.id}`)
})
}

View File

@@ -1,6 +1,6 @@
import { gql, useLazyQuery, useMutation } from '@apollo/client'
import React, { useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import SelectFaceGroupTable from './SelectFaceGroupTable'
import SelectImageFacesTable from './SelectImageFacesTable'
import { MY_FACES_QUERY } from '../PeoplePage'
@@ -61,7 +61,7 @@ const MoveImageFacesModal = ({
myFaces_myFaceGroups | singleFaceGroup_faceGroup | null
>(null)
const [imagesSelected, setImagesSelected] = useState(false)
const history = useHistory()
const navigate = useNavigate()
const [moveImageFacesMutation] = useMutation<
moveImageFaces,
@@ -115,7 +115,7 @@ const MoveImageFacesModal = ({
},
}).then(() => {
setOpen(false)
history.push(`/people/${selectedFaceGroup.id}`)
navigate(`/people/${selectedFaceGroup.id}`)
})
}

View File

@@ -0,0 +1,27 @@
/**
* Light lazy-loadable wrapper for the people module
*/
import React from 'react'
import { Route } from 'react-router-dom'
const PeoplePage = React.lazy(() =>
import('./PeoplePage').then(x => ({
default: x.PeoplePage,
}))
)
const PersonPage = React.lazy(() =>
import('./PeoplePage').then(x => ({
default: x.PersonPage,
}))
)
const peoplePageRoute = () => (
<>
<Route path=":person" element={<PersonPage />} />
<Route index element={<PeoplePage />} />
</>
)
export default peoplePageRoute

View File

@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import { useQuery, gql } from '@apollo/client'
import { match as MatchType, Route, Switch } from 'react-router-dom'
import { Route, useParams } from 'react-router-dom'
import styled from 'styled-components'
import {
getSharePassword,
@@ -11,6 +10,7 @@ import AlbumSharePage from './AlbumSharePage'
import MediaSharePage from './MediaSharePage'
import { useTranslation } from 'react-i18next'
import PasswordProtectedShare from './PasswordProtectedShare'
import { isNil } from '../../helpers/utils'
export const SHARE_TOKEN_QUERY = gql`
query SharePageToken($token: String!, $password: String) {
@@ -77,10 +77,16 @@ export const VALIDATE_TOKEN_PASSWORD_QUERY = gql`
}
`
const AuthorizedTokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
const tokenFromParams = () => {
const { token } = useParams()
if (isNil(token)) throw new Error('Expected `token` param to be defined')
return token
}
const AuthorizedTokenRoute = () => {
const { t } = useTranslation()
const token = match.params.token
const token = tokenFromParams()
const password = getSharePassword(token)
const { loading, error, data } = useQuery(SHARE_TOKEN_QUERY, {
@@ -94,35 +100,30 @@ const AuthorizedTokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
if (loading) return <div>{t('general.loading.default', 'Loading...')}</div>
if (data.shareToken.album) {
const SharedSubAlbumPage = ({ match }: MatchProps<SubalbumRouteMatch>) => {
const SharedSubAlbumPage = () => {
const { subAlbum } = useParams()
if (isNil(subAlbum))
throw new Error('Expected `subAlbum` param to be defined')
return (
<AlbumSharePage
albumID={match.params.subAlbum}
token={token}
password={password}
/>
<AlbumSharePage albumID={subAlbum} token={token} password={password} />
)
}
SharedSubAlbumPage.propTypes = {
match: PropTypes.any,
}
return (
<Switch>
<>
<Route path=":subAlbum" element={<SharedSubAlbumPage />} />
<Route
exact
path={`${match.path}/:subAlbum`}
component={SharedSubAlbumPage}
path="/"
element={
<AlbumSharePage
albumID={data.shareToken.album.id}
token={token}
password={password}
/>
}
/>
<Route exact path={match.path}>
<AlbumSharePage
albumID={data.shareToken.album.id}
token={token}
password={password}
/>
</Route>
</Switch>
</>
)
}
@@ -133,39 +134,22 @@ const AuthorizedTokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
return <h1>{t('share_page.share_not_found', 'Share not found')}</h1>
}
AuthorizedTokenRoute.propTypes = {
match: PropTypes.object.isRequired,
}
export const MessageContainer = styled.div`
max-width: 400px;
margin: 100px auto 0;
`
interface TokenRouteMatch {
token: string
}
interface SubalbumRouteMatch extends TokenRouteMatch {
subAlbum: string
}
interface MatchProps<Route> {
match: MatchType<Route>
}
const TokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
export const TokenRoute = () => {
const { t } = useTranslation()
const token = match.params.token
const token = tokenFromParams()
const { loading, error, data, refetch } = useQuery(
VALIDATE_TOKEN_PASSWORD_QUERY,
{
notifyOnNetworkStatusChange: true,
variables: {
token: match.params.token,
password: getSharePassword(match.params.token),
token: token,
password: getSharePassword(token),
},
}
)
@@ -202,22 +186,5 @@ const TokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
if (loading) return <div>{t('general.loading.default', 'Loading...')}</div>
return <AuthorizedTokenRoute match={match} />
return <AuthorizedTokenRoute />
}
const SharePage = ({ match }: { match: MatchType }) => {
const { t } = useTranslation()
return (
<Switch>
<Route path={`${match.url}/:token`}>
{({ match }: { match: MatchType<TokenRouteMatch> }) => {
return <TokenRoute match={match} />
}}
</Route>
<Route path="/">{t('routes.page_not_found', 'Page not found')}</Route>
</Switch>
)
}
export default SharePage

View File

@@ -0,0 +1,25 @@
/**
* Light lazy-loadable wrapper for share page module
*/
import React from 'react'
import type { TFunction } from 'react-i18next'
import { Route } from 'react-router-dom'
import { NotFoundPage } from '../../components/routes/Routes'
const TokenRoute = React.lazy(() =>
import('./SharePage').then(x => ({
default: x.TokenRoute,
}))
)
const sharePageRoute = ({ t }: { t: TFunction }) => {
return (
<>
<Route path={':token'} element={<TokenRoute />} />
<Route index element={<NotFoundPage t={t} />} />
</>
)
}
export default sharePageRoute

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components'
import { useLazyQuery, gql } from '@apollo/client'
import { debounce, DebouncedFn } from '../../helpers/utils'
import { ProtectedImage } from '../photoGallery/ProtectedMedia'
import { NavLink, useHistory, useRouteMatch } from 'react-router-dom'
import { NavLink, useNavigate, useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import {
searchQuery,
@@ -77,11 +77,11 @@ const SearchBar = () => {
}
}
const routerMatch = useRouteMatch()
const location = useLocation()
useEffect(() => {
setExpanded(false)
setQuery('')
}, [routerMatch])
}, [location])
const [selectedItem, setSelectedItem] = useState<number | null>(null)
@@ -291,13 +291,11 @@ const SearchRow = ({
setSelected,
}: SearchRowProps) => {
const rowEl = useRef<HTMLLIElement>(null)
const history = useHistory()
const navigate = useNavigate()
useEffect(() => {
const keydownEvent = (event: KeyboardEvent) => {
if (event.key == 'Enter') {
history.push(link)
}
if (event.key == 'Enter') navigate(link)
}
document.addEventListener('keydown', keydownEvent)

View File

@@ -4,6 +4,7 @@ import { useQuery, gql } from '@apollo/client'
import { authToken } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next'
import { mapboxEnabledQuery } from '../../__generated__/mapboxEnabledQuery'
import classNames from 'classnames'
export const MAPBOX_QUERY = gql`
query mapboxEnabledQuery {
@@ -41,9 +42,12 @@ const MenuButton = ({
return (
<NavLink
to={to}
exact={exact}
className={`rounded-lg my-2 ${className}`}
activeClassName={`ring-4 lg:ring-4 ${activeClasses}`}
end={exact}
className={({ isActive }) =>
classNames(`rounded-lg my-2`, className, {
[`ring-4 lg:ring-4 ${activeClasses}`]: isActive,
})
}
>
<li className="flex items-center">
<div

View File

@@ -1,18 +1,17 @@
import React, { ReactChild, useEffect } from 'react'
import PropTypes, { ReactComponentLike } from 'prop-types'
import { Route, Redirect, RouteProps } from 'react-router-dom'
import React, { useEffect } from 'react'
import { RouteProps, Navigate } from 'react-router-dom'
import { useLazyQuery } from '@apollo/client'
import { authToken } from '../../helpers/authentication'
import { ADMIN_QUERY } from '../layout/Layout'
export const useIsAdmin = (enabled = true) => {
export const useIsAdmin = () => {
const [fetchAdminQuery, { data, called }] = useLazyQuery(ADMIN_QUERY)
useEffect(() => {
if (authToken() && !called && enabled) {
if (authToken() && !called) {
fetchAdminQuery()
}
}, [authToken(), enabled])
}, [authToken()])
if (!authToken()) {
return false
@@ -28,47 +27,23 @@ export const Authorized = ({ children }: { children: JSX.Element }) => {
}
interface AuthorizedRouteProps extends Omit<RouteProps, 'component'> {
component: ReactComponentLike
children: React.ReactNode
admin?: boolean
}
const AuthorizedRoute = ({
component: Component,
admin = false,
...props
}: AuthorizedRouteProps) => {
const AuthorizedRoute = ({ admin = false, children }: AuthorizedRouteProps) => {
const token = authToken()
const isAdmin = useIsAdmin(admin)
const isAdmin = useIsAdmin()
let unauthorizedRedirect: null | ReactChild = null
if (!token) {
unauthorizedRedirect = <Redirect to="/login" />
return <Navigate to="/" />
}
let adminRedirect: null | ReactChild = null
if (token && admin) {
if (isAdmin === false) {
adminRedirect = <Redirect to="/" />
}
if (admin && !isAdmin) {
return <Navigate to="/" />
}
return (
<Route
{...props}
render={routeProps => (
<>
{unauthorizedRedirect}
{adminRedirect}
<Component {...routeProps} />
</>
)}
/>
)
}
AuthorizedRoute.propTypes = {
component: PropTypes.elementType.isRequired,
admin: PropTypes.bool,
return <>{children}</>
}
export default AuthorizedRoute

View File

@@ -1,12 +1,20 @@
import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import {
Route,
Routes as RouterRoutes,
Navigate,
useNavigate,
NavigateFunction,
Outlet,
} from 'react-router-dom'
import Layout from '../layout/Layout'
import { clearTokenCookie } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next'
import { authToken, clearTokenCookie } from '../../helpers/authentication'
import { TFunction, useTranslation } from 'react-i18next'
import Loader from '../../primitives/Loader'
const AuthorizedRoute = React.lazy(() => import('./AuthorizedRoute'))
import AuthorizedRoute from './AuthorizedRoute'
import sharePageRoute from '../../Pages/SharePage/sharePageRoute'
import peoplePageRoute from '../../Pages/PeoplePage/peoplePageRoute'
const AlbumsPage = React.lazy(
() => import('../../Pages/AllAlbumsPage/AlbumsPage')
@@ -16,8 +24,6 @@ const TimelinePage = React.lazy(
() => import('../../Pages/TimelinePage/TimelinePage')
)
const PlacesPage = React.lazy(() => import('../../Pages/PlacesPage/PlacesPage'))
const SharePage = React.lazy(() => import('../../Pages/SharePage/SharePage'))
const PeoplePage = React.lazy(() => import('../../Pages/PeoplePage/PeoplePage'))
const LoginPage = React.lazy(() => import('../../Pages/LoginPage/LoginPage'))
const InitialSetupPage = React.lazy(
@@ -30,6 +36,7 @@ const SettingsPage = React.lazy(
const Routes = () => {
const { t } = useTranslation()
const navigate = useNavigate()
return (
<React.Suspense
@@ -39,37 +46,86 @@ const Routes = () => {
</Layout>
}
>
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/logout">
{() => {
clearTokenCookie()
location.href = '/'
}}
<RouterRoutes>
<Route path="/login" element={<LoginPage />} />
<Route path="/logout" element={<LogoutPage navigate={navigate} />} />
<Route path="/initialSetup" element={<InitialSetupPage />} />
<Route path="/share">{sharePageRoute({ t })}</Route>
<Route
path="/albums"
element={
<AuthorizedRoute>
<AlbumsPage />
</AuthorizedRoute>
}
/>
<Route
path="/album/:id"
element={
<AuthorizedRoute>
<AlbumPage />
</AuthorizedRoute>
}
/>
<Route
path="/timeline"
element={
<AuthorizedRoute>
<TimelinePage />
</AuthorizedRoute>
}
/>
<Route
path="/places"
element={
<AuthorizedRoute>
<PlacesPage />
</AuthorizedRoute>
}
/>
<Route
path="/people"
element={
<AuthorizedRoute>
<Outlet />
</AuthorizedRoute>
}
>
{peoplePageRoute()}
</Route>
<Route path="/initialSetup" component={InitialSetupPage} />
<Route path="/share" component={SharePage} />
<AuthorizedRoute exact path="/albums" component={AlbumsPage} />
<AuthorizedRoute path="/album/:id" component={AlbumPage} />
<AuthorizedRoute path="/timeline" component={TimelinePage} />
<AuthorizedRoute path="/places" component={PlacesPage} />
<AuthorizedRoute path="/people/:person?" component={PeoplePage} />
<AuthorizedRoute path="/settings" component={SettingsPage} />
<Route path="/" exact render={() => <Redirect to="/timeline" />} />
<Route
path="/settings"
element={
<AuthorizedRoute>
<SettingsPage />
</AuthorizedRoute>
}
/>
<Route index element={<IndexPage />} />
{/* For backwards compatibility */}
<Route
path="/photos"
exact
render={() => <Redirect to="/timeline" />}
/>
<Route
render={() => (
<div>{t('routes.page_not_found', 'Page not found')}</div>
)}
/>
</Switch>
<Route path="/photos" element={<Navigate to="/timeline" />} />
<Route element={<NotFoundPage t={t} />} />
</RouterRoutes>
</React.Suspense>
)
}
const IndexPage = () => {
const token = authToken()
const dest = token ? '/timeline' : '/login'
return <Navigate to={dest} />
}
export const NotFoundPage = ({ t }: { t: TFunction }) => {
return <div>{t('routes.page_not_found', 'Page not found')}</div>
}
const LogoutPage = ({ navigate }: { navigate: NavigateFunction }) => {
clearTokenCookie()
navigate('/')
return null
}
export default Routes

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link, useHistory } from 'react-router-dom'
import { Link, useNavigate } from 'react-router-dom'
import FaceCircleImage from '../../../Pages/PeoplePage/FaceCircleImage'
import { SidebarSection, SidebarSectionTitle } from '../SidebarComponents'
import { MediaSidebarMedia, SIDEBAR_MEDIA_QUERY } from './MediaSidebar'
@@ -73,7 +73,7 @@ const PersonMoreMenu = ({
},
]
const history = useHistory()
const navigate = useNavigate()
const detachImageFaceMutation = useDetachImageFaces({
refetchQueries,
})
@@ -107,7 +107,7 @@ const PersonMoreMenu = ({
return
detachImageFaceMutation([face]).then(({ data }) => {
if (isNil(data)) throw new Error('Expected data not to be null')
history.push(`/people/${data.detachImageFaces.id}`)
navigate(`/people/${data.detachImageFaces.id}`)
})
}

View File

@@ -56,6 +56,7 @@ const CheckboxLabelWrapper = styled.label`
type CheckboxProps = React.InputHTMLAttributes<HTMLInputElement> & {
label: string
className?: string
}
const Checkbox = ({ label, className, ...props }: CheckboxProps) => {

View File

@@ -19,6 +19,7 @@ type DropdownProps = React.SelectHTMLAttributes<HTMLSelectElement> & {
items: DropdownItem[]
selected?: string
setSelected(value: string): void
className?: string
}
const Dropdown = ({

View File

@@ -141,6 +141,7 @@ export const TextField = forwardRef(
type ButtonProps = {
variant?: 'negative' | 'positive' | 'default'
background?: 'default' | 'white'
className?: string
}
const buttonStyles = ({ variant, background }: ButtonProps) =>
@@ -155,6 +156,7 @@ const buttonStyles = ({ variant, background }: ButtonProps) =>
type SubmitProps = ButtonProps & {
children: string
className?: string
}
export const Submit = ({