diff --git a/src/app/[locale]/complete-registration/page.tsx b/src/app/[locale]/complete-registration/page.tsx
index e606231e..775b2d58 100644
--- a/src/app/[locale]/complete-registration/page.tsx
+++ b/src/app/[locale]/complete-registration/page.tsx
@@ -1,14 +1,14 @@
import { type ReactElement } from 'react';
import CompleteRegistration from './CompleteRegistration';
import { ReduxGateWrapper } from '../../components/ReduxGateWrapper';
+import { ProtectedPageWrapper } from '../../components/ProtectedPageWrapper';
export default function CompleteRegistrationPage(): ReactElement {
return (
- {/* TODO: Revisit protected page wrappers. This page changes the status of the user which causes flickers of mismatched authentication */}
- {/* */}
-
- {/* */}
+
+
+
);
}
diff --git a/src/app/[locale]/verify-email/page.tsx b/src/app/[locale]/verify-email/page.tsx
index e2a931a3..a26f0ef7 100644
--- a/src/app/[locale]/verify-email/page.tsx
+++ b/src/app/[locale]/verify-email/page.tsx
@@ -1,14 +1,14 @@
import { type ReactElement } from 'react';
import PostRegistration from './PostRegistration';
import { ReduxGateWrapper } from '../../components/ReduxGateWrapper';
+import { ProtectedPageWrapper } from '../../components/ProtectedPageWrapper';
export default function VerifyEmailPage(): ReactElement {
return (
- {/* TODO: Revisit protected page wrappers. This page changes the status of the user which causes flickers of mismatched authentication */}
- {/* */}
-
- {/* */}
+
+
+
);
}
diff --git a/src/app/components/ProtectedPageWrapper.tsx b/src/app/components/ProtectedPageWrapper.tsx
index 9b8b2c35..9ed11a31 100644
--- a/src/app/components/ProtectedPageWrapper.tsx
+++ b/src/app/components/ProtectedPageWrapper.tsx
@@ -5,21 +5,22 @@
// targetStatus can be used to scope groups further (e.g. (authenticated), (unverified)).
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
-import { useRouter } from 'next/navigation';
+import { useRouter, useSearchParams } from 'next/navigation';
import { useAppDispatch } from '../hooks';
import { refreshApp, refreshAppSuccess } from '../store/profile-reducer';
import { selectUserProfileStatus } from '../store/selectors';
import { app } from '../../firebase';
import { SIGN_IN_TARGET } from '../constants/Navigation';
+import { useAuthSession } from './AuthSessionProvider';
interface ProtectedPageWrapperProps {
children: React.ReactNode;
/**
- * The user profile status required to access this page.
+ * The user profile status or statuses required to access this page.
* Mirrors the `targetStatus` prop from the legacy ProtectedRoute component.
* Defaults to 'registered'.
*/
- targetStatus?: string;
+ targetStatus?: string | string[];
redirect?: string;
}
@@ -29,9 +30,15 @@ export function ProtectedPageWrapper({
redirect = SIGN_IN_TARGET,
}: ProtectedPageWrapperProps): React.ReactElement | null {
const userProfileStatus = useSelector(selectUserProfileStatus);
+ const { isAuthResolved } = useAuthSession();
const router = useRouter();
+ const searchParams = useSearchParams();
const dispatch = useAppDispatch();
+ const allowedStatuses = Array.isArray(targetStatus)
+ ? targetStatus
+ : [targetStatus];
+
useEffect(() => {
app.auth();
@@ -51,13 +58,29 @@ export function ProtectedPageWrapper({
};
}, [dispatch]);
+ const isAuthorized = allowedStatuses.includes(userProfileStatus);
+
useEffect(() => {
- if (userProfileStatus !== targetStatus) {
- router.replace(redirect);
+ // Only redirect once auth state has resolved and profile is hydrated
+ if (!isAuthResolved) return;
+ if (userProfileStatus === 'idle' || userProfileStatus === 'loading') return;
+
+ if (!isAuthorized) {
+ const query = searchParams?.toString();
+ const target =
+ query && !redirect.includes('?') ? `${redirect}?${query}` : redirect;
+ router.replace(target);
}
- }, [userProfileStatus, targetStatus, redirect, router]);
+ }, [
+ isAuthResolved,
+ userProfileStatus,
+ isAuthorized,
+ redirect,
+ router,
+ searchParams,
+ ]);
- if (userProfileStatus !== targetStatus) {
+ if (!isAuthResolved || !isAuthorized) {
return null;
}