Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions portal-frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ApplicationStatusPage from "./pages/ApplicationStatusPage";
import "./App.css";
import { FullPageLoadingSpinner } from "./components/FullPageLoadingSpinner";
import NotFound from "./components/404page";
import { useInitializeAuth } from "./utils/authUtils";

const DynamicFormPage = () => {
const params = new URLSearchParams(window.location.search);
Expand All @@ -23,6 +24,8 @@ const DynamicFormPage = () => {

function App() {
const { isLoading } = useAuth0();
useInitializeAuth();

if (isLoading) {
return <FullPageLoadingSpinner />;
}
Expand Down
2 changes: 1 addition & 1 deletion portal-frontend/src/components/WhiteBackground.js
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just fixing some lints that were cluttering up my console

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const WhiteBackground = () => {
});

return (
<div class="white-bg-frame">
<div className="white-bg-frame">
<img
className="white-bg-full"
id="white-bg-full"
Expand Down
3 changes: 2 additions & 1 deletion portal-frontend/src/index.js
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also a lint, ignore this

Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ root.render(
authorizationParams={{
redirect_uri: window.location.origin,
audience: process.env.REACT_APP_AUTH0_AUDIENCE,
scope: "openid profile email offline_access",
}}
useRefreshTokens={true}
cacheLocation="localstorage"
>
<App />
</Auth0Provider>
</React.StrictMode>,
</React.StrictMode>
);
7 changes: 6 additions & 1 deletion portal-frontend/src/pages/ApplicationStatusPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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`,
{
Expand Down
26 changes: 10 additions & 16 deletions portal-frontend/src/pages/FormPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -76,6 +62,7 @@ const FormPage = ({ formKey }) => {
setError(null);

try {
const getAuthToken = createGetAuthToken(getAccessTokenSilently, setError);
const token = await getAuthToken();
if (!token) {
return;
Expand Down Expand Up @@ -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);

Expand Down
111 changes: 64 additions & 47 deletions portal-frontend/src/pages/FormsLandingPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,78 +7,94 @@ 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 = () => {
const { getAccessTokenSilently } = useAuth0();
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;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just to make sure errors get caught here -- wasn't visible before

} 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}`);
Expand Down Expand Up @@ -110,6 +126,7 @@ const FormsLandingPage = () => {
<Navbar />
<WhiteBackground />
{loading && <FullPageLoadingSpinner />}
{error && <div style={{ color: "red", padding: "1rem" }}>{error}</div>}
<div className="forms-landing-container">
<h1 className="forms-landing-title">Applications & Forms</h1>
<p className="forms-landing-subtitle">
Expand Down
58 changes: 58 additions & 0 deletions portal-frontend/src/utils/authUtils.js
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the main fix -- force relogin on any of the above issues.

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);
};