[web] App context refactoring (#1879)
This commit is contained in:
commit
c5aa536c3b
|
@ -1,6 +1,8 @@
|
||||||
import { CustomHead } from "@/next/components/Head";
|
import { CustomHead } from "@/next/components/Head";
|
||||||
import { setupI18n } from "@/next/i18n";
|
import { setupI18n } from "@/next/i18n";
|
||||||
import { logUnhandledErrorsAndRejections } from "@/next/log-web";
|
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 { PAGES } from "@ente/accounts/constants/pages";
|
||||||
import { accountLogout } from "@ente/accounts/services/logout";
|
import { accountLogout } from "@ente/accounts/services/logout";
|
||||||
import { APPS, APP_TITLES } from "@ente/shared/apps/constants";
|
import { APPS, APP_TITLES } from "@ente/shared/apps/constants";
|
||||||
|
@ -19,19 +21,21 @@ import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { AppProps } from "next/app";
|
import { AppProps } from "next/app";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { createContext, useEffect, useState } from "react";
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
import "styles/global.css";
|
import "styles/global.css";
|
||||||
|
|
||||||
interface AppContextProps {
|
/** The accounts app has no extra properties on top of the base context. */
|
||||||
isMobile: boolean;
|
type AppContextT = BaseAppContextT;
|
||||||
showNavBar: (show: boolean) => void;
|
|
||||||
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
|
|
||||||
logout: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
|
const appName: AppName = "account";
|
||||||
|
|
||||||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||||
|
|
||||||
const [showNavbar, setShowNavBar] = useState(false);
|
const [showNavbar, setShowNavBar] = useState(false);
|
||||||
|
@ -83,6 +87,14 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
void accountLogout().then(() => router.push(PAGES.ROOT));
|
void accountLogout().then(() => router.push(PAGES.ROOT));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appContext = {
|
||||||
|
appName,
|
||||||
|
logout,
|
||||||
|
showNavBar,
|
||||||
|
isMobile,
|
||||||
|
setDialogBoxAttributesV2,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: This string doesn't actually exist
|
// TODO: This string doesn't actually exist
|
||||||
const title = isI18nReady
|
const title = isI18nReady
|
||||||
? t("title", { context: "accounts" })
|
? t("title", { context: "accounts" })
|
||||||
|
@ -101,14 +113,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
attributes={dialogBoxAttributeV2 as any}
|
attributes={dialogBoxAttributeV2 as any}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppContext.Provider
|
<AppContext.Provider value={appContext}>
|
||||||
value={{
|
|
||||||
isMobile,
|
|
||||||
showNavBar,
|
|
||||||
setDialogBoxAttributesV2,
|
|
||||||
logout,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!isI18nReady && (
|
{!isI18nReady && (
|
||||||
<Overlay
|
<Overlay
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
|
|
6
web/apps/accounts/src/pages/credentials.tsx
Normal file
6
web/apps/accounts/src/pages/credentials.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/credentials";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/generate.tsx
Normal file
6
web/apps/accounts/src/pages/generate.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/generate";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/login.tsx
Normal file
6
web/apps/accounts/src/pages/login.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/login";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
|
@ -1,16 +1,12 @@
|
||||||
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
|
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
|
||||||
import RecoverPage from "@ente/accounts/pages/recover";
|
import RecoverPage from "@ente/accounts/pages/two-factor/recover";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
import { useAppContext } from "../../_app";
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import { useContext } from "react";
|
|
||||||
|
|
||||||
export default function Recover() {
|
const Page = () => (
|
||||||
const appContext = useContext(AppContext);
|
<RecoverPage
|
||||||
return (
|
appContext={useAppContext()}
|
||||||
<RecoverPage
|
twoFactorType={TwoFactorType.PASSKEY}
|
||||||
appContext={appContext}
|
/>
|
||||||
appName={APPS.PHOTOS}
|
);
|
||||||
twoFactorType={TwoFactorType.PASSKEY}
|
|
||||||
/>
|
export default Page;
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
6
web/apps/accounts/src/pages/recover.tsx
Normal file
6
web/apps/accounts/src/pages/recover.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/recover";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/signup.tsx
Normal file
6
web/apps/accounts/src/pages/signup.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/signup";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/two-factor/recover.tsx
Normal file
6
web/apps/accounts/src/pages/two-factor/recover.tsx
Normal 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;
|
|
@ -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} />
|
|
||||||
);
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/two-factor/setup.tsx
Normal file
6
web/apps/accounts/src/pages/two-factor/setup.tsx
Normal 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;
|
|
@ -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} />
|
|
||||||
);
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/two-factor/verify.tsx
Normal file
6
web/apps/accounts/src/pages/two-factor/verify.tsx
Normal 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;
|
|
@ -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} />
|
|
||||||
);
|
|
||||||
}
|
|
6
web/apps/accounts/src/pages/verify.tsx
Normal file
6
web/apps/accounts/src/pages/verify.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/verify";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
|
@ -4,6 +4,8 @@ import {
|
||||||
logStartupBanner,
|
logStartupBanner,
|
||||||
logUnhandledErrorsAndRejections,
|
logUnhandledErrorsAndRejections,
|
||||||
} from "@/next/log-web";
|
} 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 { accountLogout } from "@ente/accounts/services/logout";
|
||||||
import {
|
import {
|
||||||
APPS,
|
APPS,
|
||||||
|
@ -28,25 +30,30 @@ import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
import { useRouter } from "next/router";
|
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 LoadingBar, { type LoadingBarRef } from "react-top-loading-bar";
|
||||||
import "../../public/css/global.css";
|
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;
|
startLoading: () => void;
|
||||||
finishLoading: () => void;
|
finishLoading: () => void;
|
||||||
isMobile: boolean;
|
|
||||||
themeColor: THEME_COLOR;
|
themeColor: THEME_COLOR;
|
||||||
setThemeColor: (themeColor: THEME_COLOR) => void;
|
setThemeColor: (themeColor: THEME_COLOR) => void;
|
||||||
somethingWentWrong: () => 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) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
|
const appName: AppName = "auth";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
@ -131,6 +138,19 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
void accountLogout().then(() => router.push(PAGES.ROOT));
|
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
|
// TODO: Refactor this to have a fallback
|
||||||
const title = isI18nReady
|
const title = isI18nReady
|
||||||
? t("title", { context: "auth" })
|
? t("title", { context: "auth" })
|
||||||
|
@ -156,19 +176,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
attributes={dialogBoxAttributeV2}
|
attributes={dialogBoxAttributeV2}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppContext.Provider
|
<AppContext.Provider value={appContext}>
|
||||||
value={{
|
|
||||||
showNavBar,
|
|
||||||
startLoading,
|
|
||||||
finishLoading,
|
|
||||||
isMobile,
|
|
||||||
themeColor,
|
|
||||||
setThemeColor,
|
|
||||||
somethingWentWrong,
|
|
||||||
setDialogBoxAttributesV2,
|
|
||||||
logout,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(loading || !isI18nReady) && (
|
{(loading || !isI18nReady) && (
|
||||||
<Overlay
|
<Overlay
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/change-email";
|
||||||
import ChangeEmailPage from "@ente/accounts/pages/change-email";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <ChangeEmailPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/change-password";
|
||||||
import ChangePasswordPage from "@ente/accounts/pages/change-password";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <ChangePasswordPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/credentials";
|
||||||
import CredentialPage from "@ente/accounts/pages/credentials";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <CredentialPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/generate";
|
||||||
import GeneratePage from "@ente/accounts/pages/generate";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <GeneratePage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/login";
|
||||||
import LoginPage from "@ente/accounts/pages/login";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <LoginPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/recover";
|
||||||
import RecoverPage from "@ente/accounts/pages/recover";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <RecoverPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/signup";
|
||||||
import SignupPage from "@ente/accounts/pages/signup";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <SignupPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/two-factor/recover";
|
||||||
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
|
import { useAppContext } from "../_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <TwoFactorRecoverPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/two-factor/setup";
|
||||||
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
|
import { useAppContext } from "../_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <TwoFactorSetupPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/two-factor/verify";
|
||||||
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
|
import { useAppContext } from "../_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
import { AppContext } from "../_app";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <TwoFactorVerifyPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import Page_ from "@ente/accounts/pages/verify";
|
||||||
import VerifyPage from "@ente/accounts/pages/verify";
|
import { useAppContext } from "./_app";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { AppContext } from "pages/_app";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
const appContext = ensure(useContext(AppContext));
|
|
||||||
return <VerifyPage appContext={appContext} appName={APPS.AUTH} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -604,7 +604,7 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
|
||||||
label={t("PREFERENCES")}
|
label={t("PREFERENCES")}
|
||||||
/>
|
/>
|
||||||
<RecoveryKey
|
<RecoveryKey
|
||||||
appContext={appContext}
|
isMobile={appContext.isMobile}
|
||||||
show={recoverModalView}
|
show={recoverModalView}
|
||||||
onHide={closeRecoveryKeyModal}
|
onHide={closeRecoveryKeyModal}
|
||||||
somethingWentWrong={somethingWentWrong}
|
somethingWentWrong={somethingWentWrong}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import {
|
||||||
logStartupBanner,
|
logStartupBanner,
|
||||||
logUnhandledErrorsAndRejections,
|
logUnhandledErrorsAndRejections,
|
||||||
} from "@/next/log-web";
|
} from "@/next/log-web";
|
||||||
|
import type { AppName, BaseAppContextT } from "@/next/types/app";
|
||||||
import { AppUpdate } from "@/next/types/ipc";
|
import { AppUpdate } from "@/next/types/ipc";
|
||||||
|
import { ensure } from "@/utils/ensure";
|
||||||
import {
|
import {
|
||||||
APPS,
|
APPS,
|
||||||
APP_TITLES,
|
APP_TITLES,
|
||||||
|
@ -44,7 +46,7 @@ import isElectron from "is-electron";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import "photoswipe/dist/photoswipe.css";
|
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 LoadingBar from "react-top-loading-bar";
|
||||||
import DownloadManager from "services/download";
|
import DownloadManager from "services/download";
|
||||||
import { resumeExportsIfNeeded } from "services/export";
|
import { resumeExportsIfNeeded } from "services/export";
|
||||||
|
@ -74,8 +76,11 @@ const redirectMap = new Map([
|
||||||
[REDIRECTS.FAMILIES, getFamilyPortalRedirectURL],
|
[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;
|
mlSearchEnabled: boolean;
|
||||||
mapEnabled: boolean;
|
mapEnabled: boolean;
|
||||||
updateMlSearchEnabled: (enabled: boolean) => Promise<void>;
|
updateMlSearchEnabled: (enabled: boolean) => Promise<void>;
|
||||||
|
@ -89,19 +94,22 @@ type AppContextType = {
|
||||||
setWatchFolderView: (isOpen: boolean) => void;
|
setWatchFolderView: (isOpen: boolean) => void;
|
||||||
watchFolderFiles: FileList;
|
watchFolderFiles: FileList;
|
||||||
setWatchFolderFiles: (files: FileList) => void;
|
setWatchFolderFiles: (files: FileList) => void;
|
||||||
isMobile: boolean;
|
|
||||||
themeColor: THEME_COLOR;
|
themeColor: THEME_COLOR;
|
||||||
setThemeColor: (themeColor: THEME_COLOR) => void;
|
setThemeColor: (themeColor: THEME_COLOR) => void;
|
||||||
somethingWentWrong: () => void;
|
somethingWentWrong: () => void;
|
||||||
setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void;
|
|
||||||
isCFProxyDisabled: boolean;
|
isCFProxyDisabled: boolean;
|
||||||
setIsCFProxyDisabled: (disabled: boolean) => void;
|
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) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
|
const appName: AppName = "photos";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
@ -324,6 +332,32 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
void photosLogout().then(() => router.push(PAGES.ROOT));
|
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
|
const title = isI18nReady
|
||||||
? t("title", { context: "photos" })
|
? t("title", { context: "photos" })
|
||||||
: APP_TITLES.get(APPS.PHOTOS);
|
: APP_TITLES.get(APPS.PHOTOS);
|
||||||
|
@ -359,32 +393,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
attributes={notificationAttributes}
|
attributes={notificationAttributes}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppContext.Provider
|
<AppContext.Provider value={appContext}>
|
||||||
value={{
|
|
||||||
showNavBar,
|
|
||||||
mlSearchEnabled,
|
|
||||||
updateMlSearchEnabled,
|
|
||||||
startLoading,
|
|
||||||
finishLoading,
|
|
||||||
closeMessageDialog,
|
|
||||||
setDialogMessage,
|
|
||||||
watchFolderView,
|
|
||||||
setWatchFolderView,
|
|
||||||
watchFolderFiles,
|
|
||||||
setWatchFolderFiles,
|
|
||||||
isMobile,
|
|
||||||
setNotificationAttributes,
|
|
||||||
themeColor,
|
|
||||||
setThemeColor,
|
|
||||||
somethingWentWrong,
|
|
||||||
setDialogBoxAttributesV2,
|
|
||||||
mapEnabled,
|
|
||||||
updateMapEnabled,
|
|
||||||
isCFProxyDisabled,
|
|
||||||
setIsCFProxyDisabled,
|
|
||||||
logout,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(loading || !isI18nReady) && (
|
{(loading || !isI18nReady) && (
|
||||||
<Overlay
|
<Overlay
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
|
|
6
web/apps/photos/src/pages/change-email.tsx
Normal file
6
web/apps/photos/src/pages/change-email.tsx
Normal 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;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/change-password.tsx
Normal file
6
web/apps/photos/src/pages/change-password.tsx
Normal 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;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/credentials.tsx
Normal file
6
web/apps/photos/src/pages/credentials.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/credentials";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/generate.tsx
Normal file
6
web/apps/photos/src/pages/generate.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/generate";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
import log from "@/next/log";
|
import log from "@/next/log";
|
||||||
import Login from "@ente/accounts/components/Login";
|
import { Login } from "@ente/accounts/components/Login";
|
||||||
import SignUp from "@ente/accounts/components/SignUp";
|
import { SignUp } from "@ente/accounts/components/SignUp";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
|
||||||
import { EnteLogo } from "@ente/shared/components/EnteLogo";
|
import { EnteLogo } from "@ente/shared/components/EnteLogo";
|
||||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||||
|
@ -21,18 +20,18 @@ import { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel";
|
import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel";
|
||||||
import "pure-react-carousel/dist/react-carousel.es.css";
|
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 { Trans } from "react-i18next";
|
||||||
import { AppContext } from "./_app";
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
|
const { appName, showNavBar, setDialogMessage } = useAppContext();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appContext = useContext(AppContext);
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [showLogin, setShowLogin] = useState(true);
|
const [showLogin, setShowLogin] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
appContext.showNavBar(false);
|
showNavBar(false);
|
||||||
const currentURL = new URL(window.location.href);
|
const currentURL = new URL(window.location.href);
|
||||||
const albumsURL = new URL(getAlbumsURL());
|
const albumsURL = new URL(getAlbumsURL());
|
||||||
currentURL.pathname = router.pathname;
|
currentURL.pathname = router.pathname;
|
||||||
|
@ -90,7 +89,7 @@ export default function LandingPage() {
|
||||||
await localForage.ready();
|
await localForage.ready();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("usage in incognito mode tried", e);
|
log.error("usage in incognito mode tried", e);
|
||||||
appContext.setDialogMessage({
|
setDialogMessage({
|
||||||
title: t("LOCAL_STORAGE_NOT_ACCESSIBLE"),
|
title: t("LOCAL_STORAGE_NOT_ACCESSIBLE"),
|
||||||
|
|
||||||
nonClosable: true,
|
nonClosable: true,
|
||||||
|
@ -134,13 +133,9 @@ export default function LandingPage() {
|
||||||
<DesktopBox>
|
<DesktopBox>
|
||||||
<SideBox>
|
<SideBox>
|
||||||
{showLogin ? (
|
{showLogin ? (
|
||||||
<Login signUp={signUp} appName={APPS.PHOTOS} />
|
<Login {...{ signUp, appName }} />
|
||||||
) : (
|
) : (
|
||||||
<SignUp
|
<SignUp {...{ router, appName, login }} />
|
||||||
router={router}
|
|
||||||
appName={APPS.PHOTOS}
|
|
||||||
login={login}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</SideBox>
|
</SideBox>
|
||||||
</DesktopBox>
|
</DesktopBox>
|
||||||
|
|
6
web/apps/photos/src/pages/login.tsx
Normal file
6
web/apps/photos/src/pages/login.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/login";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
3
web/apps/photos/src/pages/passkeys/finish.tsx
Normal file
3
web/apps/photos/src/pages/passkeys/finish.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import Page from "@ente/accounts/pages/passkeys/finish";
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -1,11 +0,0 @@
|
||||||
import PasskeysFinishPage from "@ente/accounts/pages/passkeys/finish";
|
|
||||||
|
|
||||||
const PasskeysFinish = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PasskeysFinishPage />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PasskeysFinish;
|
|
6
web/apps/photos/src/pages/recover.tsx
Normal file
6
web/apps/photos/src/pages/recover.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/recover";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/signup.tsx
Normal file
6
web/apps/photos/src/pages/signup.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/signup";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/two-factor/recover.tsx
Normal file
6
web/apps/photos/src/pages/two-factor/recover.tsx
Normal 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;
|
|
@ -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} />
|
|
||||||
);
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/two-factor/setup.tsx
Normal file
6
web/apps/photos/src/pages/two-factor/setup.tsx
Normal 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;
|
|
@ -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} />;
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/two-factor/verify.tsx
Normal file
6
web/apps/photos/src/pages/two-factor/verify.tsx
Normal 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;
|
|
@ -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} />
|
|
||||||
);
|
|
||||||
}
|
|
6
web/apps/photos/src/pages/verify.tsx
Normal file
6
web/apps/photos/src/pages/verify.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Page_ from "@ente/accounts/pages/verify";
|
||||||
|
import { useAppContext } from "./_app";
|
||||||
|
|
||||||
|
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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} />;
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -1,5 +1,6 @@
|
||||||
import log from "@/next/log";
|
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 FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
||||||
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
|
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
|
||||||
import LinkButton from "@ente/shared/components/LinkButton";
|
import LinkButton from "@ente/shared/components/LinkButton";
|
||||||
|
@ -16,10 +17,12 @@ import { PAGES } from "../constants/pages";
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
signUp: () => void;
|
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 router = useRouter();
|
||||||
|
|
||||||
const loginUser: SingleInputFormProps["callback"] = async (
|
const loginUser: SingleInputFormProps["callback"] = async (
|
||||||
|
@ -31,7 +34,7 @@ export default function Login(props: LoginProps) {
|
||||||
const srpAttributes = await getSRPAttributes(email);
|
const srpAttributes = await getSRPAttributes(email);
|
||||||
log.debug(() => ` srpAttributes: ${JSON.stringify(srpAttributes)}`);
|
log.debug(() => ` srpAttributes: ${JSON.stringify(srpAttributes)}`);
|
||||||
if (!srpAttributes || srpAttributes.isEmailMFAEnabled) {
|
if (!srpAttributes || srpAttributes.isEmailMFAEnabled) {
|
||||||
await sendOtt(props.appName, email);
|
await sendOtt(appNameOld, email);
|
||||||
router.push(PAGES.VERIFY);
|
router.push(PAGES.VERIFY);
|
||||||
} else {
|
} else {
|
||||||
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
|
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import log from "@/next/log";
|
import log from "@/next/log";
|
||||||
|
import type { AppName } from "@/next/types/app";
|
||||||
import { sendOtt } from "@ente/accounts/api/user";
|
import { sendOtt } from "@ente/accounts/api/user";
|
||||||
import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength";
|
import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength";
|
||||||
import { PAGES } from "@ente/accounts/constants/pages";
|
import { PAGES } from "@ente/accounts/constants/pages";
|
||||||
import { isWeakPassword } from "@ente/accounts/utils";
|
import { isWeakPassword } from "@ente/accounts/utils";
|
||||||
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
|
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
|
||||||
import { APPS } from "@ente/shared/apps/constants";
|
import { LS_KEYS } from "@ente/shared//storage/localStorage";
|
||||||
import { VerticallyCentered } from "@ente/shared/components//Container";
|
import { appNameToAppNameOld } from "@ente/shared/apps/constants";
|
||||||
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
||||||
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
|
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
|
||||||
import ShowHidePassword from "@ente/shared/components/Form/ShowHidePassword";
|
import ShowHidePassword from "@ente/shared/components/Form/ShowHidePassword";
|
||||||
|
@ -15,7 +17,7 @@ import {
|
||||||
generateAndSaveIntermediateKeyAttributes,
|
generateAndSaveIntermediateKeyAttributes,
|
||||||
saveKeyInSessionStore,
|
saveKeyInSessionStore,
|
||||||
} from "@ente/shared/crypto/helpers";
|
} from "@ente/shared/crypto/helpers";
|
||||||
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
|
import { setData } from "@ente/shared/storage/localStorage";
|
||||||
import {
|
import {
|
||||||
setJustSignedUp,
|
setJustSignedUp,
|
||||||
setLocalReferralSource,
|
setLocalReferralSource,
|
||||||
|
@ -51,10 +53,12 @@ interface FormValues {
|
||||||
interface SignUpProps {
|
interface SignUpProps {
|
||||||
router: NextRouter;
|
router: NextRouter;
|
||||||
login: () => void;
|
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 [acceptTerms, setAcceptTerms] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
@ -82,7 +86,7 @@ export default function SignUp({ router, appName, login }: SignUpProps) {
|
||||||
try {
|
try {
|
||||||
setData(LS_KEYS.USER, { email });
|
setData(LS_KEYS.USER, { email });
|
||||||
setLocalReferralSource(referral);
|
setLocalReferralSource(referral);
|
||||||
await sendOtt(appName, email);
|
await sendOtt(appNameOld, email);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = e instanceof Error ? e.message : "";
|
const message = e instanceof Error ? e.message : "";
|
||||||
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${message}`);
|
setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${message}`);
|
||||||
|
|
|
@ -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 { 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 { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
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 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 { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
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();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -23,10 +41,175 @@ function ChangeEmailPage({ appName, appContext }: PageProps) {
|
||||||
<VerticallyCentered>
|
<VerticallyCentered>
|
||||||
<FormPaper>
|
<FormPaper>
|
||||||
<FormPaperTitle>{t("CHANGE_EMAIL")}</FormPaperTitle>
|
<FormPaperTitle>{t("CHANGE_EMAIL")}</FormPaperTitle>
|
||||||
<ChangeEmailForm appName={appName} appContext={appContext} />
|
<ChangeEmailForm appName={appNameOld} />
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@ import {
|
||||||
convertBase64ToBuffer,
|
convertBase64ToBuffer,
|
||||||
convertBufferToBase64,
|
convertBufferToBase64,
|
||||||
} from "@ente/accounts/utils";
|
} from "@ente/accounts/utils";
|
||||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
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 { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
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 [token, setToken] = useState<string>();
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
|
|
||||||
|
@ -123,7 +127,7 @@ export default function ChangePassword({ appName }: PageProps) {
|
||||||
const redirectToAppHome = () => {
|
const redirectToAppHome = () => {
|
||||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
// 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.
|
// TODO: Handle the case where user is not loaded yet.
|
||||||
|
@ -146,4 +150,6 @@ export default function ChangePassword({ appName }: PageProps) {
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { isDevBuild } from "@/next/env";
|
import { isDevBuild } from "@/next/env";
|
||||||
import log from "@/next/log";
|
import log from "@/next/log";
|
||||||
import { ensure } from "@/utils/ensure";
|
import { ensure } from "@/utils/ensure";
|
||||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
|
@ -51,10 +50,13 @@ import {
|
||||||
generateSRPSetupAttributes,
|
generateSRPSetupAttributes,
|
||||||
loginViaSRP,
|
loginViaSRP,
|
||||||
} from "../services/srp";
|
} from "../services/srp";
|
||||||
|
import type { PageProps } from "../types/page";
|
||||||
import type { SRPAttributes } from "../types/srp";
|
import type { SRPAttributes } from "../types/srp";
|
||||||
|
|
||||||
export default function Credentials({ appContext, appName }: PageProps) {
|
const Page: React.FC<PageProps> = ({ appContext }) => {
|
||||||
const { logout } = appContext;
|
const { appName, logout } = appContext;
|
||||||
|
|
||||||
|
const appNameOld = appNameToAppNameOld(appName);
|
||||||
|
|
||||||
const [srpAttributes, setSrpAttributes] = useState<SRPAttributes>();
|
const [srpAttributes, setSrpAttributes] = useState<SRPAttributes>();
|
||||||
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
||||||
|
@ -88,7 +90,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
if (key && token) {
|
if (key && token) {
|
||||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||||
router.push(APP_HOMES.get(appName) ?? "/");
|
router.push(APP_HOMES.get(appNameOld) ?? "/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const kekEncryptedAttributes: B64EncryptionResult = getKey(
|
const kekEncryptedAttributes: B64EncryptionResult = getKey(
|
||||||
|
@ -248,7 +250,7 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
||||||
}
|
}
|
||||||
const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
|
const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
|
||||||
InMemoryStore.delete(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) {
|
} catch (e) {
|
||||||
log.error("useMasterPassword failed", e);
|
log.error("useMasterPassword failed", e);
|
||||||
}
|
}
|
||||||
|
@ -293,7 +295,9 @@ export default function Credentials({ appContext, appName }: PageProps) {
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
||||||
const Header: React.FC<React.PropsWithChildren> = ({ children }) => {
|
const Header: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -7,8 +7,7 @@ import SetPasswordForm, {
|
||||||
import { PAGES } from "@ente/accounts/constants/pages";
|
import { PAGES } from "@ente/accounts/constants/pages";
|
||||||
import { configureSRP } from "@ente/accounts/services/srp";
|
import { configureSRP } from "@ente/accounts/services/srp";
|
||||||
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
|
import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
|
||||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
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 { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import type { PageProps } from "../types/page";
|
||||||
|
|
||||||
export default function Generate({ appContext, appName }: PageProps) {
|
const Page: React.FC<PageProps> = ({ appContext }) => {
|
||||||
const { logout } = appContext;
|
const { appName, logout } = appContext;
|
||||||
|
|
||||||
|
const appNameOld = appNameToAppNameOld(appName);
|
||||||
|
|
||||||
const [token, setToken] = useState<string>();
|
const [token, setToken] = useState<string>();
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
|
@ -57,7 +59,7 @@ export default function Generate({ appContext, appName }: PageProps) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
// 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) {
|
} else if (keyAttributes?.encryptedKey) {
|
||||||
router.push(PAGES.CREDENTIALS);
|
router.push(PAGES.CREDENTIALS);
|
||||||
|
@ -103,12 +105,12 @@ export default function Generate({ appContext, appName }: PageProps) {
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
) : recoverModalView ? (
|
) : recoverModalView ? (
|
||||||
<RecoveryKey
|
<RecoveryKey
|
||||||
appContext={appContext}
|
isMobile={appContext.isMobile}
|
||||||
show={recoverModalView}
|
show={recoverModalView}
|
||||||
onHide={() => {
|
onHide={() => {
|
||||||
setRecoveryModalView(false);
|
setRecoveryModalView(false);
|
||||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
// 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 */
|
/* TODO: Why is this error being ignored */
|
||||||
somethingWentWrong={() => {}}
|
somethingWentWrong={() => {}}
|
||||||
|
@ -132,4 +134,6 @@ export default function Generate({ appContext, appName }: PageProps) {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Login from "../components/Login";
|
import { Login } from "../components/Login";
|
||||||
import { PAGES } from "../constants/pages";
|
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 [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -19,7 +21,7 @@ export default function LoginPage({ appContext, appName }: PageProps) {
|
||||||
router.push(PAGES.VERIFY);
|
router.push(PAGES.VERIFY);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
appContext.showNavBar(true);
|
showNavBar(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const register = () => {
|
const register = () => {
|
||||||
|
@ -37,4 +39,6 @@ export default function LoginPage({ appContext, appName }: PageProps) {
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -4,9 +4,9 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
||||||
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
const PasskeysFinishPage = () => {
|
const Page: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
|
@ -43,4 +43,4 @@ const PasskeysFinishPage = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasskeysFinishPage;
|
export default Page;
|
||||||
|
|
|
@ -2,8 +2,7 @@ import log from "@/next/log";
|
||||||
import { ensure } from "@/utils/ensure";
|
import { ensure } from "@/utils/ensure";
|
||||||
import { sendOtt } from "@ente/accounts/api/user";
|
import { sendOtt } from "@ente/accounts/api/user";
|
||||||
import { PAGES } from "@ente/accounts/constants/pages";
|
import { PAGES } from "@ente/accounts/constants/pages";
|
||||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
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 { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import type { PageProps } from "../types/page";
|
||||||
|
|
||||||
const bip39 = require("bip39");
|
const bip39 = require("bip39");
|
||||||
// mobile client library only supports english.
|
// mobile client library only supports english.
|
||||||
bip39.setDefaultWordlist("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<
|
const [keyAttributes, setKeyAttributes] = useState<
|
||||||
KeyAttributes | undefined
|
KeyAttributes | undefined
|
||||||
>();
|
>();
|
||||||
|
@ -45,7 +49,7 @@ export default function Recover({ appContext, appName }: PageProps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!user?.encryptedToken && !user?.token) {
|
if (!user?.encryptedToken && !user?.token) {
|
||||||
sendOtt(appName, user.email);
|
sendOtt(appNameOld, user.email);
|
||||||
InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.RECOVER);
|
InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.RECOVER);
|
||||||
router.push(PAGES.VERIFY);
|
router.push(PAGES.VERIFY);
|
||||||
return;
|
return;
|
||||||
|
@ -54,7 +58,7 @@ export default function Recover({ appContext, appName }: PageProps) {
|
||||||
router.push(PAGES.GENERATE);
|
router.push(PAGES.GENERATE);
|
||||||
} else if (key) {
|
} else if (key) {
|
||||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||||
router.push(APP_HOMES.get(appName) ?? "/");
|
router.push(APP_HOMES.get(appNameOld) ?? "/");
|
||||||
} else {
|
} else {
|
||||||
setKeyAttributes(keyAttributes);
|
setKeyAttributes(keyAttributes);
|
||||||
}
|
}
|
||||||
|
@ -127,4 +131,6 @@ export default function Recover({ appContext, appName }: PageProps) {
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import SignUp from "@ente/accounts/components/SignUp";
|
|
||||||
import { PAGES } from "@ente/accounts/constants/pages";
|
import { PAGES } from "@ente/accounts/constants/pages";
|
||||||
import { LS_KEYS, getData } from "@ente/shared//storage/localStorage";
|
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 { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
import { useRouter } from "next/router";
|
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 [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -37,4 +39,6 @@ export default function SignUpPage({ appContext, appName }: PageProps) {
|
||||||
)}
|
)}
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import log from "@/next/log";
|
import log from "@/next/log";
|
||||||
|
import type { BaseAppContextT } from "@/next/types/app";
|
||||||
import { ensure } from "@/utils/ensure";
|
import { ensure } from "@/utils/ensure";
|
||||||
import { recoverTwoFactor, removeTwoFactor } from "@ente/accounts/api/user";
|
import { recoverTwoFactor, removeTwoFactor } from "@ente/accounts/api/user";
|
||||||
import { PAGES } from "@ente/accounts/constants/pages";
|
import { PAGES } from "@ente/accounts/constants/pages";
|
||||||
import { TwoFactorType } from "@ente/accounts/constants/twofactor";
|
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 { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
|
@ -29,10 +30,16 @@ const bip39 = require("bip39");
|
||||||
// mobile client library only supports english.
|
// mobile client library only supports english.
|
||||||
bip39.setDefaultWordlist("english");
|
bip39.setDefaultWordlist("english");
|
||||||
|
|
||||||
export default function Recover({
|
export interface RecoverPageProps {
|
||||||
|
appContext: BaseAppContextT;
|
||||||
|
appName?: APPS;
|
||||||
|
twoFactorType?: TwoFactorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page: React.FC<RecoverPageProps> = ({
|
||||||
appContext,
|
appContext,
|
||||||
twoFactorType = TwoFactorType.TOTP,
|
twoFactorType = TwoFactorType.TOTP,
|
||||||
}: PageProps) {
|
}) => {
|
||||||
const { logout } = appContext;
|
const { logout } = appContext;
|
||||||
|
|
||||||
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
|
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
|
||||||
|
@ -182,4 +189,6 @@ export default function Recover({
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -6,8 +6,7 @@ import VerifyTwoFactor, {
|
||||||
} from "@ente/accounts/components/two-factor/VerifyForm";
|
} from "@ente/accounts/components/two-factor/VerifyForm";
|
||||||
import { TwoFactorSetup } from "@ente/accounts/components/two-factor/setup";
|
import { TwoFactorSetup } from "@ente/accounts/components/two-factor/setup";
|
||||||
import type { TwoFactorSecret } from "@ente/accounts/types/user";
|
import type { TwoFactorSecret } from "@ente/accounts/types/user";
|
||||||
import { APP_HOMES } from "@ente/shared/apps/constants";
|
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import LinkButton from "@ente/shared/components/LinkButton";
|
import LinkButton from "@ente/shared/components/LinkButton";
|
||||||
import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers";
|
import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers";
|
||||||
|
@ -17,13 +16,18 @@ import Card from "@mui/material/Card";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import type { PageProps } from "../../types/page";
|
||||||
|
|
||||||
export enum SetupMode {
|
export enum SetupMode {
|
||||||
QR_CODE,
|
QR_CODE,
|
||||||
MANUAL_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<
|
const [twoFactorSecret, setTwoFactorSecret] = useState<
|
||||||
TwoFactorSecret | undefined
|
TwoFactorSecret | undefined
|
||||||
>();
|
>();
|
||||||
|
@ -59,7 +63,7 @@ export default function SetupTwoFactor({ appName }: PageProps) {
|
||||||
isTwoFactorEnabled: true,
|
isTwoFactorEnabled: true,
|
||||||
});
|
});
|
||||||
// TODO: Refactor the type of APP_HOMES to not require the ??
|
// TODO: Refactor the type of APP_HOMES to not require the ??
|
||||||
router.push(APP_HOMES.get(appName) ?? "/");
|
router.push(APP_HOMES.get(appNameOld) ?? "/");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -85,4 +89,6 @@ export default function SetupTwoFactor({ appName }: PageProps) {
|
||||||
</Card>
|
</Card>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
|
import { ensure } from "@/utils/ensure";
|
||||||
import { verifyTwoFactor } from "@ente/accounts/api/user";
|
import { verifyTwoFactor } from "@ente/accounts/api/user";
|
||||||
import VerifyTwoFactor, {
|
import VerifyTwoFactor, {
|
||||||
type VerifyTwoFactorCallback,
|
type VerifyTwoFactorCallback,
|
||||||
} from "@ente/accounts/components/two-factor/VerifyForm";
|
} from "@ente/accounts/components/two-factor/VerifyForm";
|
||||||
import { PAGES } from "@ente/accounts/constants/pages";
|
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 { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||||
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
|
||||||
|
@ -19,10 +17,9 @@ import { HttpStatusCode } from "axios";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import type { PageProps } from "../../types/page";
|
||||||
|
|
||||||
export const TwoFactorVerify: React.FC<PageProps> = ({
|
const Page: React.FC<PageProps> = ({ appContext }) => {
|
||||||
appContext,
|
|
||||||
}: PageProps) => {
|
|
||||||
const { logout } = appContext;
|
const { logout } = appContext;
|
||||||
|
|
||||||
const [sessionID, setSessionID] = useState("");
|
const [sessionID, setSessionID] = useState("");
|
||||||
|
@ -93,4 +90,4 @@ export const TwoFactorVerify: React.FC<PageProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TwoFactorVerify;
|
export default Page;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import { ensure } from "@/utils/ensure";
|
||||||
import type { UserVerificationResponse } from "@ente/accounts/types/user";
|
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 { VerticallyCentered } from "@ente/shared/components/Container";
|
||||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
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 { putAttributes, sendOtt, verifyOtt } from "../api/user";
|
||||||
import { PAGES } from "../constants/pages";
|
import { PAGES } from "../constants/pages";
|
||||||
import { configureSRP } from "../services/srp";
|
import { configureSRP } from "../services/srp";
|
||||||
|
import type { PageProps } from "../types/page";
|
||||||
import type { SRPSetupAttributes } from "../types/srp";
|
import type { SRPSetupAttributes } from "../types/srp";
|
||||||
|
|
||||||
export default function VerifyPage({ appContext, appName }: PageProps) {
|
const Page: React.FC<PageProps> = ({ appContext }) => {
|
||||||
const { logout } = appContext;
|
const { appName, logout } = appContext;
|
||||||
|
|
||||||
|
const appNameOld = appNameToAppNameOld(appName);
|
||||||
|
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [resend, setResend] = useState(0);
|
const [resend, setResend] = useState(0);
|
||||||
|
@ -148,7 +151,7 @@ export default function VerifyPage({ appContext, appName }: PageProps) {
|
||||||
|
|
||||||
const resendEmail = async () => {
|
const resendEmail = async () => {
|
||||||
setResend(1);
|
setResend(1);
|
||||||
await sendOtt(appName, email);
|
await sendOtt(appNameOld, email);
|
||||||
setResend(2);
|
setResend(2);
|
||||||
setTimeout(() => setResend(0), 3000);
|
setTimeout(() => setResend(0), 3000);
|
||||||
};
|
};
|
||||||
|
@ -199,4 +202,6 @@ export default function VerifyPage({ appContext, appName }: PageProps) {
|
||||||
</FormPaper>
|
</FormPaper>
|
||||||
</VerticallyCentered>
|
</VerticallyCentered>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
24
web/packages/accounts/services/redirect.ts
Normal file
24
web/packages/accounts/services/redirect.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
16
web/packages/accounts/types/page.ts
Normal file
16
web/packages/accounts/types/page.ts
Normal 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;
|
||||||
|
}
|
22
web/packages/next/types/app.ts
Normal file
22
web/packages/next/types/app.ts
Normal 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;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { AppName } from "@/next/types/app";
|
||||||
import { ACCOUNTS_PAGES, AUTH_PAGES, PHOTOS_PAGES } from "../constants/pages";
|
import { ACCOUNTS_PAGES, AUTH_PAGES, PHOTOS_PAGES } from "../constants/pages";
|
||||||
|
|
||||||
export enum APPS {
|
export enum APPS {
|
||||||
|
@ -7,6 +8,19 @@ export enum APPS {
|
||||||
ACCOUNTS = "ACCOUNTS",
|
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([
|
export const CLIENT_PACKAGE_NAMES = new Map([
|
||||||
[APPS.ALBUMS, "io.ente.albums.web"],
|
[APPS.ALBUMS, "io.ente.albums.web"],
|
||||||
[APPS.PHOTOS, "io.ente.photos.web"],
|
[APPS.PHOTOS, "io.ente.photos.web"],
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { ensure } from "@/utils/ensure";
|
import { ensure } from "@/utils/ensure";
|
||||||
import type { PageProps } from "@ente/shared/apps/types";
|
|
||||||
import CodeBlock from "@ente/shared/components/CodeBlock";
|
import CodeBlock from "@ente/shared/components/CodeBlock";
|
||||||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||||
import { getRecoveryKey } from "@ente/shared/crypto/helpers";
|
import { getRecoveryKey } from "@ente/shared/crypto/helpers";
|
||||||
|
@ -22,13 +21,13 @@ bip39.setDefaultWordlist("english");
|
||||||
const RECOVERY_KEY_FILE_NAME = "ente-recovery-key.txt";
|
const RECOVERY_KEY_FILE_NAME = "ente-recovery-key.txt";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
appContext: PageProps["appContext"];
|
isMobile: boolean;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
somethingWentWrong: any;
|
somethingWentWrong: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RecoveryKey({ somethingWentWrong, appContext, ...props }: Props) {
|
function RecoveryKey({ somethingWentWrong, isMobile, ...props }: Props) {
|
||||||
const [recoveryKey, setRecoveryKey] = useState<string | null>(null);
|
const [recoveryKey, setRecoveryKey] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -54,7 +53,7 @@ function RecoveryKey({ somethingWentWrong, appContext, ...props }: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
fullScreen={appContext.isMobile}
|
fullScreen={isMobile}
|
||||||
open={props.show}
|
open={props.show}
|
||||||
onClose={props.onHide}
|
onClose={props.onHide}
|
||||||
maxWidth="xs"
|
maxWidth="xs"
|
||||||
|
|
Loading…
Reference in a new issue