Skip to content

Commit

Permalink
[web] Make the accounts pages stateless (#2100)
Browse files Browse the repository at this point in the history
This allows user to copy paste the URLs if their passkeys are in a
different browser than the one which got opened when we asked the system
to open the browser (from the desktop / mobile apps).
  • Loading branch information
mnvr authored Jun 11, 2024
2 parents d6885d6 + b5e3bf8 commit 2e1c5d7
Show file tree
Hide file tree
Showing 23 changed files with 120 additions and 270 deletions.
18 changes: 0 additions & 18 deletions web/apps/accounts/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CustomHead } from "@/next/components/Head";
import { setClientPackageForAuthenticatedRequests } from "@/next/http";
import { setupI18n } from "@/next/i18n";
import { logUnhandledErrorsAndRejections } from "@/next/log-web";
import { appTitle, type AppName, type BaseAppContextT } from "@/next/types/app";
Expand All @@ -12,7 +11,6 @@ import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import { AppNavbar } from "@ente/shared/components/Navbar/app";
import { useLocalState } from "@ente/shared/hooks/useLocalState";
import HTTPService from "@ente/shared/network/HTTPService";
import { LS_KEYS } from "@ente/shared/storage/localStorage";
import { getTheme } from "@ente/shared/themes";
import { THEME_COLOR } from "@ente/shared/themes/constants";
Expand Down Expand Up @@ -64,22 +62,6 @@ export default function App({ Component, pageProps }: AppProps) {
return () => logUnhandledErrorsAndRejections(false);
}, []);

const setupPackageName = () => {
const clientPackage = localStorage.getItem("clientPackage");
if (!clientPackage) return;
setClientPackageForAuthenticatedRequests(clientPackage);
HTTPService.setHeaders({
"X-Client-Package": clientPackage,
});
};

useEffect(() => {
router.events.on("routeChangeComplete", setupPackageName);
return () => {
router.events.off("routeChangeComplete", setupPackageName);
};
}, [router.events]);

const closeDialogBoxV2 = () => setDialogBoxV2View(false);

const theme = getTheme(themeColor, "photos");
Expand Down
15 changes: 0 additions & 15 deletions web/apps/accounts/src/pages/account-handoff.tsx

This file was deleted.

6 changes: 0 additions & 6 deletions web/apps/accounts/src/pages/credentials.tsx

This file was deleted.

6 changes: 0 additions & 6 deletions web/apps/accounts/src/pages/generate.tsx

This file was deleted.

6 changes: 2 additions & 4 deletions web/apps/accounts/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { useRouter } from "next/router";
import React, { useEffect } from "react";

