From f09ba14a0e937b8dc6a2af239bef0f54e50dbe71 Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Tue, 13 Apr 2021 16:43:56 +0200
Subject: [PATCH] Fix translations + fix build reload + more Typescript

---
 .gitignore                                    |   1 +
 ui/babel.config.js                            |   1 -
 ui/build.mjs                                  |   4 +-
 ui/extractedTranslations/da/translation.json  | 188 +++++++++---------
 ui/extractedTranslations/en/translation.json  |   8 +-
 ui/src/App.tsx                                |  10 +
 ui/src/Layout.tsx                             |   4 +-
 .../{SettingsPage.js => SettingsPage.tsx}     |   2 +-
 ...UserPreferences.js => UserPreferences.tsx} |  26 ++-
 ui/src/Pages/SettingsPage/Users/UsersTable.js |   2 +-
 ui/src/components/AlbumTitle.tsx              |   2 +-
 .../components/albumGallery/AlbumGallery.tsx  |   7 +-
 .../photoGallery/MediaThumbnail.tsx           |   9 +-
 .../components/photoGallery/PhotoGallery.tsx  |  62 +++---
 .../photoGallery/ProtectedMedia.tsx           |   6 +
 ui/src/components/routes/AuthorizedRoute.tsx  |   4 +-
 16 files changed, 185 insertions(+), 151 deletions(-)
 rename ui/src/Pages/SettingsPage/{SettingsPage.js => SettingsPage.tsx} (95%)
 rename ui/src/Pages/SettingsPage/{UserPreferences.js => UserPreferences.tsx} (67%)

diff --git a/.gitignore b/.gitignore
index 34bbc3a..541190a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ node_modules/
 .cache/
 dist/
 build/
+*.tsbuildinfo
 
 # misc
 .DS_Store
diff --git a/ui/babel.config.js b/ui/babel.config.js
index 0f6c8a3..38b42c4 100644
--- a/ui/babel.config.js
+++ b/ui/babel.config.js
@@ -16,7 +16,6 @@ module.exports = function (api) {
         'i18next-extract',
         {
           locales: ['en', 'da'],
-          discardOldKeys: true,
           defaultValue: null,
         },
       ])
