diff --git a/components/EditProfilePage/EditProfilePage.tsx b/components/EditProfilePage/EditProfilePage.tsx index f885bd6f6..9b39e79f7 100644 --- a/components/EditProfilePage/EditProfilePage.tsx +++ b/components/EditProfilePage/EditProfilePage.tsx @@ -25,7 +25,7 @@ import { } from "./StyledEditProfileComponents" import { TestimoniesTab } from "./TestimoniesTab" import { useFlags } from "components/featureFlags" -import LoginPage from "components/Login/login" +import LoginPage from "components/Login/Login" import { PendingUpgradeBanner } from "components/PendingUpgradeBanner" const tabTitle = ["about-you", "testimonies", "following"] as const diff --git a/components/Login/Login.tsx b/components/Login/Login.tsx new file mode 100644 index 000000000..9387ad03a --- /dev/null +++ b/components/Login/Login.tsx @@ -0,0 +1,86 @@ +import { useForm } from "react-hook-form" +import { useRouter } from "next/router" +import { + useSignInWithEmailAndPassword, + SignInWithEmailAndPasswordData +} from "../auth/hooks" +import { Form, Stack, Alert, Container, Row, Col, Card } from "../bootstrap" +import Input from "../forms/Input" +import PasswordInput from "../forms/PasswordInput" +import { useTranslation } from "next-i18next" +import { LoadingButton } from "../buttons" +import SocialSignOnButtons from "components/auth/SocialSignOnButtons" +import Divider from "../Divider/Divider" + +export default function Login() { + const router = useRouter() + const redirect = (router.query.redirect as string) || "/" + const { t } = useTranslation("auth") + + const { + register, + handleSubmit, + formState: { errors } + } = useForm() + + const signIn = useSignInWithEmailAndPassword() + + const success = () => { + const safeRedirect = redirect.startsWith("/") ? redirect : "/" + router.replace(safeRedirect) + } + const onSubmit = handleSubmit(credentials => { + signIn.execute(credentials).then(success) + }) + + return ( + + + + +

{t("signInToAccess")}

+ +
+ {signIn.error && ( + {signIn.error.message} + )} + + + + + + + + {t("signIn")} + + + {t("or")} + + router.replace(redirect)} + /> + +
+
+ +
+
+ ) +} diff --git a/components/Login/login.tsx b/components/Login/login.tsx deleted file mode 100644 index 820d49590..000000000 --- a/components/Login/login.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useTranslation } from "next-i18next" -import styled from "styled-components" -import { Container } from "../bootstrap" -import { SignInWithButton } from "components/auth" - -export default function LoginPage() { - const { t } = useTranslation("auth") - - const StyledContainer = styled(Container)` - @media (min-width: 768px) { - } - ` - - return ( - -
-
-
-
-
- closed lock with key{" "} -
- {t("almostThere")} -
-
-
- {t("justLogIn")} -
-
-
- -
-
-
-
-
-
- ) -} diff --git a/components/Newsfeed/Newsfeed.tsx b/components/Newsfeed/Newsfeed.tsx index f05dc6ac5..10103177d 100644 --- a/components/Newsfeed/Newsfeed.tsx +++ b/components/Newsfeed/Newsfeed.tsx @@ -14,7 +14,7 @@ import { StyledContainer } from "./StyledNewsfeedComponents" import ProfileSettingsModal from "components/EditProfilePage/ProfileSettingsModal" -import LoginPage from "components/Login/login" +import LoginPage from "components/Login/Login" import { NewsfeedCard } from "components/NewsfeedCard/NewsfeedCard" import { ProfileButtons } from "components/ProfilePage/ProfileButtons" diff --git a/components/auth/Provider.tsx b/components/auth/Provider.tsx new file mode 100644 index 000000000..be783908c --- /dev/null +++ b/components/auth/Provider.tsx @@ -0,0 +1,21 @@ +import { useEffect } from "react" +import { getAuth, onAuthStateChanged, User } from "firebase/auth" +import { useDispatch } from "react-redux" +import { authChanged } from "./redux" + +export const Provider: React.FC> = ({ + children +}) => { + const dispatch = useDispatch() + + useEffect(() => { + const auth = getAuth() + const unsubscribe = onAuthStateChanged(auth, (user: User | null) => { + dispatch(authChanged({ user })) + }) + + return () => unsubscribe() + }, []) + + return <>{children} +} diff --git a/components/auth/index.ts b/components/auth/index.ts index 97c2bd18f..51ab7bfc7 100644 --- a/components/auth/index.ts +++ b/components/auth/index.ts @@ -3,3 +3,4 @@ export * from "./service" export { default as SignInWithButton } from "./SignInWithButton" export { default as SignOut } from "./SignOut" export * from "./types" +export { Provider } from "./Provider" diff --git a/components/auth/redux.ts b/components/auth/redux.ts index fc3c97ce3..cf1b622a4 100644 --- a/components/auth/redux.ts +++ b/components/auth/redux.ts @@ -23,12 +23,14 @@ export interface State { /** True iff user is signed in */ authenticated: boolean authFlowStep: AuthFlowStep + loading: boolean } const initialState: State = { authenticated: false, user: undefined, - authFlowStep: null + authFlowStep: null, + loading: true } export const { @@ -45,6 +47,7 @@ export const { state.user = payload.user state.claims = payload.claims state.authenticated = Boolean(payload.user) + state.loading = false }, authStepChanged(state, action: PayloadAction) { state.authFlowStep = action.payload diff --git a/components/auth/service.tsx b/components/auth/service.tsx index f21730599..8951a235c 100644 --- a/components/auth/service.tsx +++ b/components/auth/service.tsx @@ -8,6 +8,7 @@ import { useAppDispatch } from "../hooks" import { createService } from "../service" import { authChanged, useAuth } from "./redux" import { Claim } from "./types" +import { Row, Spinner } from "../bootstrap" export const { Provider } = createService(() => { const dispatch = useAppDispatch() @@ -76,13 +77,22 @@ export function requireAuth( Component: React.FC> ) { return function ProtectedRoute() { - const { user } = useAuth() + const { user, loading } = useAuth() const router = useRouter() useEffect(() => { - if (user === null) { - router.push({ pathname: "/" }) + if (!loading && !user) { + const currentPath = router.asPath + router.replace(`/login?redirect=${encodeURIComponent(currentPath)}`) } - }, [router, user]) + }, [user, loading, router]) + + if (loading) { + return ( + + + + ) + } return user ? : null } @@ -92,6 +102,6 @@ export function requireAuth( * Redirects user after logging out. */ export async function signOutAndRedirectToHome() { - await auth.signOut() Router.push("/") + await auth.signOut() } diff --git a/next-env.d.ts b/next-env.d.ts index a4a7b3f5c..4f11a03dc 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/pages/edit-profile/[[...docName]].tsx b/pages/edit-profile/[[...docName]].tsx index 0fa1207e3..e94ad3ed5 100644 --- a/pages/edit-profile/[[...docName]].tsx +++ b/pages/edit-profile/[[...docName]].tsx @@ -14,11 +14,10 @@ const Query = z.object({ export default createPage({ titleI18nKey: "navigation.editProfile", - Page: () => { - // Page: requireAuth(({ user }) => { + Page: requireAuth(() => { const tabTitle = Query.parse(useRouter().query).docName?.[0] || "about-you" return - } + }) }) export const getStaticPaths: GetStaticPaths = async ctx => { diff --git a/pages/login.tsx b/pages/login.tsx new file mode 100644 index 000000000..a0f478205 --- /dev/null +++ b/pages/login.tsx @@ -0,0 +1,15 @@ +import { createPage } from "../components/page" +import Login from "components/Login/Login" + +export default createPage({ + titleI18nKey: "navigation.login", + Page: () => +}) +import { serverSideTranslations } from "next-i18next/serverSideTranslations" +export async function getStaticProps({ locale }: any) { + return { + props: { + ...(await serverSideTranslations(locale, ["auth", "common"])) + } + } +} diff --git a/pages/newsfeed.tsx b/pages/newsfeed.tsx index 7c409a4cd..38f6bda7a 100644 --- a/pages/newsfeed.tsx +++ b/pages/newsfeed.tsx @@ -1,11 +1,12 @@ import { createPage } from "../components/page" import Newsfeed from "components/Newsfeed/Newsfeed" +import { requireAuth } from "../components/auth" export default createPage({ titleI18nKey: "navigation.newsfeed", - Page: () => { + Page: requireAuth(() => { return - } + }) }) // this must only be on pages in the pages folder diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json index 3ce7ab303..0e518bda3 100644 --- a/public/locales/en/auth.json +++ b/public/locales/en/auth.json @@ -55,6 +55,9 @@ "verifyEmail": "Verify your email address", "verifyLinkSent": "Please verify your email for your account by clicking the verification link we sent to your email. You will be required to verify your email before submitting testimony.", "setUpProfile": "Set Up Your Profile", + "probablySignedOut": "You were possibly signed out while trying to go to a page that needs to be signed in to function", + "pleaseConsider": "Please consider logging in first:", + "signInToAccess": "Login required to access the content", "almostThere": "Almost there!", "justLogIn": "You’ll be redirected automatically after logging in." } \ No newline at end of file diff --git a/public/locales/en/common.json b/public/locales/en/common.json index eab8fbaf4..789009ed4 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -79,7 +79,8 @@ "supportMaple": "Support MAPLE", "aboutTestimony": "About Testimony", "viewProfile": "View Profile", - "whyUseMaple": "Why Use MAPLE" + "whyUseMaple": "Why Use MAPLE", + "login": "Login" }, "newcomer": "New to MAPLE? For extra support, check", "newsletter": "Click Here to Subscribe to Our Newsletter",