[web] App context refactoring (#1879)

This commit is contained in:
Manav Rathi 2024-05-26 22:03:33 +05:30 committed by GitHub
commit c5aa536c3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 659 additions and 679 deletions

View file

@ -1,6 +1,8 @@
import { CustomHead } from "@/next/components/Head";
import { setupI18n } from "@/next/i18n";
import { logUnhandledErrorsAndRejections } from "@/next/log-web";
import type { AppName, BaseAppContextT } from "@/next/types/app";
import { ensure } from "@/utils/ensure";
import { PAGES } from "@ente/accounts/constants/pages";
import { accountLogout } from "@ente/accounts/services/logout";
import { APPS, APP_TITLES } from "@ente/shared/apps/constants";
@ -19,19 +21,21 @@ import { ThemeProvider } from "@mui/material/styles";
import { t } from "i18next";
import { AppProps } from "next/app";
import { useRouter } from "next/router";
import { createContext, useEffect, useState } from "react";
import { createContext, useContext, useEffect, useState } from "react";
import "styles/global.css";
interface AppContextProps {
isMobile: boolean;
showNavBar: (show: boolean) => void;
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
logout: () => void;
}
/** The accounts app has no extra properties on top of the base context. */
type AppContextT = BaseAppContextT;
export const AppContext = createContext<AppContextProps>({} as AppContextProps);
/** The React {@link Context} available to all pages. */
export const AppContext = createContext<AppContextT | undefined>(undefined);
/** Utility hook to reduce amount of boilerplate in account related pages. */
export const useAppContext = () => ensure(useContext(AppContext));
export default function App({ Component, pageProps }: AppProps) {
const appName: AppName = "account";
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
const [showNavbar, setShowNavBar] = useState(false);
@ -83,6 +87,14 @@ export default function App({ Component, pageProps }: AppProps) {
void accountLogout().then(() => router.push(PAGES.ROOT));
};
const appContext = {
appName,
logout,
showNavBar,
isMobile,
setDialogBoxAttributesV2,
};
// TODO: This string doesn't actually exist
const title = isI18nReady
? t("title", { context: "accounts" })
@ -101,14 +113,7 @@ export default function App({ Component, pageProps }: AppProps) {
attributes={dialogBoxAttributeV2 as any}
/>
<AppContext.Provider
value={{
isMobile,
showNavBar,
setDialogBoxAttributesV2,
logout,
}}
>
<AppContext.Provider value={appContext}>
{!isI18nReady && (
<Overlay
sx={(theme) => ({

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/credentials";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import CredentialPage from "@ente/accounts/pages/credentials";
import { APPS } from "@ente/shared/apps/constants";
import { useContext } from "react";
import { AppContext } from "../_app";
export default function Credential() {
const appContext = useContext(AppContext);
return <CredentialPage appContext={appContext} appName={APPS.ACCOUNTS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/generate";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import GeneratePage from "@ente/accounts/pages/generate";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Generate() {
const appContext = useContext(AppContext);
return <GeneratePage appContext={appContext} appName={APPS.ACCOUNTS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/login";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import LoginPage from "@ente/accounts/pages/login";
import { APPS } from "@ente/shared/apps/constants";
import { useContext } from "react";
import { AppContext } from "../_app";
export default function Login() {
const appContext = useContext(AppContext);
return <LoginPage appContext={appContext} appName={APPS.ACCOUNTS} />;
}

View file

@ -1,16 +1,12 @@
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
import RecoverPage from "@ente/accounts/pages/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
import RecoverPage from "@ente/accounts/pages/two-factor/recover";
import { useAppContext } from "../../_app";
export default function Recover() {
const appContext = useContext(AppContext);
return (
const Page = () => (
<RecoverPage
appContext={appContext}
appName={APPS.PHOTOS}
appContext={useAppContext()}
twoFactorType={TwoFactorType.PASSKEY}
/>
);
}
);
export default Page;

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/recover";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import RecoverPage from "@ente/accounts/pages/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Recover() {
const appContext = useContext(AppContext);
return <RecoverPage appContext={appContext} appName={APPS.ACCOUNTS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/signup";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import SignupPage from "@ente/accounts/pages/signup";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Sigup() {
const appContext = useContext(AppContext);
return <SignupPage appContext={appContext} appName={APPS.ACCOUNTS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/two-factor/recover";
import { useAppContext } from "../_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,11 +0,0 @@
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function TwoFactorRecover() {
const appContext = useContext(AppContext);
return (
<TwoFactorRecoverPage appContext={appContext} appName={APPS.ACCOUNTS} />
);
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/two-factor/setup";
import { useAppContext } from "../_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,11 +0,0 @@
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function TwoFactorSetup() {
const appContext = useContext(AppContext);
return (
<TwoFactorSetupPage appContext={appContext} appName={APPS.ACCOUNTS} />
);
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/two-factor/verify";
import { useAppContext } from "../_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,11 +0,0 @@
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function TwoFactorVerify() {
const appContext = useContext(AppContext);
return (
<TwoFactorVerifyPage appContext={appContext} appName={APPS.ACCOUNTS} />
);
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/verify";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import VerifyPage from "@ente/accounts/pages/verify";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Verify() {
const appContext = useContext(AppContext);
return <VerifyPage appContext={appContext} appName={APPS.ACCOUNTS} />;
}

View file

@ -4,6 +4,8 @@ import {
logStartupBanner,
logUnhandledErrorsAndRejections,
} from "@/next/log-web";
import type { AppName, BaseAppContextT } from "@/next/types/app";
import { ensure } from "@/utils/ensure";
import { accountLogout } from "@ente/accounts/services/logout";
import {
APPS,
@ -28,25 +30,30 @@ import { ThemeProvider } from "@mui/material/styles";
import { t } from "i18next";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import { createContext, useEffect, useRef, useState } from "react";
import { createContext, useContext, useEffect, useRef, useState } from "react";
import LoadingBar, { type LoadingBarRef } from "react-top-loading-bar";
import "../../public/css/global.css";
type AppContextType = {
showNavBar: (show: boolean) => void;
/**
* Properties available via the {@link AppContext} to the Auth app's React tree.
*/
type AppContextT = BaseAppContextT & {
startLoading: () => void;
finishLoading: () => void;
isMobile: boolean;
themeColor: THEME_COLOR;
setThemeColor: (themeColor: THEME_COLOR) => void;
somethingWentWrong: () => void;
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
logout: () => void;
};
export const AppContext = createContext<AppContextType | undefined>(undefined);
/** The React {@link Context} available to all pages. */
export const AppContext = createContext<AppContextT | undefined>(undefined);
/** Utility hook to reduce amount of boilerplate in account related pages. */
export const useAppContext = () => ensure(useContext(AppContext));
export default function App({ Component, pageProps }: AppProps) {
const appName: AppName = "auth";
const router = useRouter();
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
const [loading, setLoading] = useState(false);
@ -131,6 +138,19 @@ export default function App({ Component, pageProps }: AppProps) {
void accountLogout().then(() => router.push(PAGES.ROOT));
};
const appContext = {
appName,
logout,
showNavBar,
isMobile,
setDialogBoxAttributesV2,
startLoading,
finishLoading,
themeColor,
setThemeColor,
somethingWentWrong,
};
// TODO: Refactor this to have a fallback
const title = isI18nReady
? t("title", { context: "auth" })
@ -156,19 +176,7 @@ export default function App({ Component, pageProps }: AppProps) {
attributes={dialogBoxAttributeV2}
/>
<AppContext.Provider
value={{
showNavBar,
startLoading,
finishLoading,
isMobile,
themeColor,
setThemeColor,
somethingWentWrong,
setDialogBoxAttributesV2,
logout,
}}
>
<AppContext.Provider value={appContext}>
{(loading || !isI18nReady) && (
<Overlay
sx={(theme) => ({

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import ChangeEmailPage from "@ente/accounts/pages/change-email";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/change-email";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <ChangeEmailPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import ChangePasswordPage from "@ente/accounts/pages/change-password";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/change-password";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <ChangePasswordPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import CredentialPage from "@ente/accounts/pages/credentials";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/credentials";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <CredentialPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import GeneratePage from "@ente/accounts/pages/generate";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/generate";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <GeneratePage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import LoginPage from "@ente/accounts/pages/login";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/login";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <LoginPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import RecoverPage from "@ente/accounts/pages/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/recover";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <RecoverPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import SignupPage from "@ente/accounts/pages/signup";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/signup";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <SignupPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/two-factor/recover";
import { useAppContext } from "../_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <TwoFactorRecoverPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/two-factor/setup";
import { useAppContext } from "../_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <TwoFactorSetupPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
import { APPS } from "@ente/shared/apps/constants";
import React, { useContext } from "react";
import { AppContext } from "../_app";
import Page_ from "@ente/accounts/pages/two-factor/verify";
import { useAppContext } from "../_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <TwoFactorVerifyPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,12 +1,6 @@
import { ensure } from "@/utils/ensure";
import VerifyPage from "@ente/accounts/pages/verify";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import React, { useContext } from "react";
import Page_ from "@ente/accounts/pages/verify";
import { useAppContext } from "./_app";
const Page: React.FC = () => {
const appContext = ensure(useContext(AppContext));
return <VerifyPage appContext={appContext} appName={APPS.AUTH} />;
};
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -604,7 +604,7 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
label={t("PREFERENCES")}
/>
<RecoveryKey
appContext={appContext}
isMobile={appContext.isMobile}
show={recoverModalView}
onHide={closeRecoveryKeyModal}
somethingWentWrong={somethingWentWrong}

View file

@ -5,7 +5,9 @@ import {
logStartupBanner,
logUnhandledErrorsAndRejections,
} from "@/next/log-web";
import type { AppName, BaseAppContextT } from "@/next/types/app";
import { AppUpdate } from "@/next/types/ipc";
import { ensure } from "@/utils/ensure";
import {
APPS,
APP_TITLES,
@ -44,7 +46,7 @@ import isElectron from "is-electron";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import "photoswipe/dist/photoswipe.css";
import { createContext, useEffect, useRef, useState } from "react";
import { createContext, useContext, useEffect, useRef, useState } from "react";
import LoadingBar from "react-top-loading-bar";
import DownloadManager from "services/download";
import { resumeExportsIfNeeded } from "services/export";
@ -74,8 +76,11 @@ const redirectMap = new Map([
[REDIRECTS.FAMILIES, getFamilyPortalRedirectURL],
]);
type AppContextType = {
showNavBar: (show: boolean) => void;
/**
* Properties available via the {@link AppContext} to the Photos app's React
* tree.
*/
type AppContextT = BaseAppContextT & {
mlSearchEnabled: boolean;
mapEnabled: boolean;
updateMlSearchEnabled: (enabled: boolean) => Promise<void>;
@ -89,19 +94,22 @@ type AppContextType = {
setWatchFolderView: (isOpen: boolean) => void;
watchFolderFiles: FileList;
setWatchFolderFiles: (files: FileList) => void;
isMobile: boolean;
themeColor: THEME_COLOR;
setThemeColor: (themeColor: THEME_COLOR) => void;
somethingWentWrong: () => void;
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
isCFProxyDisabled: boolean;
setIsCFProxyDisabled: (disabled: boolean) => void;
logout: () => void;
};
export const AppContext = createContext<AppContextType>(null);
/** The React {@link Context} available to all pages. */
export const AppContext = createContext<AppContextT | undefined>(undefined);
/** Utility hook to reduce amount of boilerplate in account related pages. */
export const useAppContext = () => ensure(useContext(AppContext));
export default function App({ Component, pageProps }: AppProps) {
const appName: AppName = "photos";
const router = useRouter();
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
const [loading, setLoading] = useState(false);
@ -324,6 +332,32 @@ export default function App({ Component, pageProps }: AppProps) {
void photosLogout().then(() => router.push(PAGES.ROOT));
};
const appContext = {
appName,
showNavBar,
mlSearchEnabled,
updateMlSearchEnabled,
startLoading,
finishLoading,
closeMessageDialog,
setDialogMessage,
watchFolderView,
setWatchFolderView,
watchFolderFiles,
setWatchFolderFiles,
isMobile,
setNotificationAttributes,
themeColor,
setThemeColor,
somethingWentWrong,
setDialogBoxAttributesV2,
mapEnabled,
updateMapEnabled,
isCFProxyDisabled,
setIsCFProxyDisabled,
logout,
};
const title = isI18nReady
? t("title", { context: "photos" })
: APP_TITLES.get(APPS.PHOTOS);
@ -359,32 +393,7 @@ export default function App({ Component, pageProps }: AppProps) {
attributes={notificationAttributes}
/>
<AppContext.Provider
value={{
showNavBar,
mlSearchEnabled,
updateMlSearchEnabled,
startLoading,
finishLoading,
closeMessageDialog,
setDialogMessage,
watchFolderView,
setWatchFolderView,
watchFolderFiles,
setWatchFolderFiles,
isMobile,
setNotificationAttributes,
themeColor,
setThemeColor,
somethingWentWrong,
setDialogBoxAttributesV2,
mapEnabled,
updateMapEnabled,
isCFProxyDisabled,
setIsCFProxyDisabled,
logout,
}}
>
<AppContext.Provider value={appContext}>
{(loading || !isI18nReady) && (
<Overlay
sx={(theme) => ({

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/change-email";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import ChangeEmailPage from "@ente/accounts/pages/change-email";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function ChangeEmail() {
const appContext = useContext(AppContext);
return <ChangeEmailPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/change-password";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import ChangePasswordPage from "@ente/accounts/pages/change-password";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function ChangePassword() {
const appContext = useContext(AppContext);
return <ChangePasswordPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/credentials";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import CredentialPage from "@ente/accounts/pages/credentials";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Credential() {
const appContext = useContext(AppContext);
return <CredentialPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/generate";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import GeneratePage from "@ente/accounts/pages/generate";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Generate() {
const appContext = useContext(AppContext);
return <GeneratePage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -1,7 +1,6 @@
import log from "@/next/log";
import Login from "@ente/accounts/components/Login";
import SignUp from "@ente/accounts/components/SignUp";
import { APPS } from "@ente/shared/apps/constants";
import { Login } from "@ente/accounts/components/Login";
import { SignUp } from "@ente/accounts/components/SignUp";
import { EnteLogo } from "@ente/shared/components/EnteLogo";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
@ -21,18 +20,18 @@ import { t } from "i18next";
import { useRouter } from "next/router";
import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel";
import "pure-react-carousel/dist/react-carousel.es.css";
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
import { AppContext } from "./_app";
import { useAppContext } from "./_app";
export default function LandingPage() {
const { appName, showNavBar, setDialogMessage } = useAppContext();
const router = useRouter();
const appContext = useContext(AppContext);
const [loading, setLoading] = useState(true);
const [showLogin, setShowLogin] = useState(true);
useEffect(() => {
appContext.showNavBar(false);
showNavBar(false);
const currentURL = new URL(window.location.href);
const albumsURL = new URL(getAlbumsURL());
currentURL.pathname = router.pathname;
@ -90,7 +89,7 @@ export default function LandingPage() {
await localForage.ready();
} catch (e) {
log.error("usage in incognito mode tried", e);
appContext.setDialogMessage({
setDialogMessage({
title: t("LOCAL_STORAGE_NOT_ACCESSIBLE"),
nonClosable: true,
@ -134,13 +133,9 @@ export default function LandingPage() {
<DesktopBox>
<SideBox>
{showLogin ? (
<Login signUp={signUp} appName={APPS.PHOTOS} />
<Login {...{ signUp, appName }} />
) : (
<SignUp
router={router}
appName={APPS.PHOTOS}
login={login}
/>
<SignUp {...{ router, appName, login }} />
)}
</SideBox>
</DesktopBox>

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/login";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import LoginPage from "@ente/accounts/pages/login";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Login() {
const appContext = useContext(AppContext);
return <LoginPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,3 @@
import Page from "@ente/accounts/pages/passkeys/finish";
export default Page;

View file

@ -1,11 +0,0 @@
import PasskeysFinishPage from "@ente/accounts/pages/passkeys/finish";
const PasskeysFinish = () => {
return (
<>
<PasskeysFinishPage />
</>
);
};
export default PasskeysFinish;

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/recover";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,10 +0,0 @@
import RecoverPage from "@ente/accounts/pages/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Recover() {
const appContext = useContext(AppContext);
return <RecoverPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/signup";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import SignupPage from "@ente/accounts/pages/signup";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Sigup() {
const appContext = useContext(AppContext);
return <SignupPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/two-factor/recover";
import { useAppContext } from "../_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,11 +0,0 @@
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function TwoFactorRecover() {
const appContext = useContext(AppContext);
return (
<TwoFactorRecoverPage appContext={appContext} appName={APPS.PHOTOS} />
);
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/two-factor/setup";
import { useAppContext } from "../_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function TwoFactorSetup() {
const appContext = useContext(AppContext);
return <TwoFactorSetupPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/two-factor/verify";
import { useAppContext } from "../_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,11 +0,0 @@
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function TwoFactorVerify() {
const appContext = useContext(AppContext);
return (
<TwoFactorVerifyPage appContext={appContext} appName={APPS.PHOTOS} />
);
}

View file

@ -0,0 +1,6 @@
import Page_ from "@ente/accounts/pages/verify";
import { useAppContext } from "./_app";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;

View file

@ -1,9 +0,0 @@
import VerifyPage from "@ente/accounts/pages/verify";
import { APPS } from "@ente/shared/apps/constants";
import { AppContext } from "pages/_app";
import { useContext } from "react";
export default function Verify() {
const appContext = useContext(AppContext);
return <VerifyPage appContext={appContext} appName={APPS.PHOTOS} />;
}

View file

@ -1,184 +0,0 @@
import { ensure } from "@/utils/ensure";
import { wait } from "@/utils/promise";
import { changeEmail, sendOTTForEmailChange } from "@ente/accounts/api/user";
import { APP_HOMES } from "@ente/shared/apps/constants";
import type { PageProps } from "@ente/shared/apps/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
import LinkButton from "@ente/shared/components/LinkButton";
import SubmitButton from "@ente/shared/components/SubmitButton";
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
import { Alert, Box, TextField } from "@mui/material";
import { Formik, type FormikHelpers } from "formik";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useState } from "react";
import { Trans } from "react-i18next";
import * as Yup from "yup";
interface formValues {
email: string;
ott?: string;
}
function ChangeEmailForm({ appName }: PageProps) {
const [loading, setLoading] = useState(false);
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
const [email, setEmail] = useState<string | null>(null);
const [showMessage, setShowMessage] = useState(false);
const [success, setSuccess] = useState(false);
const router = useRouter();
const requestOTT = async (
{ email }: formValues,
{ setFieldError }: FormikHelpers<formValues>,
) => {
try {
setLoading(true);
await sendOTTForEmailChange(email);
setEmail(email);
setShowOttInputVisibility(true);
setShowMessage(true);
// TODO: What was this meant to focus on? The ref referred to an
// Form element that was removed. Is this still needed.
// setTimeout(() => {
// ottInputRef.current?.focus();
// }, 250);
} catch (e) {
setFieldError("email", t("EMAIl_ALREADY_OWNED"));
}
setLoading(false);
};
const requestEmailChange = async (
{ email, ott }: formValues,
{ setFieldError }: FormikHelpers<formValues>,
) => {
try {
setLoading(true);
await changeEmail(email, ensure(ott));
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
setLoading(false);
setSuccess(true);
await wait(1000);
goToApp();
} catch (e) {
setLoading(false);
setFieldError("ott", t("INCORRECT_CODE"));
}
};
const goToApp = () => {
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
};
return (
<Formik<formValues>
initialValues={{ email: "" }}
validationSchema={
ottInputVisible
? Yup.object().shape({
email: Yup.string()
.email(t("EMAIL_ERROR"))
.required(t("REQUIRED")),
ott: Yup.string().required(t("REQUIRED")),
})
: Yup.object().shape({
email: Yup.string()
.email(t("EMAIL_ERROR"))
.required(t("REQUIRED")),
})
}
validateOnChange={false}
validateOnBlur={false}
onSubmit={!ottInputVisible ? requestOTT : requestEmailChange}
>
{({ values, errors, handleChange, handleSubmit }) => (
<>
{showMessage && (
<Alert
color="success"
onClose={() => setShowMessage(false)}
>
<Trans
i18nKey="EMAIL_SENT"
components={{
a: (
<Box
color="text.muted"
component={"span"}
/>
),
}}
values={{ email }}
/>
</Alert>
)}
<form noValidate onSubmit={handleSubmit}>
<VerticallyCentered>
<TextField
fullWidth
InputProps={{
readOnly: ottInputVisible,
}}
type="email"
label={t("ENTER_EMAIL")}
value={values.email}
onChange={handleChange("email")}
error={Boolean(errors.email)}
helperText={errors.email}
autoFocus
disabled={loading}
/>
{ottInputVisible && (
<TextField
fullWidth
type="text"
label={t("ENTER_OTT")}
value={values.ott}
onChange={handleChange("ott")}
error={Boolean(errors.ott)}
helperText={errors.ott}
disabled={loading}
/>
)}
<SubmitButton
success={success}
sx={{ mt: 2 }}
loading={loading}
buttonText={
!ottInputVisible
? t("SEND_OTT")
: t("VERIFY")
}
/>
</VerticallyCentered>
</form>
<FormPaperFooter
style={{
justifyContent: ottInputVisible
? "space-between"
: "normal",
}}
>
{ottInputVisible && (
<LinkButton
onClick={() => setShowOttInputVisibility(false)}
>
{t("CHANGE_EMAIL")}?
</LinkButton>
)}
<LinkButton onClick={goToApp}>
{t("GO_BACK")}
</LinkButton>
</FormPaperFooter>
</>
)}
</Formik>
);
}
export default ChangeEmailForm;

View file

@ -1,5 +1,6 @@
import log from "@/next/log";
import { APPS } from "@ente/shared/apps/constants";
import type { AppName } from "@/next/types/app";
import { appNameToAppNameOld } from "@ente/shared/apps/constants";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
import LinkButton from "@ente/shared/components/LinkButton";
@ -16,10 +17,12 @@ import { PAGES } from "../constants/pages";
interface LoginProps {
signUp: () => void;
appName: APPS;
appName: AppName;
}
export default function Login(props: LoginProps) {
export function Login(props: LoginProps) {
const appNameOld = appNameToAppNameOld(props.appName);
const router = useRouter();
const loginUser: SingleInputFormProps["callback"] = async (
@ -31,7 +34,7 @@ export default function Login(props: LoginProps) {
const srpAttributes = await getSRPAttributes(email);
log.debug(() => ` srpAttributes: ${JSON.stringify(srpAttributes)}`);
if (!srpAttributes || srpAttributes.isEmailMFAEnabled) {
await sendOtt(props.appName, email);
await sendOtt(appNameOld, email);
router.push(PAGES.VERIFY);
} else {
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);

View file

@ -1,11 +1,13 @@
import log from "@/next/log";
import type { AppName } from "@/next/types/app";
import { sendOtt } from "@ente/accounts/api/user";
import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength";
import { PAGES } from "@ente/accounts/constants/pages";
import { isWeakPassword } from "@ente/accounts/utils";
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
import { APPS } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components//Container";
import { LS_KEYS } from "@ente/shared//storage/localStorage";
import { appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
import ShowHidePassword from "@ente/shared/components/Form/ShowHidePassword";
@ -15,7 +17,7 @@ import {
generateAndSaveIntermediateKeyAttributes,
saveKeyInSessionStore,
} from "@ente/shared/crypto/helpers";
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
import { setData } from "@ente/shared/storage/localStorage";
import {
setJustSignedUp,
setLocalReferralSource,
@ -51,10 +53,12 @@ interface FormValues {
interface SignUpProps {
router: NextRouter;
login: () => void;
appName: APPS;
appName: AppName;
}
export default function SignUp({ router, appName, login }: SignUpProps) {
export function SignUp({ router, appName, login }: SignUpProps) {
const appNameOld = appNameToAppNameOld(appName);
const [acceptTerms, setAcceptTerms] = useState(false);
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
@ -82,7 +86,7 @@ export default function SignUp({ router, appName, login }: SignUpProps) {
try {
setData(LS_KEYS.USER, { email });
setLocalReferralSource(referral);
await sendOtt(appName, email);
await sendOtt(appNameOld, email);
} catch (e) {
const message = e instanceof Error ? e.message : "";
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${message}`);

View file

@ -1,15 +1,33 @@
import ChangeEmailForm from "@ente/accounts/components/ChangeEmail";
import { ensure } from "@/utils/ensure";
import { wait } from "@/utils/promise";
import { changeEmail, sendOTTForEmailChange } from "@ente/accounts/api/user";
import { PAGES } from "@ente/accounts/constants/pages";
import type { PageProps } from "@ente/shared/apps/types";
import {
APP_HOMES,
appNameToAppNameOld,
type APPS,
} from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
import { getData, LS_KEYS } from "@ente/shared/storage/localStorage";
import LinkButton from "@ente/shared/components/LinkButton";
import SubmitButton from "@ente/shared/components/SubmitButton";
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
import { Alert, Box, TextField } from "@mui/material";
import { Formik, type FormikHelpers } from "formik";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
import * as Yup from "yup";
import type { PageProps } from "../types/page";
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext;
const appNameOld = appNameToAppNameOld(appName);
function ChangeEmailPage({ appName, appContext }: PageProps) {
const router = useRouter();
useEffect(() => {
@ -23,10 +41,175 @@ function ChangeEmailPage({ appName, appContext }: PageProps) {
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{t("CHANGE_EMAIL")}</FormPaperTitle>
<ChangeEmailForm appName={appName} appContext={appContext} />
<ChangeEmailForm appName={appNameOld} />
</FormPaper>
</VerticallyCentered>
);
};
export default Page;
interface formValues {
email: string;
ott?: string;
}
export default ChangeEmailPage;
function ChangeEmailForm({ appName }: { appName: APPS }) {
const [loading, setLoading] = useState(false);
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
const [email, setEmail] = useState<string | null>(null);
const [showMessage, setShowMessage] = useState(false);
const [success, setSuccess] = useState(false);
const router = useRouter();
const requestOTT = async (
{ email }: formValues,
{ setFieldError }: FormikHelpers<formValues>,
) => {
try {
setLoading(true);
await sendOTTForEmailChange(email);
setEmail(email);
setShowOttInputVisibility(true);
setShowMessage(true);
// TODO: What was this meant to focus on? The ref referred to an
// Form element that was removed. Is this still needed.
// setTimeout(() => {
// ottInputRef.current?.focus();
// }, 250);
} catch (e) {
setFieldError("email", t("EMAIl_ALREADY_OWNED"));
}
setLoading(false);
};
const requestEmailChange = async (
{ email, ott }: formValues,
{ setFieldError }: FormikHelpers<formValues>,
) => {
try {
setLoading(true);
await changeEmail(email, ensure(ott));
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
setLoading(false);
setSuccess(true);
await wait(1000);
goToApp();
} catch (e) {
setLoading(false);
setFieldError("ott", t("INCORRECT_CODE"));
}
};
const goToApp = () => {
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
};
return (
<Formik<formValues>
initialValues={{ email: "" }}
validationSchema={
ottInputVisible
? Yup.object().shape({
email: Yup.string()
.email(t("EMAIL_ERROR"))
.required(t("REQUIRED")),
ott: Yup.string().required(t("REQUIRED")),
})
: Yup.object().shape({
email: Yup.string()
.email(t("EMAIL_ERROR"))
.required(t("REQUIRED")),
})
}
validateOnChange={false}
validateOnBlur={false}
onSubmit={!ottInputVisible ? requestOTT : requestEmailChange}
>
{({ values, errors, handleChange, handleSubmit }) => (
<>
{showMessage && (
<Alert
color="success"
onClose={() => setShowMessage(false)}
>
<Trans
i18nKey="EMAIL_SENT"
components={{
a: (
<Box
color="text.muted"
component={"span"}
/>
),
}}
values={{ email }}
/>
</Alert>
)}
<form noValidate onSubmit={handleSubmit}>
<VerticallyCentered>
<TextField
fullWidth
InputProps={{
readOnly: ottInputVisible,
}}
type="email"
label={t("ENTER_EMAIL")}
value={values.email}
onChange={handleChange("email")}
error={Boolean(errors.email)}
helperText={errors.email}
autoFocus
disabled={loading}
/>
{ottInputVisible && (
<TextField
fullWidth
type="text"
label={t("ENTER_OTT")}
value={values.ott}
onChange={handleChange("ott")}
error={Boolean(errors.ott)}
helperText={errors.ott}
disabled={loading}
/>
)}
<SubmitButton
success={success}
sx={{ mt: 2 }}
loading={loading}
buttonText={
!ottInputVisible
? t("SEND_OTT")
: t("VERIFY")
}
/>
</VerticallyCentered>
</form>
<FormPaperFooter
style={{
justifyContent: ottInputVisible
? "space-between"
: "normal",
}}
>
{ottInputVisible && (
<LinkButton
onClick={() => setShowOttInputVisibility(false)}
>
{t("CHANGE_EMAIL")}?
</LinkButton>
)}
<LinkButton onClick={goToApp}>
{t("GO_BACK")}
</LinkButton>
</FormPaperFooter>
</>
)}
</Formik>
);
}

View file

@ -13,8 +13,7 @@ import {
convertBase64ToBuffer,
convertBufferToBase64,
} from "@ente/accounts/utils";
import { APP_HOMES } from "@ente/shared/apps/constants";
import type { PageProps } from "@ente/shared/apps/types";
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
@ -34,8 +33,13 @@ import type { KEK, KeyAttributes, User } from "@ente/shared/user/types";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import type { PageProps } from "../types/page";
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext;
const appNameOld = appNameToAppNameOld(appName);
export default function ChangePassword({ appName }: PageProps) {
const [token, setToken] = useState<string>();
const [user, setUser] = useState<User>();
@ -123,7 +127,7 @@ export default function ChangePassword({ appName }: PageProps) {
const redirectToAppHome = () => {
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
router.push(APP_HOMES.get(appNameOld) ?? "/");
};
// TODO: Handle the case where user is not loaded yet.
@ -146,4 +150,6 @@ export default function ChangePassword({ appName }: PageProps) {
</FormPaper>
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -1,8 +1,7 @@
import { isDevBuild } from "@/next/env";
import log from "@/next/log";
import { ensure } from "@/utils/ensure";
import { APP_HOMES } from "@ente/shared/apps/constants";
import type { PageProps } from "@ente/shared/apps/types";
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import FormPaper from "@ente/shared/components/Form/FormPaper";
@ -51,10 +50,13 @@ import {
generateSRPSetupAttributes,
loginViaSRP,
} from "../services/srp";
import type { PageProps } from "../types/page";
import type { SRPAttributes } from "../types/srp";
export default function Credentials({ appContext, appName }: PageProps) {
const { logout } = appContext;
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName, logout } = appContext;
const appNameOld = appNameToAppNameOld(appName);
const [srpAttributes, setSrpAttributes] = useState<SRPAttributes>();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
@ -88,7 +90,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
const token = getToken();
if (key && token) {
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
router.push(APP_HOMES.get(appNameOld) ?? "/");
return;
}
const kekEncryptedAttributes: B64EncryptionResult = getKey(
@ -248,7 +250,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
}
const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
InMemoryStore.delete(MS_KEYS.REDIRECT_URL);
router.push(redirectURL ?? APP_HOMES.get(appName));
router.push(redirectURL ?? APP_HOMES.get(appNameOld));
} catch (e) {
log.error("useMasterPassword failed", e);
}
@ -293,7 +295,9 @@ export default function Credentials({ appContext, appName }: PageProps) {
</FormPaper>
</VerticallyCentered>
);
}
};
export default Page;
const Header: React.FC<React.PropsWithChildren> = ({ children }) => {
return (

View file

@ -7,8 +7,7 @@ import SetPasswordForm, {
import { PAGES } from "@ente/accounts/constants/pages";
import { configureSRP } from "@ente/accounts/services/srp";
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
import { APP_HOMES } from "@ente/shared/apps/constants";
import type { PageProps } from "@ente/shared/apps/types";
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import FormPaper from "@ente/shared/components/Form/FormPaper";
@ -30,9 +29,12 @@ import type { KeyAttributes, User } from "@ente/shared/user/types";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import type { PageProps } from "../types/page";
export default function Generate({ appContext, appName }: PageProps) {
const { logout } = appContext;
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName, logout } = appContext;
const appNameOld = appNameToAppNameOld(appName);
const [token, setToken] = useState<string>();
const [user, setUser] = useState<User>();
@ -57,7 +59,7 @@ export default function Generate({ appContext, appName }: PageProps) {
setLoading(false);
} else {
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
router.push(APP_HOMES.get(appNameOld) ?? "/");
}
} else if (keyAttributes?.encryptedKey) {
router.push(PAGES.CREDENTIALS);
@ -103,12 +105,12 @@ export default function Generate({ appContext, appName }: PageProps) {
</VerticallyCentered>
) : recoverModalView ? (
<RecoveryKey
appContext={appContext}
isMobile={appContext.isMobile}
show={recoverModalView}
onHide={() => {
setRecoveryModalView(false);
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
router.push(APP_HOMES.get(appNameOld) ?? "/");
}}
/* TODO: Why is this error being ignored */
somethingWentWrong={() => {}}
@ -132,4 +134,6 @@ export default function Generate({ appContext, appName }: PageProps) {
)}
</>
);
}
};
export default Page;

View file

@ -1,14 +1,16 @@
import type { PageProps } from "@ente/shared/apps/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import Login from "../components/Login";
import React, { useEffect, useState } from "react";
import { Login } from "../components/Login";
import { PAGES } from "../constants/pages";
import type { PageProps } from "../types/page";
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName, showNavBar } = appContext;
export default function LoginPage({ appContext, appName }: PageProps) {
const [loading, setLoading] = useState(true);
const router = useRouter();
@ -19,7 +21,7 @@ export default function LoginPage({ appContext, appName }: PageProps) {
router.push(PAGES.VERIFY);
}
setLoading(false);
appContext.showNavBar(true);
showNavBar(true);
}, []);
const register = () => {
@ -37,4 +39,6 @@ export default function LoginPage({ appContext, appName }: PageProps) {
</FormPaper>
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -4,9 +4,9 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner";
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
import { useRouter } from "next/router";
import { useEffect } from "react";
import React, { useEffect } from "react";
const PasskeysFinishPage = () => {
const Page: React.FC = () => {
const router = useRouter();
const init = async () => {
@ -43,4 +43,4 @@ const PasskeysFinishPage = () => {
);
};
export default PasskeysFinishPage;
export default Page;

View file

@ -2,8 +2,7 @@ import log from "@/next/log";
import { ensure } from "@/utils/ensure";
import { sendOtt } from "@ente/accounts/api/user";
import { PAGES } from "@ente/accounts/constants/pages";
import { APP_HOMES } from "@ente/shared/apps/constants";
import type { PageProps } from "@ente/shared/apps/types";
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
@ -24,12 +23,17 @@ import type { KeyAttributes, User } from "@ente/shared/user/types";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import type { PageProps } from "../types/page";
const bip39 = require("bip39");
// mobile client library only supports english.
bip39.setDefaultWordlist("english");
export default function Recover({ appContext, appName }: PageProps) {
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext;
const appNameOld = appNameToAppNameOld(appName);
const [keyAttributes, setKeyAttributes] = useState<
KeyAttributes | undefined
>();
@ -45,7 +49,7 @@ export default function Recover({ appContext, appName }: PageProps) {
return;
}
if (!user?.encryptedToken && !user?.token) {
sendOtt(appName, user.email);
sendOtt(appNameOld, user.email);
InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.RECOVER);
router.push(PAGES.VERIFY);
return;
@ -54,7 +58,7 @@ export default function Recover({ appContext, appName }: PageProps) {
router.push(PAGES.GENERATE);
} else if (key) {
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
router.push(APP_HOMES.get(appNameOld) ?? "/");
} else {
setKeyAttributes(keyAttributes);
}
@ -127,4 +131,6 @@ export default function Recover({ appContext, appName }: PageProps) {
</FormPaper>
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -1,14 +1,16 @@
import SignUp from "@ente/accounts/components/SignUp";
import { PAGES } from "@ente/accounts/constants/pages";
import { LS_KEYS, getData } from "@ente/shared//storage/localStorage";
import type { PageProps } from "@ente/shared/apps/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { SignUp } from "../components/SignUp";
import type { PageProps } from "../types/page";
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext;
export default function SignUpPage({ appContext, appName }: PageProps) {
const [loading, setLoading] = useState(true);
const router = useRouter();
@ -37,4 +39,6 @@ export default function SignUpPage({ appContext, appName }: PageProps) {
)}
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -1,9 +1,10 @@
import log from "@/next/log";
import type { BaseAppContextT } from "@/next/types/app";
import { ensure } from "@/utils/ensure";
import { recoverTwoFactor, removeTwoFactor } from "@ente/accounts/api/user";
import { PAGES } from "@ente/accounts/constants/pages";
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
import type { PageProps } from "@ente/shared/apps/types";
import { APPS } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
import FormPaper from "@ente/shared/components/Form/FormPaper";
@ -29,10 +30,16 @@ const bip39 = require("bip39");
// mobile client library only supports english.
bip39.setDefaultWordlist("english");
export default function Recover({
export interface RecoverPageProps {
appContext: BaseAppContextT;
appName?: APPS;
twoFactorType?: TwoFactorType;
}
const Page: React.FC<RecoverPageProps> = ({
appContext,
twoFactorType = TwoFactorType.TOTP,
}: PageProps) {
}) => {
const { logout } = appContext;
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
@ -182,4 +189,6 @@ export default function Recover({
</FormPaper>
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -6,8 +6,7 @@ import VerifyTwoFactor, {
} from "@ente/accounts/components/two-factor/VerifyForm";
import { TwoFactorSetup } from "@ente/accounts/components/two-factor/setup";
import type { TwoFactorSecret } from "@ente/accounts/types/user";
import { APP_HOMES } from "@ente/shared/apps/constants";
import type { PageProps } from "@ente/shared/apps/types";
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import LinkButton from "@ente/shared/components/LinkButton";
import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers";
@ -17,13 +16,18 @@ import Card from "@mui/material/Card";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import type { PageProps } from "../../types/page";
export enum SetupMode {
QR_CODE,
MANUAL_CODE,
}
export default function SetupTwoFactor({ appName }: PageProps) {
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext;
const appNameOld = appNameToAppNameOld(appName);
const [twoFactorSecret, setTwoFactorSecret] = useState<
TwoFactorSecret | undefined
>();
@ -59,7 +63,7 @@ export default function SetupTwoFactor({ appName }: PageProps) {
isTwoFactorEnabled: true,
});
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appName) ?? "/");
router.push(APP_HOMES.get(appNameOld) ?? "/");
};
return (
@ -85,4 +89,6 @@ export default function SetupTwoFactor({ appName }: PageProps) {
</Card>
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -1,11 +1,9 @@
import { ensure } from "@/utils/ensure";
import { verifyTwoFactor } from "@ente/accounts/api/user";
import VerifyTwoFactor, {
type VerifyTwoFactorCallback,
} from "@ente/accounts/components/two-factor/VerifyForm";
import { PAGES } from "@ente/accounts/constants/pages";
import { ensure } from "@/utils/ensure";
import type { PageProps } from "@ente/shared/apps/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
@ -19,10 +17,9 @@ import { HttpStatusCode } from "axios";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import type { PageProps } from "../../types/page";
export const TwoFactorVerify: React.FC<PageProps> = ({
appContext,
}: PageProps) => {
const Page: React.FC<PageProps> = ({ appContext }) => {
const { logout } = appContext;
const [sessionID, setSessionID] = useState("");
@ -93,4 +90,4 @@ export const TwoFactorVerify: React.FC<PageProps> = ({
);
};
export default TwoFactorVerify;
export default Page;

View file

@ -1,6 +1,6 @@
import { ensure } from "@/utils/ensure";
import type { UserVerificationResponse } from "@ente/accounts/types/user";
import type { PageProps } from "@ente/shared/apps/types";
import { appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import FormPaper from "@ente/shared/components/Form/FormPaper";
@ -30,10 +30,13 @@ import { Trans } from "react-i18next";
import { putAttributes, sendOtt, verifyOtt } from "../api/user";
import { PAGES } from "../constants/pages";
import { configureSRP } from "../services/srp";
import type { PageProps } from "../types/page";
import type { SRPSetupAttributes } from "../types/srp";
export default function VerifyPage({ appContext, appName }: PageProps) {
const { logout } = appContext;
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName, logout } = appContext;
const appNameOld = appNameToAppNameOld(appName);
const [email, setEmail] = useState("");
const [resend, setResend] = useState(0);
@ -148,7 +151,7 @@ export default function VerifyPage({ appContext, appName }: PageProps) {
const resendEmail = async () => {
setResend(1);
await sendOtt(appName, email);
await sendOtt(appNameOld, email);
setResend(2);
setTimeout(() => setResend(0), 3000);
};
@ -199,4 +202,6 @@ export default function VerifyPage({ appContext, appName }: PageProps) {
</FormPaper>
</VerticallyCentered>
);
}
};
export default Page;

View file

@ -0,0 +1,24 @@
import type { AppName } from "@/next/types/app";
import {
ACCOUNTS_PAGES,
AUTH_PAGES,
PHOTOS_PAGES,
} from "@ente/shared/constants/pages";
/**
* The "home" route for each of our apps.
*
* This is where we redirect to after successful authentication.
*/
export const appHomeRoute = (appName: AppName): string => {
switch (appName) {
case "account":
return ACCOUNTS_PAGES.PASSKEYS;
case "albums":
return "/";
case "auth":
return AUTH_PAGES.AUTH;
case "photos":
return PHOTOS_PAGES.GALLERY;
}
};

View file

@ -0,0 +1,16 @@
import type { BaseAppContextT } from "@/next/types/app";
/**
* The default type for pages exposed by this package.
*
* Some specific pages might extend this further (e.g. the two-factor/recover).
*/
export interface PageProps {
/**
* The common denominator AppContext.
*
* Within this package we do not have access to the context object declared
* with the app's code, so we need to take the context as a parameter.
*/
appContext: BaseAppContextT;
}

View file

@ -0,0 +1,22 @@
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
/**
* Arbitrary names that we used as keys for indexing various constants
* corresponding to our apps that rely on this package.
*/
export type AppName = "account" | "albums" | "auth" | "photos";
/**
* Properties guaranteed to be present in the AppContext types for apps that are
* listed in {@link AppName}.
*/
export interface BaseAppContextT {
/** The unique key for the app. */
appName: AppName;
/** Perform the (possibly app specific) logout sequence. */
logout: () => void;
/** Show or hide the app's navigation bar. */
showNavBar: (show: boolean) => void;
isMobile: boolean;
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
}

View file

@ -1,3 +1,4 @@
import type { AppName } from "@/next/types/app";
import { ACCOUNTS_PAGES, AUTH_PAGES, PHOTOS_PAGES } from "../constants/pages";
export enum APPS {
@ -7,6 +8,19 @@ export enum APPS {
ACCOUNTS = "ACCOUNTS",
}
export const appNameToAppNameOld = (appName: AppName): APPS => {
switch (appName) {
case "account":
return APPS.ACCOUNTS;
case "albums":
return APPS.ALBUMS;
case "photos":
return APPS.PHOTOS;
case "auth":
return APPS.AUTH;
}
};
export const CLIENT_PACKAGE_NAMES = new Map([
[APPS.ALBUMS, "io.ente.albums.web"],
[APPS.PHOTOS, "io.ente.photos.web"],

View file

@ -1,14 +0,0 @@
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
import { APPS } from "./constants";
export interface PageProps {
appContext: {
showNavBar: (show: boolean) => void;
isMobile: boolean;
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
logout: () => void;
};
appName: APPS;
twoFactorType?: TwoFactorType;
}

View file

@ -1,5 +1,4 @@
import { ensure } from "@/utils/ensure";
import type { PageProps } from "@ente/shared/apps/types";
import CodeBlock from "@ente/shared/components/CodeBlock";
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
import { getRecoveryKey } from "@ente/shared/crypto/helpers";
@ -22,13 +21,13 @@ bip39.setDefaultWordlist("english");
const RECOVERY_KEY_FILE_NAME = "ente-recovery-key.txt";
interface Props {
appContext: PageProps["appContext"];
isMobile: boolean;
show: boolean;
onHide: () => void;
somethingWentWrong: any;
}
function RecoveryKey({ somethingWentWrong, appContext, ...props }: Props) {
function RecoveryKey({ somethingWentWrong, isMobile, ...props }: Props) {
const [recoveryKey, setRecoveryKey] = useState<string | null>(null);
useEffect(() => {
@ -54,7 +53,7 @@ function RecoveryKey({ somethingWentWrong, appContext, ...props }: Props) {
return (
<Dialog
fullScreen={appContext.isMobile}
fullScreen={isMobile}
open={props.show}
onClose={props.onHide}
maxWidth="xs"