Start building a login form, behind a feature flag
Thinking about longevity, I think I wanna cut Auth0 loose, and just go back to using our own auth. I had figured at the time that I didn't want to integrate with OpenNeo ID's whole mess, and I didn't want to write a whole new auth system, so Auth0 seemed to make things easier. But now, it's just kinda a lot to be carrying along an external service as a dependency for login, especially when we've got all the stuff in the database right here. I wanna remove architecture pieces! Get it outta here! And I'll finally build account creation from the 2020 site while I'm at it, which seemed like it was gonna be a bit of a pain with Auth0 and syncing anyway. (I think at the time I was a bit more optimistic about a full transfer from one system to another, but that's much further off than I realized, and this path will be much better for keeping things in sync.)
This commit is contained in:
parent
41efe05be4
commit
ce503ea730
3 changed files with 259 additions and 4 deletions
|
@ -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() {
|
|||
<NavButton as={Link} to="/modeling">
|
||||
Modeling
|
||||
</NavButton>
|
||||
<NavButton onClick={() => loginWithRedirect()}>Log in</NavButton>
|
||||
<LoginButton />
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<NavButton onClick={onClick}>Log in</NavButton>
|
||||
{authMode === "db" && (
|
||||
<React.Suspense fallback="">
|
||||
<LoginModal isOpen={isOpen} onClose={onClose} />
|
||||
</React.Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 <NavLinkItem /> children as a dropdown menu or as a list
|
||||
* of buttons, depending on the screen size.
|
||||
|
|
135
src/app/components/LoginModal.js
Normal file
135
src/app/components/LoginModal.js
Normal file
|
@ -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 (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Welcome back to Dress to Impress! ✨</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>Log in</Tab>
|
||||
<Tab>Create account</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<ModalBody>
|
||||
<LoginForm />
|
||||
</ModalBody>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ModalBody>
|
||||
<CreateAccountForm />
|
||||
</ModalBody>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginForm() {
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
alert("TODO: Log in!");
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<FormControl>
|
||||
<FormLabel>DTI Username</FormLabel>
|
||||
<Input type="text" />
|
||||
<FormHelperText>
|
||||
This is separate from your Neopets.com account.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Box height="4" />
|
||||
<FormControl>
|
||||
<FormLabel>DTI Password</FormLabel>
|
||||
<Input type="password" />
|
||||
<FormHelperText>
|
||||
Careful, never enter your Neopets password on another site!
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Box marginTop="6" display="flex" alignItems="center">
|
||||
<Button size="sm" onClick={() => alert("TODO: Forgot password")}>
|
||||
Forgot password?
|
||||
</Button>
|
||||
<Box flex="1 0 auto" width="4" />
|
||||
<Button type="submit" colorScheme="green">
|
||||
Log in
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateAccountForm() {
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
alert("TODO: Create account!");
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<FormControl>
|
||||
<FormLabel>DTI Username</FormLabel>
|
||||
<Input type="text" />
|
||||
<FormHelperText>
|
||||
This will be separate from your Neopets.com account.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Box height="4" />
|
||||
<FormControl>
|
||||
<FormLabel>DTI Password</FormLabel>
|
||||
<Input type="password" />
|
||||
<FormHelperText>
|
||||
Careful, never use your Neopets password for another site!
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Box height="4" />
|
||||
<FormControl>
|
||||
<FormLabel>Confirm DTI Password</FormLabel>
|
||||
<Input type="password" />
|
||||
<FormHelperText>One more time, to make sure!</FormHelperText>
|
||||
</FormControl>
|
||||
<Box height="4" />
|
||||
<FormControl>
|
||||
<FormLabel>Email address</FormLabel>
|
||||
<Input type="password" />
|
||||
<FormHelperText>
|
||||
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.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Box height="6" />
|
||||
<Box display="flex" justifyContent="flex-end">
|
||||
<Button type="submit" colorScheme="green">
|
||||
Create account
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -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 ` +
|
||||
`<LoginModal /> instead.`
|
||||
);
|
||||
alert(
|
||||
`Error: Cannot call startLogin in db login mode. Open a ` +
|
||||
`<LoginModal /> 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;
|
||||
|
|
Loading…
Reference in a new issue