From ba01927707b437c9fd118be535fefd8cf384e1cc Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Tue, 1 Feb 2022 12:54:00 +0100
Subject: [PATCH] Rework Routes.tsx and SharePage.tsx routing

---
 ui/src/Pages/SharePage/SharePage.test.js  |  98 ++++++++++++--
 ui/src/Pages/SharePage/SharePage.tsx      |   8 +-
 ui/src/Pages/SharePage/sharePageRoute.tsx |  25 ----
 ui/src/components/routes/Routes.tsx       | 154 +++++++++++++---------
 4 files changed, 181 insertions(+), 104 deletions(-)
 delete mode 100644 ui/src/Pages/SharePage/sharePageRoute.tsx

diff --git a/ui/src/Pages/SharePage/SharePage.test.js b/ui/src/Pages/SharePage/SharePage.test.js
index e80bad9..dff8e93 100644
--- a/ui/src/Pages/SharePage/SharePage.test.js
+++ b/ui/src/Pages/SharePage/SharePage.test.js
@@ -1,5 +1,5 @@
 import React from 'react'
-import { MemoryRouter } from 'react-router-dom'
+import { MemoryRouter, Routes, Route } from 'react-router-dom'
 import { MockedProvider } from '@apollo/client/testing'
 
 import {
@@ -8,8 +8,9 @@ import {
   waitForElementToBeRemoved,
 } from '@testing-library/react'
 
-import SharePage, {
+import {
   SHARE_TOKEN_QUERY,
+  TokenRoute,
   VALIDATE_TOKEN_PASSWORD_QUERY,
 } from './SharePage'
 
@@ -21,13 +22,6 @@ jest.mock('../../hooks/useScrollPagination')
 describe('load correct share page, based on graphql query', () => {
   const token = 'TOKEN123'
 
-  const matchMock = {
-    url: `/share`,
-    params: {},
-    path: `/share`,
-    isExact: false,
-  }
-
   const historyMock = [{ pathname: `/share/${token}` }]
 
   const graphqlMocks = [
@@ -101,7 +95,9 @@ describe('load correct share page, based on graphql query', () => {
         }}
       >
         <MemoryRouter initialEntries={historyMock}>
-          <SharePage match={matchMock} />
+          <Routes>
+            <Route path="/share/:token" element={<TokenRoute />} />
+          </Routes>
         </MemoryRouter>
       </MockedProvider>
     )
@@ -176,13 +172,93 @@ describe('load correct share page, based on graphql query', () => {
         }}
       >
         <MemoryRouter initialEntries={historyMock}>
-          <SharePage match={matchMock} />
+          <Routes>
+            <Route path="/share/:token" element={<TokenRoute />} />
+          </Routes>
         </MemoryRouter>
       </MockedProvider>
     )
 
     expect(screen.getByText('Loading...')).toBeInTheDocument()
+    await waitForElementToBeRemoved(() => screen.getByText('Loading...'))
 
+    expect(screen.getByTestId('Layout')).toBeInTheDocument()
+    expect(screen.getByTestId('AlbumSharePage')).toBeInTheDocument()
+  })
+
+  test('load subalbum of a shared album', async () => {
+    const subalbumID = '456'
+    const subalbumHistoryMock = [{ pathname: `/share/${token}/${subalbumID}` }]
+
+    const subalbumPageMocks = [
+      {
+        request: {
+          query: SHARE_TOKEN_QUERY,
+          variables: {
+            token,
+            password: null,
+          },
+        },
+        result: {
+          data: {
+            shareToken: {
+              token: token,
+              album: {
+                id: subalbumID,
+              },
+              media: null,
+            },
+          },
+        },
+      },
+      {
+        request: {
+          query: SHARE_ALBUM_QUERY,
+          variables: {
+            id: subalbumID,
+            token: token,
+            password: null,
+            limit: 200,
+            offset: 0,
+            mediaOrderBy: 'date_shot',
+            mediaOrderDirection: 'ASC',
+          },
+        },
+        result: {
+          data: {
+            album: {
+              id: '1',
+              title: 'album_title',
+              subAlbums: [],
+              thumbnail: {
+                url: 'https://photoview.example.com/album_thumbnail.jpg',
+              },
+              media: [],
+            },
+          },
+        },
+      },
+    ]
+
+    render(
+      <MockedProvider
+        mocks={[...graphqlMocks, ...subalbumPageMocks]}
+        addTypename={false}
+        defaultOptions={{
+          // disable cache, required to make fragments work
+          watchQuery: { fetchPolicy: 'no-cache' },
+          query: { fetchPolicy: 'no-cache' },
+        }}
+      >
+        <MemoryRouter initialEntries={subalbumHistoryMock}>
+          <Routes>
+            <Route path="/share/:token/*" element={<TokenRoute />} />
+          </Routes>
+        </MemoryRouter>
+      </MockedProvider>
+    )
+
+    expect(screen.getByText('Loading...')).toBeInTheDocument()
     await waitForElementToBeRemoved(() => screen.getByText('Loading...'))
 
     expect(screen.getByTestId('Layout')).toBeInTheDocument()
diff --git a/ui/src/Pages/SharePage/SharePage.tsx b/ui/src/Pages/SharePage/SharePage.tsx
index 071a89f..4828e26 100644
--- a/ui/src/Pages/SharePage/SharePage.tsx
+++ b/ui/src/Pages/SharePage/SharePage.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import { useQuery, gql } from '@apollo/client'
-import { Route, useParams } from 'react-router-dom'
+import { Route, Routes, useParams } from 'react-router-dom'
 import styled from 'styled-components'
 import {
   getSharePassword,
@@ -111,10 +111,10 @@ const AuthorizedTokenRoute = () => {
     }
 
     return (
-      <>
+      <Routes>
         <Route path=":subAlbum" element={<SharedSubAlbumPage />} />
         <Route
-          path="/"
+          index
           element={
             <AlbumSharePage
               albumID={data.shareToken.album.id}
@@ -123,7 +123,7 @@ const AuthorizedTokenRoute = () => {
             />
           }
         />
-      </>
+      </Routes>
     )
   }
 
diff --git a/ui/src/Pages/SharePage/sharePageRoute.tsx b/ui/src/Pages/SharePage/sharePageRoute.tsx
deleted file mode 100644
index ad9cb7a..0000000
--- a/ui/src/Pages/SharePage/sharePageRoute.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * 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
diff --git a/ui/src/components/routes/Routes.tsx b/ui/src/components/routes/Routes.tsx
index 2f94992..a4e550a 100644
--- a/ui/src/components/routes/Routes.tsx
+++ b/ui/src/components/routes/Routes.tsx
@@ -1,11 +1,10 @@
 import React from 'react'
 import {
-  Route,
-  Routes as RouterRoutes,
   Navigate,
   useNavigate,
   NavigateFunction,
   Outlet,
+  useRoutes,
 } from 'react-router-dom'
 
 import Layout from '../layout/Layout'
@@ -13,8 +12,6 @@ import { authToken, clearTokenCookie } from '../../helpers/authentication'
 import { TFunction, useTranslation } from 'react-i18next'
 import Loader from '../../primitives/Loader'
 import AuthorizedRoute from './AuthorizedRoute'
-import sharePageRoute from '../../Pages/SharePage/sharePageRoute'
-import peoplePageRoute from '../../Pages/PeoplePage/peoplePageRoute'
 
 const AlbumsPage = React.lazy(
   () => import('../../Pages/AllAlbumsPage/AlbumsPage')
@@ -30,14 +27,102 @@ const InitialSetupPage = React.lazy(
   () => import('../../Pages/LoginPage/InitialSetupPage')
 )
 
+const SharePageTokenRoute = React.lazy(() =>
+  import('../../Pages/SharePage/SharePage').then(x => ({
+    default: x.TokenRoute,
+  }))
+)
+
 const SettingsPage = React.lazy(
   () => import('../../Pages/SettingsPage/SettingsPage')
 )
 
+const PeoplePage = React.lazy(() =>
+  import('../../Pages/PeoplePage/PeoplePage').then(x => ({
+    default: x.PeoplePage,
+  }))
+)
+
+const PersonPage = React.lazy(() =>
+  import('../../Pages/PeoplePage/PeoplePage').then(x => ({
+    default: x.PersonPage,
+  }))
+)
+
 const Routes = () => {
   const { t } = useTranslation()
   const navigate = useNavigate()
 
+  const authorized = (element: React.ReactNode) => (
+    <AuthorizedRoute>{element}</AuthorizedRoute>
+  )
+
+  const routes = useRoutes([
+    {
+      index: true,
+      element: <IndexPage />,
+    },
+    {
+      path: '/login',
+      element: <LoginPage />,
+    },
+    {
+      path: '/logout',
+      element: <LogoutPage navigate={navigate} />,
+    },
+    {
+      path: '/initialSetup',
+      element: <InitialSetupPage />,
+    },
+    {
+      path: '/share/:token/*',
+      element: <SharePageTokenRoute />,
+    },
+    {
+      path: '/albums',
+      element: authorized(<AlbumsPage />),
+    },
+    {
+      path: '/album/:id',
+      element: authorized(<AlbumPage />),
+    },
+    {
+      path: '/timeline',
+      element: authorized(<TimelinePage />),
+    },
+    {
+      path: '/places',
+      element: authorized(<PlacesPage />),
+    },
+    {
+      path: '/settings',
+      element: authorized(<SettingsPage />),
+    },
+    {
+      path: '/people',
+      element: authorized(<Outlet />),
+      children: [
+        {
+          path: ':person',
+          element: <PersonPage />,
+        },
+        {
+          index: true,
+          element: <PeoplePage />,
+        },
+      ],
+    },
+    {
+      // for backwards-compatibility
+      path: '/photos',
+      element: <Navigate to="/timeline" />,
+    },
+    {
+      path: '*',
+      element: <NotFoundPage t={t} />,
+    },
+  ])
+
   return (
     <React.Suspense
       fallback={
@@ -46,66 +131,7 @@ const Routes = () => {
         </Layout>
       }
     >
-      <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="/settings"
-          element={
-            <AuthorizedRoute>
-              <SettingsPage />
-            </AuthorizedRoute>
-          }
-        />
-        <Route index element={<IndexPage />} />
-        {/* For backwards compatibility */}
-        <Route path="/photos" element={<Navigate to="/timeline" />} />
-        <Route element={<NotFoundPage t={t} />} />
-      </RouterRoutes>
+      {routes}
     </React.Suspense>
   )
 }
