- ,
+
);
diff --git a/portal-frontend/src/pages/ApplicationStatusPage.js b/portal-frontend/src/pages/ApplicationStatusPage.js
index 36059a7..3bfd932 100644
--- a/portal-frontend/src/pages/ApplicationStatusPage.js
+++ b/portal-frontend/src/pages/ApplicationStatusPage.js
@@ -12,6 +12,7 @@ import { QRCodeSVG } from "qrcode.react";
import Button from "../components/Button";
import axios from "axios";
import { useNavigate } from "react-router-dom";
+import { createGetAuthToken } from "../utils/authUtils";
// defaulting this, but only shd happen if page is navigated to directly (no query args)
const DEFAULT_FORM_KEY = "2025-cfg-application";
@@ -87,7 +88,11 @@ const ApplicationStatusPage = () => {
const checkIfSubmitted = async () => {
try {
setLoading(true);
- const token = await getAccessTokenSilently();
+ const getAuthToken = createGetAuthToken(getAccessTokenSilently, setError);
+ const token = await getAuthToken();
+ if (!token) {
+ return;
+ }
const response = await axios.get(
`${process.env.REACT_APP_BACKEND_URL}/application`,
{
diff --git a/portal-frontend/src/pages/FormPage.js b/portal-frontend/src/pages/FormPage.js
index 0dce8a1..d40a264 100644
--- a/portal-frontend/src/pages/FormPage.js
+++ b/portal-frontend/src/pages/FormPage.js
@@ -9,6 +9,7 @@ import { useNavigate } from "react-router-dom";
import { FullPageLoadingSpinner } from "../components/FullPageLoadingSpinner";
import Modal, { ModalHeader } from "../components/Modal";
import { getFormByKey } from "../forms/forms";
+import { createGetAuthToken } from "../utils/authUtils";
const FormPage = ({ formKey }) => {
const { getAccessTokenSilently } = useAuth0();
@@ -24,21 +25,6 @@ const FormPage = ({ formKey }) => {
const formDefinition = getFormByKey(formKey);
- const getAuthToken = async () => {
- try {
- return await getAccessTokenSilently();
- } catch (tokenError) {
- if (tokenError.message.includes("Missing Refresh Token")) {
- openModal();
- setError(
- "Session expired. Please try logging out and logging back in."
- );
- return null;
- }
- throw tokenError;
- }
- };
-
const prepareFormData = (data) => {
const formData = new FormData();
formData.append("form_key", formKey);
@@ -76,6 +62,7 @@ const FormPage = ({ formKey }) => {
setError(null);
try {
+ const getAuthToken = createGetAuthToken(getAccessTokenSilently, setError);
const token = await getAuthToken();
if (!token) {
return;
@@ -132,7 +119,14 @@ const FormPage = ({ formKey }) => {
try {
setLoading(true);
- const token = await getAccessTokenSilently();
+ const getAuthToken = createGetAuthToken(
+ getAccessTokenSilently,
+ setError
+ );
+ const token = await getAuthToken();
+ if (!token) {
+ return;
+ }
const isOpen = await checkFormStatus(token);
setIsFormOpen(isOpen);
diff --git a/portal-frontend/src/pages/FormsLandingPage.js b/portal-frontend/src/pages/FormsLandingPage.js
index 3addbe7..ea0f607 100644
--- a/portal-frontend/src/pages/FormsLandingPage.js
+++ b/portal-frontend/src/pages/FormsLandingPage.js
@@ -7,6 +7,7 @@ import { WhiteBackground } from "../components/WhiteBackground";
import { FullPageLoadingSpinner } from "../components/FullPageLoadingSpinner";
import Button from "../components/Button";
import { getAllForms } from "../forms/forms";
+import { createGetAuthToken } from "../utils/authUtils";
import "./FormsLandingPage.css";
const FormsLandingPage = () => {
@@ -14,71 +15,86 @@ const FormsLandingPage = () => {
const navigate = useNavigate();
const [formsStatus, setFormsStatus] = useState({});
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
const allForms = useMemo(() => getAllForms(), []);
useEffect(() => {
const fetchAllFormsStatus = async () => {
- setLoading(true);
- const token = await getAccessTokenSilently();
- const statusPromises = allForms.map(async (form) => {
- try {
- const statusRes = await axios.get(
- `${process.env.REACT_APP_BACKEND_URL}/application/form-status`,
- {
- headers: { Authorization: `Bearer ${token}` },
- params: { form_key: form.formKey },
- }
- );
-
- const isOpen = Boolean(statusRes.data?.is_open);
+ try {
+ setLoading(true);
+ setError(null);
+
+ const getAuthToken = createGetAuthToken(getAccessTokenSilently, setError);
+ const token = await getAuthToken();
+ if (!token) {
+ setLoading(false);
+ return;
+ }
- let hasSubmitted = false;
+ const statusPromises = allForms.map(async (form) => {
try {
- await axios.get(
- `${process.env.REACT_APP_BACKEND_URL}/application`,
+ const statusRes = await axios.get(
+ `${process.env.REACT_APP_BACKEND_URL}/application/form-status`,
{
headers: { Authorization: `Bearer ${token}` },
params: { form_key: form.formKey },
}
);
- hasSubmitted = true;
- } catch (error) {
- if (error.response?.status !== 404) {
- console.error(
- `Error checking submission for ${form.formKey}:`,
- error
+
+ const isOpen = Boolean(statusRes.data?.is_open);
+
+ let hasSubmitted = false;
+ try {
+ await axios.get(
+ `${process.env.REACT_APP_BACKEND_URL}/application`,
+ {
+ headers: { Authorization: `Bearer ${token}` },
+ params: { form_key: form.formKey },
+ }
);
+ hasSubmitted = true;
+ } catch (error) {
+ if (error.response?.status !== 404) {
+ console.error(
+ `Error checking submission for ${form.formKey}:`,
+ error
+ );
+ }
}
- }
- return {
- formKey: form.formKey,
- isOpen,
- hasSubmitted,
- };
- } catch (error) {
- console.error(`Error fetching status for ${form.formKey}:`, error);
- return {
- formKey: form.formKey,
- isOpen: false,
- hasSubmitted: false,
- error: true,
- };
- }
- });
-
- const results = await Promise.all(statusPromises);
- const statusMap = {};
- results.forEach((result) => {
- statusMap[result.formKey] = result;
- });
- setFormsStatus(statusMap);
- setLoading(false);
+ return {
+ formKey: form.formKey,
+ isOpen,
+ hasSubmitted,
+ };
+ } catch (error) {
+ console.error(`Error fetching status for ${form.formKey}:`, error);
+ return {
+ formKey: form.formKey,
+ isOpen: false,
+ hasSubmitted: false,
+ error: true,
+ };
+ }
+ });
+
+ const results = await Promise.all(statusPromises);
+ const statusMap = {};
+ results.forEach((result) => {
+ statusMap[result.formKey] = result;
+ });
+ setFormsStatus(statusMap);
+ setLoading(false);
+ } catch (err) {
+ console.error("Error in fetchAllFormsStatus:", err);
+ setError("Failed to load.");
+ setLoading(false);
+ }
};
fetchAllFormsStatus();
- }, [getAccessTokenSilently, allForms]);
+ }, [allForms, getAccessTokenSilently]);
const handleFormClick = (formKey) => {
navigate(`/form?formKey=${formKey}`);
@@ -110,6 +126,7 @@ const FormsLandingPage = () => {
{loading &&
}
+ {error &&
{error}
}
Applications & Forms
diff --git a/portal-frontend/src/utils/authUtils.js b/portal-frontend/src/utils/authUtils.js
new file mode 100644
index 0000000..680063d
--- /dev/null
+++ b/portal-frontend/src/utils/authUtils.js
@@ -0,0 +1,58 @@
+import { useAuth0 } from "@auth0/auth0-react";
+
+let loginWithRedirect = null;
+
+export const setLoginWithRedirect = (loginFn) => {
+ loginWithRedirect = loginFn;
+};
+
+export const createGetAuthToken = (getAccessTokenSilently, onError) => {
+ return async () => {
+ try {
+ return await getAccessTokenSilently();
+ } catch (tokenError) {
+ const errorMessage = tokenError.message || "";
+ const errorCode = tokenError.error || "";
+
+ console.error("Auth token error:", {
+ errorMessage,
+ errorCode,
+ tokenError,
+ });
+
+ if (
+ errorMessage.includes("Missing Refresh Token") ||
+ errorCode === "login_required" ||
+ errorCode === "consent_required" ||
+ errorCode === "missing_refresh_token" ||
+ errorCode === "invalid_grant"
+ ) {
+ if (loginWithRedirect) {
+ loginWithRedirect();
+ } else if (onError) {
+ onError("Session expired. Please log in again.");
+ }
+ throw tokenError;
+ }
+
+ if (errorMessage.includes("timeout") || errorCode === "timeout") {
+ if (onError) {
+ onError("Authentication took too long. Please try again.");
+ }
+ throw tokenError;
+ }
+
+ if (onError) {
+ onError(
+ `Authentication error: ${errorMessage || errorCode || "Unknown error"}`
+ );
+ }
+ throw tokenError;
+ }
+ };
+};
+
+export const useInitializeAuth = () => {
+ const { loginWithRedirect } = useAuth0();
+ setLoginWithRedirect(loginWithRedirect);
+};