diff --git a/src/app/GlobalHeader.js b/src/app/GlobalHeader.js index 4b6e66d..a67a81c 100644 --- a/src/app/GlobalHeader.js +++ b/src/app/GlobalHeader.js @@ -8,14 +8,17 @@ import { MenuButton, MenuList, MenuItem, + useDisclosure, } from "@chakra-ui/react"; import { HamburgerIcon } from "@chakra-ui/icons"; import { Link, useLocation } from "react-router-dom"; -import { useAuth0 } from "@auth0/auth0-react"; import { ChevronLeftIcon } from "@chakra-ui/icons"; import Image from "next/image"; -import useCurrentUser from "./components/useCurrentUser"; +import useCurrentUser, { + useAuthModeFeatureFlag, + useLoginActions, +} from "./components/useCurrentUser"; import HomeLinkIcon from "./images/home-link-icon.png"; function GlobalHeader() { @@ -109,8 +112,8 @@ function HomeLink(props) { } function UserNavBarSection() { - const { loginWithRedirect, logout } = useAuth0(); const { isLoading, isLoggedIn, id, username } = useCurrentUser(); + const { logout } = useLoginActions(); if (isLoading) { return null; @@ -150,12 +153,43 @@ function UserNavBarSection() { Modeling - loginWithRedirect()}>Log in + ); } } +function LoginButton() { + const authMode = useAuthModeFeatureFlag(); + const { startLogin } = useLoginActions(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const onClick = () => { + if (authMode === "auth0") { + startLogin(); + } else if (authMode === "db") { + onOpen(); + } else { + throw new Error(`unexpected auth mode: ${JSON.stringify(authMode)}`); + } + }; + + return ( + <> + Log in + {authMode === "db" && ( + + + + )} + + ); +} + +// I don't wanna load all these Chakra components as part of the bundle for +// every single page. Split it out! +const LoginModal = React.lazy(() => import("./components/LoginModal")); + /** * Renders the given children as a dropdown menu or as a list * of buttons, depending on the screen size. diff --git a/src/app/components/LoginModal.js b/src/app/components/LoginModal.js new file mode 100644 index 0000000..85d9c8c --- /dev/null +++ b/src/app/components/LoginModal.js @@ -0,0 +1,135 @@ +import { + Box, + Button, + FormControl, + FormHelperText, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, +} from "@chakra-ui/react"; +import React from "react"; + +export default function LoginModal({ isOpen, onClose }) { + return ( + + + + Welcome back to Dress to Impress! ✨ + + + + Log in + Create account + + + + + + + + + + + + + + + + + ); +} + +function LoginForm() { + const onSubmit = (e) => { + e.preventDefault(); + alert("TODO: Log in!"); + }; + + return ( +
+ + DTI Username + + + This is separate from your Neopets.com account. + + + + + DTI Password + + + Careful, never enter your Neopets password on another site! + + + + + + + + + ); +} + +function CreateAccountForm() { + const onSubmit = (e) => { + e.preventDefault(); + alert("TODO: Create account!"); + }; + + return ( +
+ + DTI Username + + + This will be separate from your Neopets.com account. + + + + + DTI Password + + + Careful, never use your Neopets password for another site! + + + + + Confirm DTI Password + + One more time, to make sure! + + + + Email address + + + We'll use this in the future if you need to reset your password, or + for us to contact you about your account. We won't sell this address, + and we won't send marketing-y emails. + + + + + + + + ); +} diff --git a/src/app/components/useCurrentUser.js b/src/app/components/useCurrentUser.js index 9943c18..2ef3b07 100644 --- a/src/app/components/useCurrentUser.js +++ b/src/app/components/useCurrentUser.js @@ -1,4 +1,6 @@ import { useAuth0 } from "@auth0/auth0-react"; +import { useEffect } from "react"; +import { useLocalStorage } from "../util"; function useCurrentUser() { const { isLoading, isAuthenticated, user } = useAuth0(); @@ -61,4 +63,88 @@ function getUserInfo(user) { }; } +/** + * useLoginActions returns a `startLogin` function to start login with Auth0, + * and a `logout` function to logout from whatever auth mode is in use. + * + * Note that `startLogin` is only supported with the Auth0 auto mode. In db + * mode, you should open a `LoginModal` instead! + */ +export function useLoginActions() { + const { + loginWithRedirect: auth0StartLogin, + logout: auth0Logout, + } = useAuth0(); + const authMode = useAuthModeFeatureFlag(); + + if (authMode === "auth0") { + return { startLogin: auth0StartLogin, logout: auth0Logout }; + } else if (authMode === "db") { + return { + startLogin: () => { + console.error( + `Error: Cannot call startLogin in db login mode. Open a ` + + ` instead.` + ); + alert( + `Error: Cannot call startLogin in db login mode. Open a ` + + ` instead.` + ); + }, + logout: () => { + alert(`TODO: logout`); + }, + }; + } else { + console.error(`unexpected auth mode: ${JSON.stringify(authMode)}`); + return { startLogin: () => {}, logout: () => {} }; + } +} + +/** + * useAuthModeFeatureFlag returns "auth0" by default, but "db" if you're trying + * the new db-backed login mode. + * + * To set this manually, run `window.setAuthModeFeatureFlag("db")` in your + * browser console. + */ +export function useAuthModeFeatureFlag() { + // We'll probably add a like, experimental gradual rollout thing here too. + // But for now we just check your device's local storage! (This is why we + // default to `null` instead of "auth0", I want to be unambiguous that this + // is the *absence* of a localStorage value, and not risk accidentally + // setting this override value to auth0 on everyone's devices 😅) + const [savedValue] = useLocalStorage("DTIAuthModeFeatureFlag", null); + + useEffect(() => { + window.setAuthModeFeatureFlag = setAuthModeFeatureFlag; + }); + + if (!["auth0", "db", null].includes(savedValue)) { + console.warn( + `Unexpected DTIAuthModeFeatureFlag value: %o. Treating as null.`, + savedValue + ); + return null; + } + + return savedValue || "auth0"; +} + +/** + * setAuthModeFeatureFlag is mounted on the window, so you can call it from the + * browser console to set this override manually. + */ +function setAuthModeFeatureFlag(newValue) { + if (!["auth0", "db", null].includes(newValue)) { + throw new Error(`Auth mode must be "auth0", "db", or null.`); + } + + localStorage.setItem("DTIAuthModeFeatureFlag", JSON.stringify(newValue)); + + // The useLocalStorage hook isn't *quite* good enough to catch this change. + // Let's just reload the page lmao. + window.location.reload(); +} + export default useCurrentUser;