Skip to content

1795 auth redirect page #1858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 14, 2025
Merged
2 changes: 1 addition & 1 deletion components/EditProfilePage/EditProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions components/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -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<SignInWithEmailAndPasswordData>()

const signIn = useSignInWithEmailAndPassword()

const success = () => {
const safeRedirect = redirect.startsWith("/") ? redirect : "/"
router.replace(safeRedirect)
}
const onSubmit = handleSubmit(credentials => {
signIn.execute(credentials).then(success)
})

return (
<Container className="py-5">
<Row className="justify-content-center">
<Col xs={12} sm={10} md={8} lg={6}>
<Card className="p-4 shadow-lg">
<h2 className="text-center">{t("signInToAccess")}</h2>

<Form onSubmit={onSubmit} noValidate>
{signIn.error && (
<Alert variant="danger">{signIn.error.message}</Alert>
)}

<Stack gap={3}>
<Input
label={t("email")}
type="email"
{...register("email", {
required: t("emailIsRequired") ?? "Email is required"
})}
error={errors.email?.message}
/>

<PasswordInput
label={t("password")}
{...register("password", {
required: t("passwordRequired") ?? "Password is required"
})}
error={errors.password?.message}
/>

<LoadingButton
type="submit"
className="w-100"
loading={signIn.loading}
>
{t("signIn")}
</LoadingButton>

<Divider className="px-4">{t("or")}</Divider>

<SocialSignOnButtons
onComplete={() => router.replace(redirect)}
/>
</Stack>
</Form>
</Card>
</Col>
</Row>
</Container>
)
}
50 changes: 0 additions & 50 deletions components/Login/login.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion components/Newsfeed/Newsfeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
21 changes: 21 additions & 0 deletions components/auth/Provider.tsx
Original file line number Diff line number Diff line change
@@ -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<React.PropsWithChildren<unknown>> = ({
children
}) => {
const dispatch = useDispatch()

useEffect(() => {
const auth = getAuth()
const unsubscribe = onAuthStateChanged(auth, (user: User | null) => {
dispatch(authChanged({ user }))
})

return () => unsubscribe()
}, [])

return <>{children}</>
}
1 change: 1 addition & 0 deletions components/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
5 changes: 4 additions & 1 deletion components/auth/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<AuthFlowStep>) {
state.authFlowStep = action.payload
Expand Down
20 changes: 15 additions & 5 deletions components/auth/service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -76,13 +77,22 @@ export function requireAuth(
Component: React.FC<React.PropsWithChildren<{ user: User }>>
) {
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 (
<Row>
<Spinner animation="border" className="mx-auto" />
</Row>
)
}

return user ? <Component user={user} /> : null
}
Expand All @@ -92,6 +102,6 @@ export function requireAuth(
* Redirects user after logging out.
*/
export async function signOutAndRedirectToHome() {
await auth.signOut()
Router.push("/")
await auth.signOut()
}
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// 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.
5 changes: 2 additions & 3 deletions pages/edit-profile/[[...docName]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <EditProfile tabTitle={tabTitle as TabTitles} />
}
})
})

export const getStaticPaths: GetStaticPaths = async ctx => {
Expand Down
15 changes: 15 additions & 0 deletions pages/login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createPage } from "../components/page"
import Login from "components/Login/Login"

export default createPage({
titleI18nKey: "navigation.login",
Page: () => <Login />
})
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
export async function getStaticProps({ locale }: any) {
return {
props: {
...(await serverSideTranslations(locale, ["auth", "common"]))
}
}
}
5 changes: 3 additions & 2 deletions pages/newsfeed.tsx
Original file line number Diff line number Diff line change
@@ -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 <Newsfeed />
}
})
})

// this must only be on pages in the pages folder
Expand Down
3 changes: 3 additions & 0 deletions public/locales/en/auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
3 changes: 2 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down