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
36 changes: 35 additions & 1 deletion frontend/src/authentication/authenticate.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import { baseUrl, retrieveUserData } from "../emails/emailHandler";

/**
* Initiates the OAuth authentication flow by redirecting to the backend login endpoint.
* Sets the redirect URI to the /loading route.
* @async
* @returns {void}
*/
export const authenticate = async () => {
// Check for auth hash and render OAuthCallback if present
const redirect_uri = `${window.location.origin}/loading`;
window.location.href = `${baseUrl}/auth/login?redirect_uri=${redirect_uri}`;
};

// When Reach loading component call this function
/**
* Handles the OAuth callback after authentication.
* Parses the auth state from the URL hash, verifies authentication, and stores the token.
* Navigates to the error page if authentication fails.
* @async
* @returns {Promise<void>}
*/
export const handleOAuthCallback = async () => {
const hash = window.location.hash;
if (hash && hash.startsWith("#auth=")) {
Expand All @@ -30,6 +43,13 @@ export const handleOAuthCallback = async () => {
}
};

/**
* Stores the authentication token and retrieves user data.
* Navigates to the error page if authentication fails.
* @async
* @param {string} token - The authentication token.
* @returns {Promise<void>}
*/
export const handleAuthenticate = async (token) => {
try {
localStorage.setItem("auth_token", token);
Expand All @@ -39,13 +59,27 @@ export const handleAuthenticate = async (token) => {
}
};


/**
* Handles authentication errors by logging out, storing the error message,
* and redirecting to the error page.
* @async
* @param {Error|string} error - The error object or message.
* @returns {Promise<void>}
*/
const handleAuthError = async (error) => {
console.error("Auth flow error:", error);
localStorage.removeItem("auth_token");
localStorage.setItem("error_message", error.message); // Store error message in local storage
window.location.href = "/error"; // go to error page
};

/**
* Checks the authentication status of the provided token by querying the backend.
* @async
* @param {string} token - The authentication token.
* @returns {Promise<boolean>} True if authenticated, false otherwise.
*/
export const checkAuthStatus = async (token) => {
const option = {
method: "GET",
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/client/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import { clientReducer, userPreferencesReducer } from "./reducers";
import { Settings } from "./settings/settings";
import SideBar from "./sidebar/sidebar";

/**
* Main client component for authenticated user experience.
* Handles sidebar, routing, user preferences, and periodic email fetching.
* @param {Object} props
* @param {Array<Email>} props.emailsByDate - List of emails grouped by date.
* @param {Object} props.defaultUserPreferences - Default user preferences.
* @returns {JSX.Element}
*/
function Client({
emailsByDate,
defaultUserPreferences = {
Expand All @@ -27,6 +35,10 @@ function Client({
defaultUserPreferences
);

/**
* Sets up an interval to fetch new emails based on user preference.
* @returns {void}
*/
useEffect(() => {
const clock = setInterval(async () => {
try {
Expand All @@ -38,19 +50,25 @@ function Client({
return () => clearInterval(clock);
}, [userPreferences.emailFetchInterval]);

// Dynamically update sidebar width
const root = document.querySelector(":root");
root.style.setProperty(
"--sidebar-width",
`calc(${client.expandedSideBar ? "70px + 5vw" : "30px + 2vw"})`
);

/** Handles logo click to toggle sidebar expansion. */
const handleLogoClick = () => {
dispatchClient({
type: "logoClick",
state: client.expandedSideBar,
});
};

/**
* Handles navigation between client pages.
* @param {string} pageName - The page route to navigate to.
*/
const handlePageChange = (pageName) => {
const toChange = import.meta.env.MODE === "test" ? "/client" : null;
if (toChange) {
Expand All @@ -60,27 +78,40 @@ function Client({
}
};

/** Toggles the summaries-in-inbox user preference. */
const handleToggleSummariesInInbox = () => {
dispatchUserPreferences({
type: "isChecked",
isChecked: userPreferences.isChecked,
});
};

/**
* Sets the email fetch interval user preference.
* @param {number} interval - Interval in seconds.
*/
const handleSetEmailFetchInterval = (interval) => {
dispatchUserPreferences({
type: "emailFetchInterval",
emailFetchInterval: interval,
});
};

/**
* Sets the theme user preference.
* @param {string} theme - Theme name ("light", "system", "dark").
*/
const handleSetTheme = (theme) => {
dispatchUserPreferences({
type: "theme",
theme: theme,
});
};

/**
* Sets the currently selected email.
* @param {Email} email - The email object to set as current.
*/
const handleSetCurEmail = (email) => {
dispatchClient({
type: "emailChange",
Expand Down
31 changes: 29 additions & 2 deletions frontend/src/components/client/dashboard/dashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import PropTypes from "prop-types";
import ViewIcon from "../../../assets/ViewIcon";
import { getTop5 } from "../../../emails/emailHandler";
import MiniViewPanel from "./miniview";
import PropTypes from "prop-types";
import "./dashboard.css";
import MiniViewPanel from "./miniview";

/**
* Dashboard component for the client.
* Displays the weighted email list and the mini view panel.
* @param {Object} props
* @param {Array<Email>} props.emailList - List of emails.
* @param {Function} props.handlePageChange - Function to change the client page.
* @param {Function} props.setCurEmail - Function to set the current email.
* @returns {JSX.Element}
*/
function Dashboard({ emailList, handlePageChange, setCurEmail }) {
return (
<div className="dashboard">
Expand All @@ -21,6 +30,15 @@ function Dashboard({ emailList, handlePageChange, setCurEmail }) {
);
}

/**
* Renders a list of the top 5 weighted emails.
* @const {JSX.Element} WEList - Returns an array of WEListEmail components for the top 5 emails.
* @param {Object} props
* @param {Array<Email>} props.emailList - List of emails.
* @param {Function} props.setCurEmail - Function to set the current email.
* @param {Function} props.handlePageChange - Function to change the client page.
* @returns {JSX.Element}
*/
function WeightedEmailList({ emailList, setCurEmail, handlePageChange }) {
const emails = () => {
const WEList = getTop5(emailList);
Expand All @@ -40,6 +58,15 @@ function WeightedEmailList({ emailList, setCurEmail, handlePageChange }) {
return <div className="weighted-email-list-container">{emails()}</div>;
}

/**
* Renders a single weighted email entry with summary and view icon.
* @const {JSX.Element} summary - Renders the summary for the email, or a loading placeholder if not available.
* @param {Object} props
* @param {Email} props.email - The email object.
* @param {Function} props.setCurEmail - Function to set the current email.
* @param {Function} props.handlePageChange - Function to change the client page.
* @returns {JSX.Element}
*/
function WEListEmail({ email, setCurEmail, handlePageChange }) {
const summary = () => {
let returnBlock;
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/components/client/dashboard/miniview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import InboxIcon from "../../../assets/InboxArrow";
import { emailsPerPage } from "../../../assets/constants";
import "./miniview.css";

/**
* MiniViewPanel component for the client dashboard.
* Displays a list of emails in a compact view with an option to expand to the full inbox.
* @param {Object} props
* @param {Array<Email>} props.emailList - List of emails.
* @param {Function} props.handlePageChange - Function to change the client page.
* @param {Function} props.setCurEmail - Function to set the current email.
* @returns {JSX.Element}
*/
function MiniViewPanel({ emailList, handlePageChange, setCurEmail }) {
return (
<div className="mini-view">
Expand All @@ -19,6 +28,12 @@ function MiniViewPanel({ emailList, handlePageChange, setCurEmail }) {
);
}

/**
* Renders the headfor the mini view
* @param {Object} props
* @param {Function} props.handlePageChange - Function to change the client page.
* @returns {JSX.Element}
*/
function MiniViewHead({ handlePageChange }) {
return (
<div className="head-container">
Expand All @@ -39,6 +54,14 @@ function MiniViewHead({ handlePageChange }) {
);
}

/**
* Displays a scrollable list of emails, loading more as the user scrolls.
* @param {Object} props
* @param {Array<Email>} props.emailList - List of emails.
* @param {Function} props.setCurEmail - Function to set the current email.
* @param {Function} props.handlePageChange - Function to change the client page.
* @returns {JSX.Element}
*/
function MiniViewBody({ emailList, setCurEmail, handlePageChange }) {
const [pages, setPages] = useState(1);
const ref = useRef(null);
Expand All @@ -48,6 +71,10 @@ function MiniViewBody({ emailList, setCurEmail, handlePageChange }) {
: emailList.length;
const hasUnloadedEmails = maxEmails < emailList.length;


/**
* Handles the scroll event to load more emails when the user scrolls to the bottom.
*/
const handleScroll = () => {
const fullyScrolled =
Math.abs(
Expand All @@ -64,6 +91,10 @@ function MiniViewBody({ emailList, setCurEmail, handlePageChange }) {
handleScroll();
}, [pages]); // Fixes minimum for large screens, but runs effect after every load which is unnecessary

/**
* Renders the list of MiniViewEmail components up to maxEmails.
* @returns {JSX.Element[]}
*/
const emails = () => {
const returnBlock = [];
for (let i = 0; i < maxEmails; i++) {
Expand All @@ -85,6 +116,14 @@ function MiniViewBody({ emailList, setCurEmail, handlePageChange }) {
);
}

/**
* Renders a single email entry in the mini view.
* @param {Object} props
* @param {Email} props.email - The email object.
* @param {Function} props.setCurEmail - Function to set the current email.
* @param {Function} props.handlePageChange - Function to change the client page.
* @returns {JSX.Element}
*/
function MiniViewEmail({ email, setCurEmail, handlePageChange }) {
return (
<div
Expand Down Expand Up @@ -127,6 +166,11 @@ MiniViewEmail.propTypes = {
email: PropTypes.object,
};

/**
* Gets the sender's name from the sender string.
* @param {string} sender - The sender string
* @returns {string} The sender's name.
*/
const getSenderName = (sender) => {
return sender.slice(0, sender.indexOf("<"));
};
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/client/inbox/Email.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import PropTypes from "prop-types";

/**
* Renders the email body, using inner HTML if present.
* @param {Object} props
* @param {Email} props.email - The email object to display.
* @returns {JSX.Element}
*/
export function Email({ email }) {
return (
<>
Expand Down
37 changes: 36 additions & 1 deletion frontend/src/components/client/inbox/emailDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import PropTypes from "prop-types";
import { useEffect, useState } from "react";
import ReactDom from "react-dom";
import ReaderViewIcon from "../../../assets/ReaderView";
import Email from "./Email";
import { getReaderView } from "../../../emails/emailHandler";
import Email from "./Email";
import "./emailDisplay.css";

/**
* Displays the currently selected email with header, body, and reader view option.
* @param {Object} props
* @param {Email} props.curEmail - The currently selected email object.
* @returns {JSX.Element}
*/
function EmailDisplay({
curEmail = {
user_id: 1,
Expand Down Expand Up @@ -40,10 +46,21 @@ function EmailDisplay({
);
}

/**
* Fetches and displays a simplified, readable version of the email.
* @param {Object} props
* @param {Email} props.curEmail - The currently selected email object.
* @returns {JSX.Element}
*/
function ReaderView({ curEmail }) {
const [text, setText] = useState("Loading ...");
const [displaying, setDisplaying] = useState(false);

/**
* Toggles the display of the reader view and fetches content if opening.
* @async
* @returns {Promise<void>}
*/
async function displayReaderView() {
setDisplaying(!displaying);
if (!displaying) {
Expand Down Expand Up @@ -87,6 +104,14 @@ function ReaderView({ curEmail }) {
);
}

/**
* Renders a modal overlay for displaying content or a loading spinner.
* @param {Object} props
* @param {boolean} props.isLoading - Whether to show the loading spinner.
* @param {Function} props.handleClose - Function to close the popup.
* @param {React.ReactNode} props.children - Content to display in the popup.
* @returns {JSX.Element}
*/
function PopUp({ isLoading, handleClose, children }) {
return ReactDom.createPortal(
isLoading ? (
Expand All @@ -108,10 +133,20 @@ function PopUp({ isLoading, handleClose, children }) {
);
}

/**
* Formats a date array as MM/DD/YYYY.
* @param {Array<string|number>} date - [year, month, day]
* @returns {string} Formatted date string.
*/
const formatDate = (date) => {
return `${date[1]}/${date[2]}/${date[0]}`;
};

/**
* Extracts the sender's name from the sender string.
* @param {string} sender - The sender string
* @returns {string} The sender's name.
*/
const getSenderName = (sender) => {
return sender.slice(0, sender.indexOf("<"));
};
Expand Down
Loading