diff --git a/web/apps/accounts/src/pages/_app.tsx b/web/apps/accounts/src/pages/_app.tsx index 400d3616c3..f828ecab03 100644 --- a/web/apps/accounts/src/pages/_app.tsx +++ b/web/apps/accounts/src/pages/_app.tsx @@ -1,12 +1,11 @@ import { staticAppTitle } from "@/base/app"; import { CustomHead } from "@/base/components/Head"; +import { LoadingOverlay } from "@/base/components/LoadingOverlay"; import { AttributedMiniDialog } from "@/base/components/MiniDialog"; -import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; -import { Overlay } from "@/base/components/mui/Container"; import { AppNavbar } from "@/base/components/Navbar"; import { useAttributedMiniDialog } from "@/base/components/utils/dialog"; +import { useSetupI18n } from "@/base/components/utils/hooks-i18n"; import { getTheme, THEME_COLOR } from "@/base/components/utils/theme"; -import { setupI18n } from "@/base/i18n"; import { disableDiskLogs } from "@/base/log"; import { logUnhandledErrorsAndRejections } from "@/base/log-web"; import { CssBaseline } from "@mui/material"; @@ -20,13 +19,13 @@ import "@fontsource-variable/inter"; import "styles/global.css"; const App: React.FC = ({ Component, pageProps }) => { - const [isI18nReady, setIsI18nReady] = useState(false); const [showNavbar, setShowNavbar] = useState(false); + + const isI18nReady = useSetupI18n(); const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog(); useEffect(() => { disableDiskLogs(); - void setupI18n().finally(() => setIsI18nReady(true)); logUnhandledErrorsAndRejections(true); return () => logUnhandledErrorsAndRejections(false); }, []); @@ -50,19 +49,7 @@ const App: React.FC = ({ Component, pageProps }) => { - {!isI18nReady && ( - ({ - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: 2000, - backgroundColor: theme.colors.background.base, - })} - > - - - )} + {!isI18nReady && } {showNavbar && } {isI18nReady && } diff --git a/web/apps/auth/src/pages/_app.tsx b/web/apps/auth/src/pages/_app.tsx index f719ad301d..67b72c3b82 100644 --- a/web/apps/auth/src/pages/_app.tsx +++ b/web/apps/auth/src/pages/_app.tsx @@ -1,13 +1,12 @@ import { accountLogout } from "@/accounts/services/logout"; import { clientPackageName, staticAppTitle } from "@/base/app"; import { CustomHead } from "@/base/components/Head"; +import { LoadingOverlay } from "@/base/components/LoadingOverlay"; import { AttributedMiniDialog } from "@/base/components/MiniDialog"; -import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; -import { Overlay } from "@/base/components/mui/Container"; import { AppNavbar } from "@/base/components/Navbar"; import { useAttributedMiniDialog } from "@/base/components/utils/dialog"; +import { useSetupI18n } from "@/base/components/utils/hooks-i18n"; import { THEME_COLOR, getTheme } from "@/base/components/utils/theme"; -import { setupI18n } from "@/base/i18n"; import { logStartupBanner, logUnhandledErrorsAndRejections, @@ -33,10 +32,10 @@ import "styles/global.css"; const App: React.FC = ({ Component, pageProps }) => { const router = useRouter(); - const [isI18nReady, setIsI18nReady] = useState(false); const [loading, setLoading] = useState(false); const [showNavbar, setShowNavBar] = useState(false); + const isI18nReady = useSetupI18n(); const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog(); const [themeColor, setThemeColor] = useLocalState( LS_KEYS.THEME, @@ -44,7 +43,6 @@ const App: React.FC = ({ Component, pageProps }) => { ); useEffect(() => { - void setupI18n().finally(() => setIsI18nReady(true)); const user = getData(LS_KEYS.USER) as User | undefined | null; void migrateKVToken(user); logStartupBanner(user?.id); @@ -94,22 +92,8 @@ const App: React.FC = ({ Component, pageProps }) => { - {(loading || !isI18nReady) && ( - ({ - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: 2000, - backgroundColor: theme.colors.background.base, - })} - > - - - )} - {isI18nReady && ( - - )} + {(loading || !isI18nReady) && } + {isI18nReady && } diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index c11da2a6f9..1049f3cc92 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -345,12 +345,12 @@ const UnparseableCode: React.FC = ({ {code.issuer} ({ - color: theme.palette.critical.main, + sx={{ + color: "critical.main", flex: 1, minHeight: "16px", mb: 2, - })} + }} > {errorMessage} diff --git a/web/apps/auth/src/pages/share.tsx b/web/apps/auth/src/pages/share.tsx index 00e16a1d93..3de9acef8a 100644 --- a/web/apps/auth/src/pages/share.tsx +++ b/web/apps/auth/src/pages/share.tsx @@ -1,5 +1,6 @@ import { EnteLogo } from "@/base/components/EnteLogo"; import { decryptMetadataJSON_New } from "@/base/crypto"; +import { Box, Button, Stack, Typography } from "@mui/material"; import React, { useEffect, useMemo, useState } from "react"; interface SharedCode { @@ -8,13 +9,7 @@ interface SharedCode { codes: string; } -interface CodeDisplay { - currentCode: string; - nextCode: string; - progress: number; -} - -const Share: React.FC = () => { +const Page: React.FC = () => { const [sharedCode, setSharedCode] = useState(null); const [error, setError] = useState(null); const [timeStatus, setTimeStatus] = useState(-10); @@ -51,10 +46,10 @@ const Share: React.FC = () => { try { const decryptedCode = (await decryptMetadataJSON_New( { - encryptedData: base64UrlToByteArray(data), - decryptionHeader: base64UrlToByteArray(header), + encryptedData: base64UrlToBytes(data), + decryptionHeader: base64UrlToBytes(header), }, - base64UrlToByteArray(key), + base64UrlToBytes(key), )) as SharedCode; setSharedCode(decryptedCode); } catch (error) { @@ -83,7 +78,7 @@ const Share: React.FC = () => { if (status === 0) { setCodeDisplay( - getCodeDisplay( + parseCodeDisplay( codes, sharedCode.startTime, sharedCode.step, @@ -101,37 +96,38 @@ const Share: React.FC = () => { [codeDisplay.progress], ); - const Message: React.FC<{ text: string }> = ({ text }) => ( -

{text}

- ); - return ( -
-
- {error &&

{error}

} + + {error && ( + + {error} + + )} {timeStatus === -10 && !error && ( - + {"Decrypting..."} )} {timeStatus === -1 && ( - + + Your or the person who shared the code has out of sync + time. + )} - {timeStatus === 1 && } + {timeStatus === 1 && The code has expired.} {timeStatus === 0 && ( -
{ position: "absolute", right: "20px", bottom: "20px", - fontSize: "12px", - opacity: 0.6, }} > -

+ {codeDisplay.nextCode === "" ? "Last code" : "next"} -

+ {codeDisplay.nextCode !== "" && ( -

+ {codeDisplay.nextCode} -

+ )}
-
+ )} -
+ - - - - + Try Ente Auth + + ); }; -export default Share; +export default Page; -const base64UrlToByteArray = (base64Url: string): Uint8Array => { +const base64UrlToBytes = (base64Url: string): Uint8Array => { const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); }; -const formatCode = (code: string): string => - code.replace(/(.{3})/g, "$1 ").trim(); +interface CodeDisplay { + currentCode: string; + nextCode: string; + progress: number; +} -const getCodeDisplay = ( +const parseCodeDisplay = ( codes: string[], startTime: number, stepDuration: number, @@ -241,3 +234,11 @@ const getCodeDisplay = ( progress, }; }; + +const formatCode = (code: string) => code.replace(/(.{3})/g, "$1 ").trim(); + +const Message: React.FC = ({ children }) => ( + + {children} + +); diff --git a/web/apps/photos/src/components/LoadingOverlay.tsx b/web/apps/photos/src/components/GalleryLoadingOverlay.tsx similarity index 85% rename from web/apps/photos/src/components/LoadingOverlay.tsx rename to web/apps/photos/src/components/GalleryLoadingOverlay.tsx index 90869c91ae..5a6b3a3427 100644 --- a/web/apps/photos/src/components/LoadingOverlay.tsx +++ b/web/apps/photos/src/components/GalleryLoadingOverlay.tsx @@ -1,5 +1,5 @@ import { styled } from "@mui/material"; -export const LoadingOverlay = styled("div")` +export const GalleryLoadingOverlay = styled("div")` left: 0; top: 0; outline: none; diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 4df9e18abd..d1908fe8db 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -1,15 +1,14 @@ import { clientPackageName, isDesktop, staticAppTitle } from "@/base/app"; import { CustomHead } from "@/base/components/Head"; +import { LoadingOverlay } from "@/base/components/LoadingOverlay"; import { AttributedMiniDialog } from "@/base/components/MiniDialog"; -import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; -import { Overlay } from "@/base/components/mui/Container"; import { AppNavbar } from "@/base/components/Navbar"; import { genericErrorDialogAttributes, useAttributedMiniDialog, } from "@/base/components/utils/dialog"; +import { useSetupI18n } from "@/base/components/utils/hooks-i18n"; import { THEME_COLOR, getTheme } from "@/base/components/utils/theme"; -import { setupI18n } from "@/base/i18n"; import log from "@/base/log"; import { logStartupBanner, @@ -54,7 +53,6 @@ import "styles/global.css"; export default function App({ Component, pageProps }: AppProps) { const router = useRouter(); - const [isI18nReady, setIsI18nReady] = useState(false); const [loading, setLoading] = useState(false); const [showNavbar, setShowNavBar] = useState(false); const [watchFolderView, setWatchFolderView] = useState(false); @@ -65,6 +63,7 @@ export default function App({ Component, pageProps }: AppProps) { useState(null); const isOffline = useIsOffline(); + const isI18nReady = useSetupI18n(); const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog(); const { loadingBarRef, showLoadingBar, hideLoadingBar } = useLoadingBar(); const [themeColor, setThemeColor] = useLocalState( @@ -73,7 +72,6 @@ export default function App({ Component, pageProps }: AppProps) { ); useEffect(() => { - void setupI18n().finally(() => setIsI18nReady(true)); const user = getData(LS_KEYS.USER) as User | undefined | null; void migrateKVToken(user); logStartupBanner(user?.id); @@ -152,7 +150,7 @@ export default function App({ Component, pageProps }: AppProps) { }); router.events.on("routeChangeComplete", () => { - log.debug(() => `Route change took ${Date.now() - t}} ms`); + log.debug(() => `Route change took ${Date.now() - t} ms`); setLoading(false); }); }, []); @@ -229,22 +227,8 @@ export default function App({ Component, pageProps }: AppProps) { /> - {(loading || !isI18nReady) && ( - ({ - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: 2000, - backgroundColor: theme.colors.background.base, - })} - > - - - )} - {isI18nReady && ( - - )} + {(loading || !isI18nReady) && } + {isI18nReady && } diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index cd274d7d81..854898c6da 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -97,7 +97,7 @@ import { import { FixCreationTime } from "components/FixCreationTime"; import FullScreenDropZone from "components/FullScreenDropZone"; import GalleryEmptyState from "components/GalleryEmptyState"; -import { LoadingOverlay } from "components/LoadingOverlay"; +import { GalleryLoadingOverlay } from "components/GalleryLoadingOverlay"; import PhotoFrame from "components/PhotoFrame"; import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList"; import Sidebar from "components/Sidebar"; @@ -892,9 +892,9 @@ export default function Gallery() { }} /> {blockingLoad && ( - + - + )} {isFirstLoad && ( diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 42db4149e6..c7c081550b 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -52,7 +52,7 @@ import { FilesDownloadProgressAttributes, } from "components/FilesDownloadProgress"; import FullScreenDropZone from "components/FullScreenDropZone"; -import { LoadingOverlay } from "components/LoadingOverlay"; +import { GalleryLoadingOverlay } from "components/GalleryLoadingOverlay"; import PhotoFrame from "components/PhotoFrame"; import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList"; import Uploader from "components/Upload/Uploader"; @@ -520,9 +520,9 @@ export default function PublicCollectionGallery() { selectable={downloadEnabled} /> {blockingLoad && ( - + - + )} ( + ({ + display: "flex", + justifyContent: "center", + alignItems: "center", + zIndex: 2000, + backgroundColor: theme.colors.background.base, + })} + > + + +); diff --git a/web/packages/base/components/utils/hooks-i18n.ts b/web/packages/base/components/utils/hooks-i18n.ts new file mode 100644 index 0000000000..1f7accce96 --- /dev/null +++ b/web/packages/base/components/utils/hooks-i18n.ts @@ -0,0 +1,21 @@ +import { setupI18n } from "@/base/i18n"; +import { useEffect, useState } from "react"; + +/** + * A hook that initializes the localization library that we use. + * + * This is only meant to be called from the top level `_app.tsx`, as this + * initialization is expected to only happen once for the lifetime of the page. + * + * @returns a boolean which will be set to true when the localized strings have + * been loaded. + */ +export const useSetupI18n = () => { + const [isI18nReady, setIsI18nReady] = useState(false); + + useEffect(() => { + void setupI18n().finally(() => setIsI18nReady(true)); + }, []); + + return isI18nReady; +};