diff --git a/ui/build.mjs b/ui/build.mjs
index 42f4006..c7b18a1 100644
--- a/ui/build.mjs
+++ b/ui/build.mjs
@@ -69,7 +69,9 @@ if (watchMode) {
   bs.watch('src/**/*.@(js|tsx|ts)').on('change', async args => {
     console.log('reloading', args)
     builderPromise = (await builderPromise).rebuild()
-    bs.reload(args)
+    setTimeout(() => {
+      bs.reload(args)
+    }, 1000)
   })
 } else {
   const build = async () => {
diff --git a/ui/extractedTranslations/da/translation.json b/ui/extractedTranslations/da/translation.json
index 2c3f540..01e60ac 100644
--- a/ui/extractedTranslations/da/translation.json
+++ b/ui/extractedTranslations/da/translation.json
@@ -1,12 +1,12 @@
 {
   "album_filter": {
-    "only_favorites": null,
-    "sort_by": null,
+    "only_favorites": "Vis kun favoritter",
+    "sort_by": "Sorter efter",
     "sorting_options": {
-      "date_imported": null,
-      "date_shot": null,
-      "title": null,
-      "type": null
+      "date_imported": "Dato for importering",
+      "date_shot": "Dato",
+      "title": "Titel",
+      "type": "Type"
     }
   },
   "albums_page": {
@@ -14,33 +14,33 @@
   },
   "general": {
     "action": {
-      "add": null,
-      "cancel": null,
-      "delete": null,
-      "remove": null,
-      "save": null
+      "add": "Tilføj",
+      "cancel": "Annuller",
+      "delete": "Slet",
+      "remove": "Fjern",
+      "save": "Gem"
     },
     "loading": {
-      "album": null,
+      "album": "Loader album",
       "default": "Loader...",
-      "media": null,
+      "media": "Loader medier",
       "page": "Loader side",
       "paginate": {
         "faces": "Loader flere personer",
         "media": "Loader flere medier"
       },
-      "shares": null,
+      "shares": "Loader delinger...",
       "timeline": "Loader tidslinje"
     }
   },
   "header": {
     "search": {
-      "loading": null,
-      "no_results": null,
-      "placeholder": null,
+      "loading": "Loader resultater...",
+      "no_results": "Fandt ingen resultater",
+      "placeholder": "Søg",
       "result_type": {
-        "albums": null,
-        "photos": null
+        "albums": "Albums",
+        "photos": "Billeder"
       }
     }
   },
@@ -62,6 +62,9 @@
     },
     "welcome": "Velkommen til Photoview"
   },
+  "meta": {
+    "description": null
+  },
   "people_page": {
     "face_group": {
       "label_placeholder": "Navn",
@@ -74,24 +77,24 @@
   },
   "settings": {
     "concurrent_workers": {
-      "description": null,
-      "title": null
+      "description": "Det maksimale antal medier som må skannes samtidig",
+      "title": "Samtidige scanner-arbejdere"
     },
     "logout": null,
     "periodic_scanner": {
-      "checkbox_label": null,
+      "checkbox_label": "Aktiver periodiske scanner",
       "field": {
-        "description": null,
-        "label": null
+        "description": "Hvor ofte scanneren bør udføre automatiske scanninger af alle brugere",
+        "label": "Periodiske scanningsintervaller"
       },
       "interval_unit": {
-        "days": null,
-        "hour": null,
-        "minutes": null,
-        "months": null,
-        "seconds": null
+        "days": "Dage",
+        "hour": "Timer",
+        "minutes": "Minutter",
+        "months": "Måneder",
+        "seconds": "Sekunder"
       },
-      "title": null
+      "title": "Periodisk scanner"
     },
     "scanner": {
       "description": "Vil scanne alle brugere for nye eller opdaterede medier",
@@ -106,21 +109,21 @@
     },
     "users": {
       "add_user": {
-        "submit": null
+        "submit": "Tilføj bruger"
       },
       "confirm_delete_user": {
-        "action": null,
-        "description": null,
-        "title": null
+        "action": "Slet {user}",
+        "description": "<0>Er du sikker på at du vil slette <1></1>?</0><p>Denne handling kan ikke fortrydes</p>",
+        "title": "Slet bruger"
       },
       "password_reset": {
-        "description": null,
+        "description": "Ændre adgangskode for <1></1>",
         "form": {
-          "label": null,
-          "placeholder": null,
-          "submit": null
+          "label": "Ny adgangskode",
+          "placeholder": "adgangskode",
+          "submit": "Ændre adgangskode"
         },
-        "title": null
+        "title": "Ændre adgangskode"
       },
       "table": {
         "column_names": {
@@ -132,10 +135,10 @@
         "new_user": "Ny bruger",
         "row": {
           "action": {
-            "change_password": null,
-            "delete": null,
-            "edit": null,
-            "scan": null
+            "change_password": "Ændre adgangskode",
+            "delete": "Slet",
+            "edit": "Rediger",
+            "scan": "Scan"
           }
         }
       },
@@ -143,6 +146,9 @@
     }
   },
   "share_page": {
+    "media": {
+      "title": null
+    },
     "protected_share": {
       "description": "Denne deling er låst med en adgangskode.",
       "title": "Beskyttet deling"
@@ -153,72 +159,72 @@
   },
   "sidebar": {
     "album": {
-      "title": null
+      "title": "Album indstillinger"
     },
     "download": {
       "filesize": {
-        "byte": null,
-        "byte_plural": null,
-        "giga_byte": null,
-        "kilo_byte": null,
-        "mega_byte": null,
-        "tera_byte": null
+        "byte": "{{count}} Byte",
+        "byte_plural": "{{count}} Bytes",
+        "giga_byte": "{{count}} GB",
+        "kilo_byte": "{{count}} KB",
+        "mega_byte": "{{count}} MB",
+        "tera_byte": "{{count}} TB"
       },
       "table_columns": {
-        "dimensions": null,
-        "file_size": null,
-        "file_type": null,
-        "name": null
+        "dimensions": "Dimension",
+        "file_size": "Størrelse",
+        "file_type": "Type",
+        "name": "Navn"
       },
-      "title": null
+      "title": "Download"
     },
     "media": {
       "exif": {
         "exposure_program": {
-          "action_program": null,
-          "aperture_priority": null,
-          "bulb": null,
-          "creative_program": null,
-          "landscape_mode": null,
-          "manual": null,
-          "normal_program": null,
-          "not_defined": null,
-          "portrait_mode": null,
-          "shutter_priority": null
+          "action_program": "Actionprogram",
+          "aperture_priority": "Blændeprioritet",
+          "bulb": "Bulb",
+          "creative_program": "Kreativ program",
+          "landscape_mode": "Landskabsmode",
+          "manual": "Manuel",
+          "normal_program": "Normal program",
+          "not_defined": "Ikke defineret",
+          "portrait_mode": "Portræt mode",
+          "shutter_priority": "Lukkerprioritet"
         },
         "flash": {
-          "auto": null,
-          "did_not_fire": null,
-          "fired": null,
-          "no_flash": null,
-          "no_flash_function": null,
-          "off": null,
-          "on": null,
-          "red_eye_reduction": null,
-          "return_detected": null,
-          "return_not_detected": null
+          "auto": "Auto",
+          "did_not_fire": "Blitz affyrede ikke",
+          "fired": "Affyrede",
+          "no_flash": "Ingen blitz",
+          "no_flash_function": "Ingen blitz-funktion",
+          "off": "Slukket",
+          "on": "Tændt",
+          "red_eye_reduction": "Røde øjne reduktion",
+          "return_detected": "Retur registreret",
+          "return_not_detected": "Retur ikke registreret"
         },
         "name": {
-          "aperture": null,
-          "camera": null,
-          "date_shot": null,
-          "exposure": null,
-          "exposure_program": null,
-          "flash": null,
-          "focal_length": null,
-          "iso": null,
-          "lens": null,
-          "maker": null
+          "aperture": "Blænde",
+          "camera": "Kamera",
+          "date_shot": "Dato",
+          "exposure": "Lukketid",
+          "exposure_program": "Lukketid program",
+          "flash": "Blitz",
+          "focal_length": "Fokallængde",
+          "iso": "ISO",
+          "lens": "Lense",
+          "maker": "Mærke"
         }
       }
     },
     "sharing": {
-      "add_share": null,
-      "copy_link": null,
-      "no_shares_found": null,
-      "public_link": null,
-      "table_header": null,
-      "title": null
+      "add_share": "Tilføj deling",
+      "copy_link": "Kopier link",
+      "no_shares_found": "Ingen delinger fundet",
+      "public_link": "Offentligt link",
+      "table_header": "Offentlige delinger",
+      "title": "Indstillinger for deling"
     }
   },
   "sidemenu": {
diff --git a/ui/extractedTranslations/en/translation.json b/ui/extractedTranslations/en/translation.json
index d5f801c..b29d397 100644
--- a/ui/extractedTranslations/en/translation.json
+++ b/ui/extractedTranslations/en/translation.json
@@ -62,6 +62,9 @@
     },
     "welcome": "Welcome to Photoview"
   },
+  "meta": {
+    "description": "Simple and User-friendly Photo Gallery for Personal Servers"
+  },
   "people_page": {
     "face_group": {
       "label_placeholder": "Label",
@@ -143,6 +146,9 @@
     }
   },
   "share_page": {
+    "media": {
+      "title": "Shared media"
+    },
     "protected_share": {
       "description": "This share is protected with a password.",
       "title": "Protected share"
@@ -158,7 +164,7 @@
     "download": {
       "filesize": {
         "byte": "{{count}} Byte",
-        "byte_plural": null,
+        "byte_plural": "{{count}} Bytes",
         "giga_byte": "{{count}} GB",
         "kilo_byte": "{{count}} KB",
         "mega_byte": "{{count}} MB",
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index dbbac51..25fdb5d 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -44,6 +44,7 @@ const SITE_TRANSLATION = gql`
 `
 
 const loadTranslations = () => {
+  console.log('load translation')
   const [loadLang, { data }] = useLazyQuery<siteTranslation>(SITE_TRANSLATION)
 
   useEffect(() => {
@@ -53,13 +54,22 @@ const loadTranslations = () => {
   }, [authToken()])
 
   useEffect(() => {
+    console.log('loading translations', data)
     switch (data?.myUserPreferences.language) {
       case LanguageTranslation.Danish:
         import('../extractedTranslations/da/translation.json').then(danish => {
+          console.log('loading danish')
           i18n.addResourceBundle('da', 'translation', danish)
           i18n.changeLanguage('da')
         })
         break
+      case LanguageTranslation.English:
+        import('../extractedTranslations/en/translation.json').then(english => {
+          console.log('loading english')
+          i18n.addResourceBundle('en', 'translation', english)
+          i18n.changeLanguage('en')
+        })
+        break
       default:
         i18n.changeLanguage('en')
     }
diff --git a/ui/src/Layout.tsx b/ui/src/Layout.tsx
index bcb2241..4ca3f16 100644
--- a/ui/src/Layout.tsx
+++ b/ui/src/Layout.tsx
@@ -1,4 +1,4 @@
-import React, { ReactChild, ReactChildren } from 'react'
+import React, { ReactChild } from 'react'
 import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import { NavLink } from 'react-router-dom'
@@ -143,7 +143,7 @@ export const SideMenu = () => {
 }
 
 type LayoutProps = {
-  children: ReactChild | ReactChildren
+  children: React.ReactNode
   title: string
 }
 
diff --git a/ui/src/Pages/SettingsPage/SettingsPage.js b/ui/src/Pages/SettingsPage/SettingsPage.tsx
similarity index 95%
rename from ui/src/Pages/SettingsPage/SettingsPage.js
rename to ui/src/Pages/SettingsPage/SettingsPage.tsx
index 31b80bf..b9d0269 100644
--- a/ui/src/Pages/SettingsPage/SettingsPage.js
+++ b/ui/src/Pages/SettingsPage/SettingsPage.tsx
@@ -10,7 +10,7 @@ import ScannerSection from './ScannerSection'
 import UserPreferences from './UserPreferences'
 import UsersTable from './Users/UsersTable'
 
-export const SectionTitle = styled.h2`
+export const SectionTitle = styled.h2<{ nospace: boolean }>`
   margin-top: ${({ nospace }) => (nospace ? '0' : '1.4em')} !important;
   padding-bottom: 0.3em;
   border-bottom: 1px solid #ddd;
diff --git a/ui/src/Pages/SettingsPage/UserPreferences.js b/ui/src/Pages/SettingsPage/UserPreferences.tsx
similarity index 67%
rename from ui/src/Pages/SettingsPage/UserPreferences.js
rename to ui/src/Pages/SettingsPage/UserPreferences.tsx
index b67d6d0..3149286 100644
--- a/ui/src/Pages/SettingsPage/UserPreferences.js
+++ b/ui/src/Pages/SettingsPage/UserPreferences.tsx
@@ -4,12 +4,17 @@ import React from 'react'
 import { useTranslation } from 'react-i18next'
 import { Dropdown } from 'semantic-ui-react'
 import styled from 'styled-components'
-
+import { LanguageTranslation } from '../../../__generated__/globalTypes'
 import { SectionTitle } from './SettingsPage'
+import {
+  changeUserPreferences,
+  changeUserPreferencesVariables,
+} from './__generated__/changeUserPreferences'
+import { myUserPreferences } from './__generated__/myUserPreferences'
 
 const languagePreferences = [
-  { key: 1, text: 'English', value: 'en' },
-  { key: 2, text: 'Dansk', value: 'da' },
+  { key: 1, text: 'English', value: LanguageTranslation.English },
+  { key: 2, text: 'Dansk', value: LanguageTranslation.Danish },
 ]
 
 const CHANGE_USER_PREFERENCES = gql`
@@ -37,14 +42,15 @@ const UserPreferencesWrapper = styled.div`
 const UserPreferences = () => {
   const { t } = useTranslation()
 
-  const { data } = useQuery(MY_USER_PREFERENCES)
+  const { data } = useQuery<myUserPreferences>(MY_USER_PREFERENCES)
 
-  const [changePrefs, { loading: loadingPrefs, error }] = useMutation(
-    CHANGE_USER_PREFERENCES
-  )
+  const [changePrefs, { loading: loadingPrefs, error }] = useMutation<
+    changeUserPreferences,
+    changeUserPreferencesVariables
+  >(CHANGE_USER_PREFERENCES)
 
   if (error) {
-    return error.message
+    return <div>{error.message}</div>
   }
 
   return (
@@ -62,12 +68,12 @@ const UserPreferences = () => {
         onChange={(event, { value: language }) => {
           changePrefs({
             variables: {
-              language,
+              language: language as LanguageTranslation,
             },
           })
         }}
         selection
-        value={data?.myUserPreferences.language}
+        value={data?.myUserPreferences.language || undefined}
         loading={loadingPrefs}
         disabled={loadingPrefs}
       />
diff --git a/ui/src/Pages/SettingsPage/Users/UsersTable.js b/ui/src/Pages/SettingsPage/Users/UsersTable.js
index 90b4ca1..4d7f21f 100644
--- a/ui/src/Pages/SettingsPage/Users/UsersTable.js
+++ b/ui/src/Pages/SettingsPage/Users/UsersTable.js
@@ -29,7 +29,7 @@ const UsersTable = () => {
   const { loading, error, data, refetch } = useQuery(USERS_QUERY)
 
   if (error) {
-    return `Users table error: ${error.message}`
+    return <div>{`Users table error: ${error.message}`}</div>
   }
 
   let userRows = []
diff --git a/ui/src/components/AlbumTitle.tsx b/ui/src/components/AlbumTitle.tsx
index b7fd32b..4cf5604 100644
--- a/ui/src/components/AlbumTitle.tsx
+++ b/ui/src/components/AlbumTitle.tsx
@@ -50,7 +50,7 @@ const ALBUM_PATH_QUERY = gql`
 `
 
 type AlbumTitleProps = {
-  album: {
+  album?: {
     id: string
     title: string
   }
diff --git a/ui/src/components/albumGallery/AlbumGallery.tsx b/ui/src/components/albumGallery/AlbumGallery.tsx
index 4c9fd92..5eeb1a6 100644
--- a/ui/src/components/albumGallery/AlbumGallery.tsx
+++ b/ui/src/components/albumGallery/AlbumGallery.tsx
@@ -6,7 +6,7 @@ import AlbumFilter from '../AlbumFilter'
 import { albumQuery_album } from '../../Pages/AlbumPage/__generated__/albumQuery'
 
 type AlbumGalleryProps = {
-  album: albumQuery_album
+  album?: albumQuery_album
   loading?: boolean
   customAlbumLink?(albumID: string): string
   showFilter?: boolean
@@ -64,10 +64,13 @@ const AlbumGallery = React.forwardRef(
     }
 
     const nextImage = () => {
+      if (album === undefined) return
       setActiveImage((imageState.activeImage + 1) % album.media.length)
     }
 
     const previousImage = () => {
+      if (album === undefined) return
+
       if (imageState.activeImage <= 0) {
         setActiveImage(album.media.length - 1)
       } else {
@@ -131,7 +134,7 @@ const AlbumGallery = React.forwardRef(
         }
         <PhotoGallery
           loading={loading}
-          media={album && album.media}
+          media={album?.media || []}
           activeIndex={imageState.activeImage}
           presenting={imageState.presenting}
           onSelectImage={index => {
diff --git a/ui/src/components/photoGallery/MediaThumbnail.tsx b/ui/src/components/photoGallery/MediaThumbnail.tsx
index 53b5397..2ddb98c 100644
--- a/ui/src/components/photoGallery/MediaThumbnail.tsx
+++ b/ui/src/components/photoGallery/MediaThumbnail.tsx
@@ -4,6 +4,10 @@ import styled from 'styled-components'
 import { Icon } from 'semantic-ui-react'
 import { ProtectedImage } from './ProtectedMedia'
 import { MediaType } from '../../../__generated__/globalTypes'
+import {
+  markMediaFavorite,
+  markMediaFavoriteVariables,
+} from './__generated__/markMediaFavorite'
 
 const markFavoriteMutation = gql`
   mutation markMediaFavorite($mediaId: ID!, $favorite: Boolean!) {
@@ -130,7 +134,10 @@ export const MediaThumbnail = ({
   setPresenting,
   onFavorite,
 }: MediaThumbnailProps) => {
-  const [markFavorite] = useMutation(markFavoriteMutation)
+  const [markFavorite] = useMutation<
+    markMediaFavorite,
+    markMediaFavoriteVariables
+  >(markFavoriteMutation)
 
   let heartIcon = null
   if (media.favorite !== undefined) {
diff --git a/ui/src/components/photoGallery/PhotoGallery.tsx b/ui/src/components/photoGallery/PhotoGallery.tsx
index 5d681af..61d140c 100644
--- a/ui/src/components/photoGallery/PhotoGallery.tsx
+++ b/ui/src/components/photoGallery/PhotoGallery.tsx
@@ -3,7 +3,7 @@ import styled from 'styled-components'
 import { Loader } from 'semantic-ui-react'
 import { MediaThumbnail, PhotoThumbnail } from './MediaThumbnail'
 import PresentView from './presentView/PresentView'
-import { SidebarContext, UpdateSidebarFn } from '../sidebar/Sidebar'
+import { SidebarContext } from '../sidebar/Sidebar'
 import MediaSidebar from '../sidebar/MediaSidebar'
 import { useTranslation } from 'react-i18next'
 import { PresentMediaProps_Media } from './presentView/PresentMedia'
@@ -65,44 +65,32 @@ const PhotoGallery = ({
   const { t } = useTranslation()
   const { updateSidebar } = useContext(SidebarContext)
 
-  if (
-    media === undefined ||
-    activeIndex === -1 ||
-    media[activeIndex] === undefined
-  ) {
-    return null
-  }
+  const activeImage: PhotoGalleryProps_Media | undefined = media[activeIndex]
 
-  const activeImage = media[activeIndex]
+  let photoElements = []
+  if (media) {
+    photoElements = media.map((photo, index) => {
+      const active = activeIndex == index
 
-  const getPhotoElements = (updateSidebar: UpdateSidebarFn) => {
-    let photoElements = []
-    if (media) {
-      photoElements = media.map((photo, index) => {
-        const active = activeIndex == index
-
-        return (
-          <MediaThumbnail
-            key={photo.id}
-            media={photo}
-            onSelectImage={index => {
-              updateSidebar(<MediaSidebar media={photo} />)
-              onSelectImage(index)
-            }}
-            onFavorite={onFavorite}
-            setPresenting={setPresenting}
-            index={index}
-            active={active}
-          />
-        )
-      })
-    } else {
-      for (let i = 0; i < 6; i++) {
-        photoElements.push(<PhotoThumbnail key={i} />)
-      }
+      return (
+        <MediaThumbnail
+          key={photo.id}
+          media={photo}
+          onSelectImage={index => {
+            updateSidebar(<MediaSidebar media={photo} />)
+            onSelectImage(index)
+          }}
+          onFavorite={onFavorite}
+          setPresenting={setPresenting}
+          index={index}
+          active={active}
+        />
+      )
+    })
+  } else {
+    for (let i = 0; i < 6; i++) {
+      photoElements.push(<PhotoThumbnail key={i} />)
     }
-
-    return photoElements
   }
 
   return (
@@ -111,7 +99,7 @@ const PhotoGallery = ({
         <Loader active={loading}>
           {t('general.loading.media', 'Loading media')}
         </Loader>
-        {getPhotoElements(updateSidebar)}
+        {photoElements}
         <PhotoFiller />
       </Gallery>
       {presenting && (
diff --git a/ui/src/components/photoGallery/ProtectedMedia.tsx b/ui/src/components/photoGallery/ProtectedMedia.tsx
index c53af99..87eaad7 100644
--- a/ui/src/components/photoGallery/ProtectedMedia.tsx
+++ b/ui/src/components/photoGallery/ProtectedMedia.tsx
@@ -26,6 +26,7 @@ export interface ProtectedImageProps
   src?: string
   key?: string
   lazyLoading?: boolean
+  loaded?: boolean
 }
 
 /**
@@ -38,6 +39,7 @@ export const ProtectedImage = ({
   src,
   key,
   lazyLoading,
+  loaded,
   ...props
 }: ProtectedImageProps) => {
   const lazyLoadProps: { 'data-src'?: string; loading?: 'lazy' | 'eager' } = {}
@@ -55,11 +57,15 @@ export const ProtectedImage = ({
       ? placeholder
       : getProtectedUrl(src) || placeholder
 
+  const loadedProp =
+    loaded !== undefined ? { loaded: loaded.toString() } : undefined
+
   return (
     <img
       key={key}
       {...props}
       {...lazyLoadProps}
+      {...loadedProp}
       src={imgSrc}
       crossOrigin="use-credentials"
     />
diff --git a/ui/src/components/routes/AuthorizedRoute.tsx b/ui/src/components/routes/AuthorizedRoute.tsx
index 6779f3d..6c0c6a4 100644
--- a/ui/src/components/routes/AuthorizedRoute.tsx
+++ b/ui/src/components/routes/AuthorizedRoute.tsx
@@ -6,10 +6,10 @@ import { authToken } from '../../helpers/authentication'
 import { ADMIN_QUERY } from '../../Layout'
 
 export const useIsAdmin = (enabled = true) => {
-  const [fetchAdminQuery, { data }] = useLazyQuery(ADMIN_QUERY)
+  const [fetchAdminQuery, { data, called }] = useLazyQuery(ADMIN_QUERY)
 
   useEffect(() => {
-    if (authToken() && !data && enabled) {
+    if (authToken() && !called && enabled) {
       fetchAdminQuery()
     }
   }, [authToken(), enabled])