const Page: React.FC = () => {
const router = useRouter();

useEffect(() => {
router.push("/login");
// There are no user navigable pages currently on accounts.ente.io.
window.location.href = "https://web.ente.io";
}, []);

return <></>;
Expand Down
6 changes: 0 additions & 6 deletions web/apps/accounts/src/pages/login.tsx

This file was deleted.

10 changes: 0 additions & 10 deletions web/apps/accounts/src/pages/passkeys/finish.tsx

This file was deleted.

12 changes: 0 additions & 12 deletions web/apps/accounts/src/pages/passkeys/flow.tsx

This file was deleted.

51 changes: 0 additions & 51 deletions web/apps/accounts/src/pages/passkeys/handoff.tsx

This file was deleted.

78 changes: 54 additions & 24 deletions web/apps/accounts/src/pages/passkeys/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import log from "@/next/log";
import { ensure } from "@/utils/ensure";
import { CenteredFlex } from "@ente/shared/components/Container";
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
import EnteButton from "@ente/shared/components/EnteButton";
Expand All @@ -10,7 +11,6 @@ import MenuItemDivider from "@ente/shared/components/Menu/MenuItemDivider";
import { MenuItemGroup } from "@ente/shared/components/Menu/MenuItemGroup";
import SingleInputForm from "@ente/shared/components/SingleInputForm";
import Titlebar from "@ente/shared/components/Titlebar";
import { getToken } from "@ente/shared/storage/localStorage/helpers";
import { formatDateTimeFull } from "@ente/shared/time/format";
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
Expand All @@ -19,7 +19,6 @@ import EditIcon from "@mui/icons-material/Edit";
import KeyIcon from "@mui/icons-material/Key";
import { Box, Button, Stack, Typography, useMediaQuery } from "@mui/material";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useAppContext } from "pages/_app";
import React, { useEffect, useState } from "react";
import {
Expand All @@ -33,36 +32,49 @@ import {
const Page: React.FC = () => {
const { showNavBar, setDialogBoxAttributesV2 } = useAppContext();

const [token, setToken] = useState<string | undefined>();
const [passkeys, setPasskeys] = useState<Passkey[]>([]);
const [showPasskeyDrawer, setShowPasskeyDrawer] = useState(false);
const [selectedPasskey, setSelectedPasskey] = useState<
Passkey | undefined
>();

const router = useRouter();
useEffect(() => {
showNavBar(true);

const urlParams = new URLSearchParams(window.location.search);

const token = urlParams.get("token");
if (token) {
setToken(token);
} else {
log.error("Missing accounts token");
showPasskeyFetchFailedErrorDialog();
}
}, []);

useEffect(() => {
if (token) {
void refreshPasskeys();
}
}, [token]);

const refreshPasskeys = async () => {
try {
setPasskeys(await getPasskeys());
setPasskeys(await getPasskeys(ensure(token)));
} catch (e) {
log.error("Failed to fetch passkeys", e);
setDialogBoxAttributesV2({
title: t("ERROR"),
content: t("passkey_fetch_failed"),
close: {},
});
showPasskeyFetchFailedErrorDialog();
}
};

useEffect(() => {
if (!getToken()) {
router.push("/login");
return;
}

showNavBar(true);
void refreshPasskeys();
}, []);
const showPasskeyFetchFailedErrorDialog = () => {
setDialogBoxAttributesV2({
title: t("ERROR"),
content: t("passkey_fetch_failed"),
close: {},
});
};

const handleSelectPasskey = (passkey: Passkey) => {
setSelectedPasskey(passkey);
Expand Down Expand Up @@ -90,7 +102,7 @@ const Page: React.FC = () => {
resetForm: () => void,
) => {
try {
await registerPasskey(inputValue);
await registerPasskey(ensure(token), inputValue);
} catch (e) {
log.error("Failed to register a new passkey", e);
// If the user cancels the operation, then an error with name
Expand Down Expand Up @@ -133,10 +145,12 @@ const Page: React.FC = () => {
</Box>
</Box>
</CenteredFlex>

<ManagePasskeyDrawer
open={showPasskeyDrawer}
onClose={handleDrawerClose}
passkey={selectedPasskey}
token={token}
onUpdateOrDeletePasskey={handleUpdateOrDeletePasskey}
/>
</>
Expand Down Expand Up @@ -205,6 +219,13 @@ interface ManagePasskeyDrawerProps {
open: boolean;
/** Callback to invoke when the drawer wants to be closed. */
onClose: () => void;
/**
* The token to use for authenticating with the backend when making requests
* for editing or deleting passkeys.
*
* It is guaranteed that this will be defined when `open` is true.
*/
token: string | undefined;
/**
* The {@link Passkey} whose details should be shown in the drawer.
*
Expand All @@ -223,6 +244,7 @@ interface ManagePasskeyDrawerProps {
const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
open,
onClose,
token,
passkey,
onUpdateOrDeletePasskey,
}) => {
Expand All @@ -232,7 +254,7 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
return (
<>
<EnteDrawer anchor="right" {...{ open, onClose }}>
{passkey && (
{token && passkey && (
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
Expand Down Expand Up @@ -270,10 +292,11 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
)}
</EnteDrawer>

{passkey && (
{token && passkey && (
<RenamePasskeyDialog
open={showRenameDialog}
onClose={() => setShowRenameDialog(false)}
token={token}
passkey={passkey}
onRenamePasskey={() => {
setShowRenameDialog(false);
Expand All @@ -282,10 +305,11 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
/>
)}

{passkey && (
{token && passkey && (
<DeletePasskeyDialog
open={showDeleteDialog}
onClose={() => setShowDeleteDialog(false)}
token={token}
passkey={passkey}
onDeletePasskey={() => {
setShowDeleteDialog(false);
Expand All @@ -302,6 +326,8 @@ interface RenamePasskeyDialogProps {
open: boolean;
/** Callback to invoke when the dialog wants to be closed. */
onClose: () => void;
/** Auth token for API requests. */
token: string;
/** The {@link Passkey} to rename. */
passkey: Passkey;
/** Callback to invoke when the passkey is renamed. */
Expand All @@ -311,14 +337,15 @@ interface RenamePasskeyDialogProps {
const RenamePasskeyDialog: React.FC<RenamePasskeyDialogProps> = ({
open,
onClose,
token,
passkey,
onRenamePasskey,
}) => {
const fullScreen = useMediaQuery("(max-width: 428px)");

const handleSubmit = async (inputValue: string) => {
try {
await renamePasskey(passkey.id, inputValue);
await renamePasskey(token, passkey.id, inputValue);
onRenamePasskey();
} catch (e) {
log.error("Failed to rename passkey", e);
Expand Down Expand Up @@ -349,6 +376,8 @@ interface DeletePasskeyDialogProps {
open: boolean;
/** Callback to invoke when the dialog wants to be closed. */
onClose: () => void;
/** Auth token for API requests. */
token: string;
/** The {@link Passkey} to delete. */
passkey: Passkey;
/** Callback to invoke when the passkey is deleted. */
Expand All @@ -358,6 +387,7 @@ interface DeletePasskeyDialogProps {
const DeletePasskeyDialog: React.FC<DeletePasskeyDialogProps> = ({
open,
onClose,
token,
passkey,
onDeletePasskey,
}) => {
Expand All @@ -367,7 +397,7 @@ const DeletePasskeyDialog: React.FC<DeletePasskeyDialogProps> = ({
const handleConfirm = async () => {
setIsDeleting(true);
try {
await deletePasskey(passkey.id);
await deletePasskey(token, passkey.id);
onDeletePasskey();
} catch (e) {
log.error("Failed to delete passkey", e);
Expand Down
13 changes: 0 additions & 13 deletions web/apps/accounts/src/pages/passkeys/recover.tsx

This file was deleted.

Loading

0 comments on commit 2e1c5d7

Please sign in to comment.