diff --git a/web/apps/accounts/.eslintrc.js b/web/apps/accounts/.eslintrc.js index 41bd549f98..0f5863fc88 100644 --- a/web/apps/accounts/.eslintrc.js +++ b/web/apps/accounts/.eslintrc.js @@ -3,17 +3,6 @@ module.exports = { ignorePatterns: ["next.config.js", "next-env.d.ts"], /* TODO: Temporary overrides */ rules: { - "react/react-in-jsx-scope": "off", - "react/prop-types": "off", "react-hooks/exhaustive-deps": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-misused-promises": "off", - "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "react-refresh/only-export-components": "off", }, }; diff --git a/web/apps/accounts/src/components/context.ts b/web/apps/accounts/src/components/context.ts new file mode 100644 index 0000000000..d6a2755e41 --- /dev/null +++ b/web/apps/accounts/src/components/context.ts @@ -0,0 +1,15 @@ +import type { BaseAppContextT } from "@/next/types/app"; +import { ensure } from "@/utils/ensure"; +import { createContext, useContext } from "react"; + +/** The accounts app has no extra properties on top of the base context. */ +type AppContextT = BaseAppContextT; + +/** The React {@link Context} available to all pages. */ +export const AppContext = createContext(undefined); + +/** + * Utility hook to get the {@link AppContextT}, throwing an exception if it is + * not defined. + */ +export const useAppContext = (): AppContextT => ensure(useContext(AppContext)); diff --git a/web/apps/accounts/src/pages/_app.tsx b/web/apps/accounts/src/pages/_app.tsx index 73c437cfd2..dc543ba0a8 100644 --- a/web/apps/accounts/src/pages/_app.tsx +++ b/web/apps/accounts/src/pages/_app.tsx @@ -1,8 +1,7 @@ import { CustomHead } from "@/next/components/Head"; import { setupI18n } from "@/next/i18n"; import { logUnhandledErrorsAndRejections } from "@/next/log-web"; -import { appTitle, type AppName, type BaseAppContextT } from "@/next/types/app"; -import { ensure } from "@/utils/ensure"; +import { appTitle, type AppName } from "@/next/types/app"; import { PAGES } from "@ente/accounts/constants/pages"; import { accountLogout } from "@ente/accounts/services/logout"; import { Overlay } from "@ente/shared/components/Container"; @@ -16,22 +15,15 @@ import { getTheme } from "@ente/shared/themes"; import { THEME_COLOR } from "@ente/shared/themes/constants"; import { CssBaseline, useMediaQuery } from "@mui/material"; import { ThemeProvider } from "@mui/material/styles"; +import { AppContext } from "components/context"; import { t } from "i18next"; import type { AppProps } from "next/app"; import { useRouter } from "next/router"; -import { createContext, useContext, useEffect, useState } from "react"; -import "styles/global.css"; - -/** The accounts app has no extra properties on top of the base context. */ -type AppContextT = BaseAppContextT; - -/** The React {@link Context} available to all pages. */ -export const AppContext = createContext(undefined); +import React, { useEffect, useState } from "react"; -/** Utility hook to reduce amount of boilerplate in account related pages. */ -export const useAppContext = () => ensure(useContext(AppContext)); +import "styles/global.css"; -export default function App({ Component, pageProps }: AppProps) { +const App: React.FC = ({ Component, pageProps }) => { const appName: AppName = "accounts"; const [isI18nReady, setIsI18nReady] = useState(false); @@ -57,7 +49,7 @@ export default function App({ Component, pageProps }: AppProps) { const [themeColor] = useLocalState(LS_KEYS.THEME, THEME_COLOR.DARK); useEffect(() => { - setupI18n().finally(() => setIsI18nReady(true)); + void setupI18n().finally(() => setIsI18nReady(true)); logUnhandledErrorsAndRejections(true); return () => logUnhandledErrorsAndRejections(false); }, []); @@ -92,7 +84,7 @@ export default function App({ Component, pageProps }: AppProps) { sx={{ zIndex: 1600 }} open={dialogBoxV2View} onClose={closeDialogBoxV2} - attributes={dialogBoxAttributeV2 as any} + attributes={dialogBoxAttributeV2} /> @@ -103,8 +95,7 @@ export default function App({ Component, pageProps }: AppProps) { justifyContent: "center", alignItems: "center", zIndex: 2000, - backgroundColor: (theme as any).colors - .background.base, + backgroundColor: theme.colors.background.base, })} > @@ -116,4 +107,6 @@ export default function App({ Component, pageProps }: AppProps) { ); -} +}; + +export default App; diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index 6472eeb18e..4f95759e0d 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -18,8 +18,8 @@ import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import KeyIcon from "@mui/icons-material/Key"; import { Box, Button, Stack, Typography, useMediaQuery } from "@mui/material"; +import { useAppContext } from "components/context"; import { t } from "i18next"; -import { useAppContext } from "pages/_app"; import React, { useEffect, useState } from "react"; import { deletePasskey, diff --git a/web/apps/accounts/src/pages/passkeys/verify.tsx b/web/apps/accounts/src/pages/passkeys/verify.tsx index 26cbbc7b77..013eaceb05 100644 --- a/web/apps/accounts/src/pages/passkeys/verify.tsx +++ b/web/apps/accounts/src/pages/passkeys/verify.tsx @@ -7,7 +7,7 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner"; import InfoIcon from "@mui/icons-material/Info"; import { Paper, Typography, styled } from "@mui/material"; import { t } from "i18next"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { beginPasskeyAuthentication, finishPasskeyAuthentication, @@ -50,7 +50,7 @@ const Page = () => { // Ensure that redirectURL is whitelisted, otherwise show an invalid // "login" URL error to the user. if (!redirectURL || !isWhitelistedRedirect(redirectURL)) { - log.error(`Redirect URL '${redirectURL}' is not whitelisted`); + log.error(`Redirect '${redirect}' is not whitelisted`); setStatus("unknownRedirect"); return; } diff --git a/web/apps/auth/src/pages/_app.tsx b/web/apps/auth/src/pages/_app.tsx index 1777d2915d..573e96aeb7 100644 --- a/web/apps/auth/src/pages/_app.tsx +++ b/web/apps/auth/src/pages/_app.tsx @@ -31,7 +31,13 @@ import { ThemeProvider } from "@mui/material/styles"; import { t } from "i18next"; import type { AppProps } from "next/app"; import { useRouter } from "next/router"; -import { createContext, useContext, useEffect, useRef, useState } from "react"; +import React, { + createContext, + useContext, + useEffect, + useRef, + useState, +} from "react"; import LoadingBar, { type LoadingBarRef } from "react-top-loading-bar"; import "../../public/css/global.css"; @@ -52,7 +58,7 @@ export const AppContext = createContext(undefined); /** Utility hook to reduce amount of boilerplate in account related pages. */ export const useAppContext = () => ensure(useContext(AppContext)); -export default function App({ Component, pageProps }: AppProps) { +const App: React.FC = ({ Component, pageProps }) => { const appName: AppName = "auth"; const router = useRouter(); @@ -75,7 +81,7 @@ export default function App({ Component, pageProps }: AppProps) { ); useEffect(() => { - setupI18n().finally(() => setIsI18nReady(true)); + void setupI18n().finally(() => setIsI18nReady(true)); const userId = (getData(LS_KEYS.USER) as User)?.id; logStartupBanner(appName, userId); logUnhandledErrorsAndRejections(true); @@ -198,4 +204,6 @@ export default function App({ Component, pageProps }: AppProps) { ); -} +}; + +export default App; diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index a9cfbdb510..2a4d23435f 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -152,7 +152,7 @@ export default function App({ Component, pageProps }: AppProps) { ); useEffect(() => { - setupI18n().finally(() => setIsI18nReady(true)); + void setupI18n().finally(() => setIsI18nReady(true)); const userId = (getData(LS_KEYS.USER) as User)?.id; logStartupBanner(appName, userId); logUnhandledErrorsAndRejections(true); diff --git a/web/packages/build-config/eslintrc-base.js b/web/packages/build-config/eslintrc-base.js index 3e65638c1b..95d9976521 100644 --- a/web/packages/build-config/eslintrc-base.js +++ b/web/packages/build-config/eslintrc-base.js @@ -25,5 +25,20 @@ module.exports = { ignoreArrowShorthand: true, }, ], + /* + Allow async functions to be passed as JSX attributes expected to be + functions that return void (typically onFoo event handlers). + + This should be safe since we have registered global unhandled Promise + handlers. + */ + "@typescript-eslint/no-misused-promises": [ + "error", + { + checksVoidReturn: { + attributes: false, + }, + }, + ], }, }; diff --git a/web/packages/shared/components/DialogBoxV2/types.ts b/web/packages/shared/components/DialogBoxV2/types.ts index f2ac31f0b1..d7db388600 100644 --- a/web/packages/shared/components/DialogBoxV2/types.ts +++ b/web/packages/shared/components/DialogBoxV2/types.ts @@ -13,7 +13,7 @@ export interface DialogBoxAttributesV2 { title?: React.ReactNode; staticBackdrop?: boolean; nonClosable?: boolean; - content?: any; + content?: React.ReactNode; close?: { text?: string; variant?: ButtonProps["color"];