Repository: Turing-Sandbox/COMP313-402-Team3-W25
Pull request #66: Story/TS-3: Frontend Base implement 
Author: login: BengeeL
Created: 2025-03-25T20:50:37Z | Merged: 2025-03-26T13:14:03Z

Will likely require more styling once I have the results from the backend queries. Might also require adjustment to the route request.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - The app now offers a dynamic, personalized experience by seamlessly loading trending, latest, and tailored recommendations. Interactive content displays allow for effortless navigation and enhanced engagement.
  - Introduced a new `ContentTile` component for displaying content items with interactive features.

- **Style**
  - Enjoy an updated visual design featuring refreshed color themes, consistent icon styling, and a modern grid layout with subtle hover effects for an improved browsing experience.
  - New styles for content tiles enhance the layout and appearance of content items.
  - Introduced a new grid layout for content lists, improving item organization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

┆Issue is synchronized with this [Trello card](https://trello.com/c/adSHTBe9) by [Unito](https://www.unito.io)



Diff:
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 6108950..d3ea87b 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -1,12 +1,180 @@
 "use client";
 
+import { useAuth } from "@/hooks/AuthProvider";
+import { Content } from "@/models/Content";
+import { useEffect, useState } from "react";
+import { apiURL } from "./scripts/api";
+import axios from "axios";
+import { HeartIcon } from "@heroicons/react/24/outline";
+import ContentTile from "@/components/content/ContentTile";
+import { User } from "@/models/User";
+
 export default function Page() {
+  const [trendingContent, setTrendingContent] = useState<Content[]>([]);
+  const [latestContent, setLatestContent] = useState<Content[]>([]);
+  const [personalizedContent, setPersonalizedContent] = useState<Content[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+
+  const { userUID } = useAuth();
+  const [user, setUser] = useState<User | null>(null);
+
+  const [errorTrending, setErrorTrending] = useState<string | null>(null);
+  const [errorLatest, setErrorLatest] = useState<string | null>(null);
+  const [errorPersonalized, setErrorPersonalized] = useState<string | null>(
+    null
+  );
+
+  useEffect(() => {
+    const fetchContent = async () => {
+      setIsLoading(true);
+
+      setErrorTrending(null);
+      setErrorLatest(null);
+      setErrorPersonalized(null);
+
+      let userFetched = await fetchUser();
+      let latestFetched = await fetchLatestContent();
+      let trendingFetched = await fetchTrendingContent();
+      let personalizedFetched = await fetchPersonalizedContent();
+
+      if (!latestFetched) {
+        setErrorLatest(
+          "Failed to fetch latest content. Please reload the page or contact support."
+        );
+      }
+
+      if (!trendingFetched) {
+        setErrorTrending(
+          "Failed to fetch trending content. Please reload the page or contact support."
+        );
+      }
+
+      if (!personalizedFetched) {
+        setErrorPersonalized(
+          "Failed to fetch personalized content. Please reload the page or contact support."
+        );
+      }
+
+      setIsLoading(false);
+    };
+
+    fetchContent();
+  }, [userUID]);
+
+  async function fetchUser(): Promise<boolean> {
+    if (!userUID) {
+      setUser(null);
+      return false;
+    }
+    try {
+      const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
+        timeout: 5000,
+      });
+
+      if (userResponse.status === 200 || userResponse.status === 201) {
+        setUser(userResponse.data);
+        return true;
+      } else {
+        setUser(null);
+      }
+    } catch (userError) {
+      setUser(null);
+    }
+
+    return false;
+  }
+
+  async function fetchTrendingContent(): Promise<boolean> {
+    try {
+      const trendingResponse = await axios.get(
+        `${apiURL}/content/feed/trending`,
+        { timeout: 5000 }
+      );
+
+      if (trendingResponse.data && trendingResponse.data.success) {
+        setTrendingContent(trendingResponse.data.trendingContent);
+        return true;
+      } else {
+        setTrendingContent([]);
+      }
+    } catch (trendingError) {
+      setTrendingContent([]);
+    }
+
+    return false;
+  }
+
+  async function fetchLatestContent(): Promise<boolean> {
+    try {
+      const contentResponse = await axios.get(`${apiURL}/content`, {
+        timeout: 5000,
+      });
+
+      if (contentResponse.data && contentResponse.data.success) {
+        setLatestContent(contentResponse.data.content);
+        return true;
+      } else {
+        setLatestContent([]);
+      }
+    } catch (contentError) {
+      setLatestContent([]);
+    }
+
+    return false;
+  }
+
+  async function fetchPersonalizedContent(): Promise<boolean> {
+    if (!userUID) {
+      setPersonalizedContent([]);
+      return false;
+    }
+  
+    try {
+      const personalizedResponse = await axios.get(
+        `${apiURL}/content/feed/${userUID}`,
+        { timeout: 5000 }
+      );
+  
+      if (personalizedResponse.data && personalizedResponse.data.success) {
+        setPersonalizedContent(personalizedResponse.data.personalizedContent);
+        return true;
+      } else {
+        setPersonalizedContent([]);
+      }
+    } catch (personalizedError) {
+      setPersonalizedContent([]);
+    }
+    return false;
+  }
+
   return (
     <div className='main-content'>
-      <div className='header'>
-        <h1>Summarizz</h1>
-        <p>Summarize your articles, videos, and more with ease.</p>
+      {isLoading && <p>Loading...</p>}
+
+      {user && <h1>Welcome, {user?.firstName}</h1>}
+      <h2>Top Trending</h2>
+      <div className='content-list'>
+        {trendingContent.map((content, index) => (
+          <ContentTile key={content.id} content={content} index={index} />
+        ))}
+      </div>
+      {errorTrending && <p className='error'>{errorTrending}</p>}
+
+      <h2>Latest Post</h2>
+      <div className='content-list'>
+        {latestContent.map((content, index) => (
+          <ContentTile key={content.id} content={content} index={index} />
+        ))}
+      </div>
+      {errorLatest && <p className='error'>{errorLatest}</p>}
+
+      <h2>For You</h2>
+      <div className='content-list'>
+        {personalizedContent.map((content, index) => (
+          <ContentTile key={content.id} content={content} index={index} />
+        ))}
       </div>
+      {errorPersonalized && <p className='error'>{errorPersonalized}</p>}
     </div>
   );
 }
diff --git a/frontend/src/app/profile/[id]/page.tsx b/frontend/src/app/profile/[id]/page.tsx
index cb1dedd..a8f7393 100644
--- a/frontend/src/app/profile/[id]/page.tsx
+++ b/frontend/src/app/profile/[id]/page.tsx
@@ -7,7 +7,12 @@ import Image from "next/image";
 
 // Third-Party Libraries (Import)
 import axios from "axios";
-import { UserPlusIcon, TrashIcon, CheckIcon, XMarkIcon } from "@heroicons/react/24/solid";
+import {
+  UserPlusIcon,
+  TrashIcon,
+  CheckIcon,
+  XMarkIcon,
+} from "@heroicons/react/24/solid";
 
 // Local Files (Import)
 import { useAuth } from "@/hooks/AuthProvider";
@@ -17,6 +22,7 @@ import { apiURL } from "@/app/scripts/api";
 
 // Stylesheets
 import "@/app/styles/profile/profile.scss";
+import ContentTile from "@/components/content/ContentTile";
 
 /**
  * Page() -> JSX.Element
@@ -38,7 +44,9 @@ export default function Page() {
   const [sharedContent, setSharedContent] = useState<Content[]>([]);
   const [isLoading, setIsLoading] = useState(true);
   const [tab, setTab] = useState<"created" | "shared">("created");
-  const [followUsernames, setFollowUsernames] = useState<{ [userId: string]: string }>({}); // Cache for usernames
+  const [followUsernames, setFollowUsernames] = useState<{
+    [userId: string]: string;
+  }>({}); // Cache for usernames
 
   const { userUID } = useAuth(); // Get logged in user's UID
   const router = useRouter();
@@ -58,13 +66,17 @@ export default function Page() {
 
         // 2. Fetch User's Created Content
         if (userData?.content) {
-          const contentPromises = userData.content.map((contentId: string) => getContent(contentId));
+          const contentPromises = userData.content.map((contentId: string) =>
+            getContent(contentId)
+          );
           await Promise.all(contentPromises);
         }
-        
+
         // 3. Fetch Shared Content (only if sharedContent exists)
         if (userData?.sharedContent) {
-          const validSharedContent = await getAndFilterSharedContent(userData.sharedContent);
+          const validSharedContent = await getAndFilterSharedContent(
+            userData.sharedContent
+          );
           setSharedContent(validSharedContent);
         }
 
@@ -86,9 +98,9 @@ export default function Page() {
   // Fetch usernames for follow requests
   useEffect(() => {
     if (!user || !user.followRequests) return;
-  
+
     const followRequests = user.followRequests;
-  
+
     async function fetchUsernames() {
       const usernamesMap: { [key: string]: string } = {};
       for (const requesterId of followRequests) {
@@ -101,9 +113,9 @@ export default function Page() {
       }
       setFollowUsernames(usernamesMap);
     }
-  
+
     fetchUsernames();
-  }, [user]);  
+  }, [user]);
 
   // Helper function to fetch and filter shared content
   async function getAndFilterSharedContent(
@@ -193,17 +205,18 @@ export default function Page() {
    * @returns void
    */
   const handleFollow = async () => {
-      if (!userUID) {
-        alert("Please log in to follow users.");
-        return;
+    if (!userUID) {
+      alert("Please log in to follow users.");
+      return;
     }
 
     if (userUID === id) {
-        alert("You can't follow yourself.");
-        return;
+      alert("You can't follow yourself.");
+      return;
     }
 
-    if (!user) { // Defensive check
+    if (!user) {
+      // Defensive check
       console.error("User data not available.");
       return;
     }
@@ -212,10 +225,12 @@ export default function Page() {
       let url = "";
       let method = "post";
 
-      if (isFollowing) { // Unfollow
+      if (isFollowing) {
+        // Unfollow
         url = `${apiURL}/user/${userUID}/unfollow/${id}`;
-      } else if (user.isPrivate) { // Private profile: Send request, or cancel if already requested (optional)
-        if(followRequested){
+      } else if (user.isPrivate) {
+        // Private profile: Send request, or cancel if already requested (optional)
+        if (followRequested) {
           // cancel request (OPTIONAL: Handle Cancel Request (requires backend endpoint))
           // url = `${apiURL}/user/${userUID}/cancel-request/${id}`;
           // await axios.post(url); // UNCOMMENT WHEN ENDPOINT IS READY
@@ -223,10 +238,11 @@ export default function Page() {
           return; // for now
         } else {
           // Send Request
-          url = `${apiURL}/user/${userUID}/request/${id}`; 
-        } 
-      } else { // Public profile: Follow directly
-          url = `${apiURL}/user/${userUID}/follow/${id}`;
+          url = `${apiURL}/user/${userUID}/request/${id}`;
+        }
+      } else {
+        // Public profile: Follow directly
+        url = `${apiURL}/user/${userUID}/follow/${id}`;
       }
 
       // Only make the API call if a URL is set
@@ -235,20 +251,19 @@ export default function Page() {
 
         // Optimistically update the UI *immediately*
         if (isFollowing) {
-            setIsFollowing(false); // Just unfollowed
+          setIsFollowing(false); // Just unfollowed
         } else if (user.isPrivate && !followRequested) {
-            setFollowRequested(true); // Just sent a request
+          setFollowRequested(true); // Just sent a request
         } else if (!user.isPrivate) {
-            setIsFollowing(true);  // Just followed directly
+          setIsFollowing(true); // Just followed directly
         }
         // Refetch data
         const userResponse = await axios.get(`${apiURL}/user/${id}`);
         setUser(userResponse.data);
       }
-
     } catch (error) {
-       console.error("Error handling follow/request:", error);
-       alert("An error occurred. Please try again.")
+      console.error("Error handling follow/request:", error);
+      alert("An error occurred. Please try again.");
     }
   };
 
@@ -260,7 +275,9 @@ export default function Page() {
 
     try {
       // Make API call to unshare
-      await axios.post(`${apiURL}/content/${contentId}/user/${userUID}/unshare`);
+      await axios.post(
+        `${apiURL}/content/${contentId}/user/${userUID}/unshare`
+      );
 
       // Update the UI: Filter out the unshared content
       setSharedContent((prevSharedContent) =>
@@ -280,23 +297,27 @@ export default function Page() {
       await axios.post(`${apiURL}/user/${id}/approve/${requesterId}`);
 
       //Update local state
-      setUser(prevUser => {
+      setUser((prevUser) => {
         if (!prevUser) return null;
 
-        const updatedFollowRequests = prevUser.followRequests?.filter(id => id !== requesterId);
-        const updatedFollowers = prevUser.followers ? [...prevUser.followers, requesterId] : [requesterId];
+        const updatedFollowRequests = prevUser.followRequests?.filter(
+          (id) => id !== requesterId
+        );
+        const updatedFollowers = prevUser.followers
+          ? [...prevUser.followers, requesterId]
+          : [requesterId];
 
-          return {
-            ...prevUser,
-            followRequests: updatedFollowRequests,
-            followers: updatedFollowers,
-          }
+        return {
+          ...prevUser,
+          followRequests: updatedFollowRequests,
+          followers: updatedFollowers,
+        };
       });
-       // Refetch user data to be 100% sure
-       const userResponse = await axios.get(`${apiURL}/user/${id}`);
-       setUser(userResponse.data);
+      // Refetch user data to be 100% sure
+      const userResponse = await axios.get(`${apiURL}/user/${id}`);
+      setUser(userResponse.data);
 
-       console.log("Follow request approved successfully.");
+      console.log("Follow request approved successfully.");
     } catch (error) {
       console.error("Error approving follow request:", error);
       alert("Failed to approve follow request. Please try again.");
@@ -309,13 +330,15 @@ export default function Page() {
       await axios.post(`${apiURL}/user/${id}/reject/${requesterId}`);
 
       // Update local state
-      setUser(prevUser => {
+      setUser((prevUser) => {
         if (!prevUser) return null;
-        const updatedFollowRequests = prevUser.followRequests?.filter(id => id !== requesterId);
+        const updatedFollowRequests = prevUser.followRequests?.filter(
+          (id) => id !== requesterId
+        );
 
         return {
           ...prevUser,
-          followRequests: updatedFollowRequests
+          followRequests: updatedFollowRequests,
         };
       });
       // Refetch user data to be 100% sure
@@ -329,50 +352,50 @@ export default function Page() {
     }
   };
 
-  function renderContentItem(content: Content, index: number) {
-    return (
-      <div
-        key={content.id || index}
-        className='content-list-item'
-        onClick={() => router.push(`/content/${content.id}`)}
-      >
-        <h3 className='content-item-title'>{content.title}</h3>
-        <p>
-          {content.dateCreated
-            ? `${content.dateCreated.toLocaleString("en-US", {
-                month: "short",
-              })} ${content.dateCreated.getDate()}${
-                content.readtime ? ` - ${content.readtime} min read` : ""
-              }`
-            : ""}
-        </p>
-        {content.thumbnail && (
-          <div className='content-thumbnail-container'>
-            <Image
-              src={content.thumbnail}
-              alt='Thumbnail'
-              width={200}
-              height={200}
-              className='content-thumbnail'
-            />
-          </div>
-        )}
-        {/* Unshare Button */}
-        {sharedContent.some((sharedItem) => sharedItem.id === content.id) && (
-          <button
-            className='icon-button'
-            onClick={(e) => {
-              e.stopPropagation(); // Prevent content item click
-              handleUnshare(content.id);
-            }}
-            title='Unshare Content'
-          >
-            <TrashIcon className='icon delete' />
-          </button>
-        )}
-      </div>
-    );
-  }
+  // function renderContentItem(content: Content, index: number) {
+  //   return (
+  //     <div
+  //       key={content.id || index}
+  //       className='content-list-item'
+  //       onClick={() => router.push(`/content/${content.id}`)}
+  //     >
+  //       <h3 className='content-item-title'>{content.title}</h3>
+  //       <p>
+  //         {content.dateCreated
+  //           ? `${content.dateCreated.toLocaleString("en-US", {
+  //               month: "short",
+  //             })} ${content.dateCreated.getDate()}${
+  //               content.readtime ? ` - ${content.readtime} min read` : ""
+  //             }`
+  //           : ""}
+  //       </p>
+  //       {content.thumbnail && (
+  //         <div className='content-thumbnail-container'>
+  //           <Image
+  //             src={content.thumbnail}
+  //             alt='Thumbnail'
+  //             width={200}
+  //             height={200}
+  //             className='content-thumbnail'
+  //           />
+  //         </div>
+  //       )}
+  //       {/* Unshare Button */}
+  //       {sharedContent.some((sharedItem) => sharedItem.id === content.id) && (
+  //         <button
+  //           className='icon-button'
+  //           onClick={(e) => {
+  //             e.stopPropagation(); // Prevent content item click
+  //             handleUnshare(content.id);
+  //           }}
+  //           title='Unshare Content'
+  //         >
+  //           <TrashIcon className='icon delete' />
+  //         </button>
+  //       )}
+  //     </div>
+  //   );
+  // }
 
   if (isLoading) {
     return <div>Loading profile...</div>;
@@ -390,27 +413,29 @@ export default function Page() {
   const followingCount = user?.following ? user.following.length : 0;
   const createdCount = user?.content ? user.content.length : 0;
   const sharedCount = user?.sharedContent ? user.sharedContent.length : 0;
-  const canViewFullProfile = !user?.isPrivate || userUID === id || (userUID && user?.followers?.includes(userUID));
+  const canViewFullProfile =
+    !user?.isPrivate ||
+    userUID === id ||
+    (userUID && user?.followers?.includes(userUID));
 
-    // Helper function to get username by ID (Simplified)
-    async function getUsername(userId: string): Promise<string> {
-      try {
-        const userResponse = await axios.get(`${apiURL}/user/${userId}`);
-        return userResponse.data.username || "Unknown User"; // Provide a fallback
-      } catch (error) {
-        console.error("Error fetching username:", error);
-        return "Unknown User"; // Fallback in case of error
-      }
-    }
-  
-  
-    if (isLoading) {
-      return <div>Loading profile...</div>;
-    }
-  
-    if (!user) {
-      return <div>User not found.</div>;
+  // Helper function to get username by ID (Simplified)
+  async function getUsername(userId: string): Promise<string> {
+    try {
+      const userResponse = await axios.get(`${apiURL}/user/${userId}`);
+      return userResponse.data.username || "Unknown User"; // Provide a fallback
+    } catch (error) {
+      console.error("Error fetching username:", error);
+      return "Unknown User"; // Fallback in case of error
     }
+  }
+
+  if (isLoading) {
+    return <div>Loading profile...</div>;
+  }
+
+  if (!user) {
+    return <div>User not found.</div>;
+  }
   return (
     <>
       <div className='main-content'>
@@ -437,7 +462,9 @@ export default function Page() {
               {/* Follow/Request Button (Conditional Rendering) */}
               {userUID !== id && ( // Don't show button if viewing own profile
                 <button
-                  className={`icon-button follow ${isFollowing ? "following" : ""}`}
+                  className={`icon-button follow ${
+                    isFollowing ? "following" : ""
+                  }`}
                   onClick={handleFollow}
                   title={
                     isFollowing
@@ -450,12 +477,9 @@ export default function Page() {
                   }
                 >
                   <UserPlusIcon
-                    className={`icon follow ${isFollowing || followRequested ? "following" : ""}`}
-                    style={{
-                      color: isFollowing || followRequested ? "black" : "#7D7F7C",
-                      width: "16px",
-                      height: "16px",
-                    }}
+                    className={`icon follow ${
+                      isFollowing || followRequested ? "following" : ""
+                    }`}
                   />
                 </button>
               )}
@@ -495,34 +519,36 @@ export default function Page() {
         </div>
 
         {/* Follow Requests Section (Conditional Rendering) */}
-        {userUID === id && user?.followRequests && user.followRequests.length > 0 && (
-          <div className="follow-requests-section">
-            <h3>Follow Requests</h3>
-            <ul>
-            {user.followRequests.map((requesterId) => (
-              <li key={requesterId}>
-                <span>{followUsernames[requesterId] || "Loading..."}</span>
-                <div className="request-buttons">
-                  <button
-                    className="icon-button approve"
-                    onClick={() => handleApproveRequest(requesterId)}
-                    title="Approve Request"
-                  >
-                    <CheckIcon className="icon check" />
-                  </button>
-                  <button
-                    className="icon-button reject"
-                    onClick={() => handleRejectRequest(requesterId)}
-                    title="Reject Request"
-                  >
-                    <XMarkIcon className="icon xmark" />
-                  </button>
-                </div>
-              </li>
-            ))}
-            </ul>
-          </div>
-        )}
+        {userUID === id &&
+          user?.followRequests &&
+          user.followRequests.length > 0 && (
+            <div className='follow-requests-section'>
+              <h3>Follow Requests</h3>
+              <ul>
+                {user.followRequests.map((requesterId) => (
+                  <li key={requesterId}>
+                    <span>{followUsernames[requesterId] || "Loading..."}</span>
+                    <div className='request-buttons'>
+                      <button
+                        className='icon-button approve'
+                        onClick={() => handleApproveRequest(requesterId)}
+                        title='Approve Request'
+                      >
+                        <CheckIcon className='icon check' />
+                      </button>
+                      <button
+                        className='icon-button reject'
+                        onClick={() => handleRejectRequest(requesterId)}
+                        title='Reject Request'
+                      >
+                        <XMarkIcon className='icon xmark' />
+                      </button>
+                    </div>
+                  </li>
+                ))}
+              </ul>
+            </div>
+          )}
 
         {/* Tabs for Created/Shared Content */}
         <div className='tabs'>
@@ -550,9 +576,13 @@ export default function Page() {
               <h3>No content found</h3>
             ) : (
               <div className='content-list'>
-                {contents.map((content, index) =>
-                  renderContentItem(content, index)
-                )}
+                {contents.map((content, index) => (
+                  <ContentTile
+                    key={content.id || index}
+                    content={content}
+                    index={index}
+                  />
+                ))}
               </div>
             )}
           </>
@@ -566,9 +596,19 @@ export default function Page() {
                 <h4>No shared content found</h4>
               ) : (
                 <div className='content-list'>
-                  {sharedContent.map((content, index) =>
-                    renderContentItem(content, index)
-                  )}
+                  {sharedContent.map((content, index) => (
+                    <ContentTile
+                      key={content.id || index}
+                      content={content}
+                      index={index}
+                      deleteShareOption={
+                        sharedContent.some(
+                          (sharedItem) => sharedItem.id === content.id
+                        ) && userUID === id
+                      }
+                      handleUnshare={handleUnshare}
+                    />
+                  ))}
                 </div>
               )
             ) : (
diff --git a/frontend/src/app/styles/colors.scss b/frontend/src/app/styles/colors.scss
index f069126..5edaa25 100644
--- a/frontend/src/app/styles/colors.scss
+++ b/frontend/src/app/styles/colors.scss
@@ -2,13 +2,16 @@
 :root {
   --background-color: #d9d9d9;
   --text-color: #2a2a2a;
+  --text-color-light: #505050;
+
   --tile-color: rgba(176, 176, 176, 0.5);
   --input-field-color: rgba(255, 255, 255, 0.25);
   --input-border-color: rgba(0, 0, 0, 0.25);
   --comment-background-color: rgb(218, 212, 210);
   --comment-text-color: rgb(255, 255, 255);
-  --icon-color: #7d7f7c;
+  --icon-color: #2a2a2a;
   --icon-hover-color: rgb(70, 70, 70);
+  --icon-selected-color: rgba(120, 120, 120, 0.5);
   --delete-hover-color: #ff5656;
   --like-hover-color: #fa4d8a;
   --edit-hover-color: #ff8500;
@@ -30,13 +33,16 @@
 [data-theme="dark"] {
   --background-color: #2a2a2a;
   --text-color: #ffffff;
+  --text-color-light: #cdcdcd;
+
   --tile-color: rgba(67, 63, 63, 0.8);
   --input-field-color: rgba(0, 0, 0, 0.25);
   --input-border-color: rgba(255, 255, 255, 0.25);
   --comment-background-color: rgb(41, 41, 41);
   --comment-text-color: rgba(0, 0, 0, 0.25);
-  --icon-color: #7d7f7c;
+  --icon-color: #ffffff;
   --icon-hover-color: rgb(200, 200, 200);
+  --icon-selected-color: rgba(129, 129, 129, 0.8);
   --delete-hover-color: #d66363;
   --like-hover-color: #fa4d8a;
   --edit-hover-color: #d69d63;
diff --git a/frontend/src/app/styles/content/contentTile.scss b/frontend/src/app/styles/content/contentTile.scss
new file mode 100644
index 0000000..8ca126d
--- /dev/null
+++ b/frontend/src/app/styles/content/contentTile.scss
@@ -0,0 +1,103 @@
+.content-tile-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 10px;
+}
+
+.unshare {
+  margin-top: 5px;
+}
+
+.content-stats {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: 10px;
+  gap: 50px;
+}
+
+.stat-item {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.stat-icon {
+  width: 20px;
+  height: 20px;
+  color: var(--icon-color);
+}
+
+.stat-number {
+  margin: 0;
+}
+
+.content-list-item {
+  padding: 20px;
+
+  border-radius: 20px;
+  border-style: solid;
+  border-color: var(--input-border-color);
+  border-width: 1px;
+  box-sizing: border-box;
+
+  background-color: var(--tile-color);
+  color: var(--text-color);
+
+  font-size: 1.1rem;
+}
+
+.content-item-title {
+  font-size: 1.5rem;
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+
+.content-list-item:hover {
+  background-color: var(--background-color);
+  cursor: pointer;
+}
+
+.content-thumbnail-container {
+  overflow: hidden;
+  border-radius: 10px;
+}
+
+.content-thumbnail {
+  width: 100%;
+  height: 200px;
+  object-fit: cover;
+  border-radius: 10px;
+  transition: transform 0.5s;
+}
+
+.content-thumbnail:hover {
+  transform: scale(1.1);
+}
+
+.icon-button {
+  padding: 0;
+  border: none;
+  background: none;
+  display: inline-flex;
+  align-items: center;
+  position: relative;
+  gap: 1px;
+}
+
+.icon {
+  width: 20px;
+  height: 20px;
+  color: var(--icon-color);
+  transition: all ease-out 0.3s;
+}
+
+.icon:hover {
+  color: var(--icon-hover-color);
+  cursor: pointer;
+}
+
+.icon.delete:hover {
+  color: var(--delete-hover-color);
+}
diff --git a/frontend/src/app/styles/feed.scss b/frontend/src/app/styles/feed.scss
new file mode 100644
index 0000000..a6464d6
--- /dev/null
+++ b/frontend/src/app/styles/feed.scss
@@ -0,0 +1,5 @@
+.content-list {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 1rem;
+}
diff --git a/frontend/src/app/styles/profile/profile.scss b/frontend/src/app/styles/profile/profile.scss
index 6beb392..6776a06 100644
--- a/frontend/src/app/styles/profile/profile.scss
+++ b/frontend/src/app/styles/profile/profile.scss
@@ -24,7 +24,7 @@
   display: flex;
   flex-direction: column;
   align-items: flex-start;
- 
+
   p {
     margin: 0;
     margin-bottom: 0.5rem; /* small gap between paragraphs */
@@ -50,38 +50,38 @@
 
 /* Follow icon styles */
 .icon.follow {
-  width: 16px; /* Match other icons */
-  height: 16px; /* Match other icons */
-  color: #7d7f7c; /* Default gray */
+  width: 16px; 
+  height: 16px; 
+  color: var(--icon-color); 
 }
 
 .icon.follow.following {
-  color: black; /* Black when following */
+  color: var(--icon-selected-color); 
 }
 
 .profile-stats {
   display: flex;
   align-items: center;
-  gap: 0.7rem;           /* Space between each stat item */
-  margin-bottom: 1.25rem;    /* Spacing below stats */
-  font-family: var(--font-body); 
+  gap: 0.7rem; 
+  margin-bottom: 1.25rem; 
+  font-family: var(--font-body);
 }
 
 .stat-item {
   display: flex;
   flex-direction: column;
-  align-items: center;  /* Center number and label horizontally */
-  font-size: 0.7rem;    /* Smaller font size for stats */
+  align-items: center; /* Center number and label horizontally */
+  font-size: 0.7rem; /* Smaller font size for stats */
 }
 
 .stat-number {
-  font-size: 0.9rem; 
+  font-size: 0.9rem;
   font-weight: bold;
 }
 
 .stat-label {
   font-size: 0.7rem;
-  color: #7d7f7c;
+  color: var(--text-color-light); 
 }
 
 .section-title {
@@ -96,77 +96,6 @@
   gap: 1rem; /* Adjust the gap between items */
 }
 
-.content-list-item {
-  padding: 20px;
-
-  border-radius: 20px;
-  border-style: solid;
-  border-color: var(--input-border-color);
-  border-width: 1px;
-  box-sizing: border-box;
-
-  background-color: var(--tile-color);
-  color: var(--text-color);
-
-  font-size: 1.1rem;
-}
-
-.content-item-title {
-  font-size: 1.5rem;
-  margin-top: 0;
-  margin-bottom: 10px;
-}
-
-.content-list-item:hover {
-  background-color: var(--background-color);
-  cursor: pointer;
-}
-
-.content-thumbnail-container {
-  overflow: hidden;
-  border-radius: 10px;
-}
-
-.content-thumbnail {
-  width: 100%;
-  height: 200px;
-  object-fit: cover;
-  border-radius: 10px;
-  transition: transform 0.5s;
-}
-
-.content-thumbnail:hover {
-  // Zoom image on hover
-  transform: scale(1.1);
-}
-
-.icon-button {
-  padding: 0;
-  border: none;
-  background: none;
-  display: inline-flex;
-  align-items: center;
-  position: relative;
-  gap: 1px;
-}
-
-.icon {
-  width: 16px; /* Or whatever size you want */
-  height: 16px;
-  color: var(--icon-color); /* Use CSS variables if you have them */
-  transition: all ease-out 0.3s;
-}
-
-.icon:hover {
-  color: var(--icon-hover-color);
-}
-
-/* Specific style for the delete icon's hover state */
-.icon.delete:hover {
-  color: var(
-    --delete-hover-color
-  ); /* Use a specific variable for delete hover */
-}
 
 /* Tabs container */
 .tabs {
diff --git a/frontend/src/components/content/ContentTile.tsx b/frontend/src/components/content/ContentTile.tsx
new file mode 100644
index 0000000..90b3556
--- /dev/null
+++ b/frontend/src/components/content/ContentTile.tsx
@@ -0,0 +1,118 @@
+"use client";
+
+import { Content } from "@/models/Content";
+import { useRouter } from "next/navigation";
+import Image from "next/image";
+import {
+  BookmarkIcon as BookmarkIconOutline,
+  EyeIcon as EyeIconOutline,
+  HeartIcon as HeartIconOutline,
+  ShareIcon as ShareIconOutline,
+  TrashIcon as TrashIconOutline,
+} from "@heroicons/react/24/outline";
+import {
+  HeartIcon as HeartIconFilled,
+  ShareIcon as ShareIconFilled,
+  BookmarkIcon as BookmarkIconFilled,
+} from "@heroicons/react/24/solid";
+
+import "@/app/styles/content/contentTile.scss";
+import { useAuth } from "@/hooks/AuthProvider";
+
+interface ContentTileProps {
+  content: Content;
+  index: number;
+  hideStats?: boolean;
+  deleteShareOption?: boolean;
+  handleUnshare?: (contentId: string) => void;
+}
+
+export default function ContentTile({
+  content,
+  index,
+  hideStats = false,
+  deleteShareOption = false,
+  handleUnshare = () => {},
+}: ContentTileProps) {
+  const router = useRouter();
+  const { userUID } = useAuth();
+
+  return (
+    <div
+      key={content.id || index}
+      className='content-list-item'
+      onClick={() => router.push(`/content/${content.id}`)}
+    >
+      <div className='content-tile-header'>
+        <h3 className='content-item-title'>{content.title}</h3>
+        {/* Unshare Button */}
+        {deleteShareOption && (
+          <button
+            className='icon-button unshare'
+            onClick={(e) => {
+              e.stopPropagation();
+              handleUnshare(content.id);
+            }}
+            title='Unshare Content'
+          >
+            <TrashIconOutline className='icon delete' />
+          </button>
+        )}
+      </div>
+
+      <p>
+        {content.dateCreated
+          ? `${content.dateCreated.toLocaleString("en-US", {
+              month: "short",
+            })} ${content.dateCreated.getDate()}${
+              content.readtime ? ` - ${content.readtime} min read` : ""
+            }`
+          : ""}
+      </p>
+      {content.thumbnail && (
+        <div className='content-thumbnail-container'>
+          <Image
+            src={content.thumbnail}
+            alt='Thumbnail'
+            width={200}
+            height={200}
+            className='content-thumbnail'
+          />
+        </div>
+      )}
+
+      {!hideStats && (
+        <div className='content-stats'>
+          <span className='stat-item'>
+            {userUID && content.peopleWhoLiked?.includes(userUID) ? (
+              <HeartIconFilled className='stat-icon' />
+            ) : (
+              <HeartIconOutline className='stat-icon' />
+            )}
+            <p className='stat-number'>{content.likes || 0}</p>
+          </span>
+          <span className='stat-item'>
+            <EyeIconOutline className='stat-icon' />
+            <p className='stat-number'>{content.views || 0}</p>
+          </span>
+          <span className='stat-item'>
+            {userUID && content.sharedBy?.includes(userUID) ? (
+              <ShareIconFilled className='stat-icon' />
+            ) : (
+              <ShareIconOutline className='stat-icon' />
+            )}
+            <p className='stat-number'>{content.shares || 0}</p>
+          </span>
+          <span className='stat-item'>
+            {userUID && content.bookmarkedBy?.includes(userUID) ? (
+              <BookmarkIconFilled className='stat-icon' />
+            ) : (
+              <BookmarkIconOutline className='stat-icon' />
+            )}
+            <p className='stat-number'>{content.bookmarkedBy?.length || 0}</p>
+          </span>
+        </div>
+      )}
+    </div>
+  );
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (6)</summary><blockquote>

<details>
<summary>frontend/src/app/profile/[id]/page.tsx (1)</summary><blockquote>

`355-398`: **Remove commented-out code.**

The renderContentItem function has been replaced by ContentTile but left as commented code. Commented-out code should be removed to maintain a clean codebase.

```diff
-// function renderContentItem(content: Content, index: number) {
-//   return (
-//     <div
-//       key={content.id || index}
-//       className='content-list-item'
-//       onClick={() => router.push(`/content/${content.id}`)}
-//     >
-//       <h3 className='content-item-title'>{content.title}</h3>
-//       <p>
-//         {content.dateCreated
-//           ? `${content.dateCreated.toLocaleString("en-US", {
-//               month: "short",
-//             })} ${content.dateCreated.getDate()}${
-//               content.readtime ? ` - ${content.readtime} min read` : ""
-//             }`
-//           : ""}
-//       </p>
-//       {content.thumbnail && (
-//         <div className='content-thumbnail-container'>
-//           <Image
-//             src={content.thumbnail}
-//             alt='Thumbnail'
-//             width={200}
-//             height={200}
-//             className='content-thumbnail'
-//           />
-//         </div>
-//       )}
-//       {/* Unshare Button */}
-//       {sharedContent.some((sharedItem) => sharedItem.id === content.id) && (
-//         <button
-//           className='icon-button'
-//           onClick={(e) => {
-//             e.stopPropagation(); // Prevent content item click
-//             handleUnshare(content.id);
-//           }}
-//           title='Unshare Content'
-//         >
-//           <TrashIcon className='icon delete' />
-//         </button>
-//       )}
-//     </div>
-//   );
-// }
```

</blockquote></details>
<details>
<summary>frontend/src/components/content/ContentTile.tsx (1)</summary><blockquote>

`84-115`: **Optimize repeated user interaction checks**

The component performs similar checks for user interactions (likes, shares, bookmarks) in multiple places, which could be optimized.

Consider computing these interaction states once at the beginning of the component:

```diff
export default function ContentTile({
  content,
  index,
  hideStats = false,
  deleteShareOption = false,
  handleUnshare = () => {},
}: ContentTileProps) {
  const router = useRouter();
  const { userUID } = useAuth();
+  
+  // Pre-compute interaction states
+  const hasLiked = userUID && content.peopleWhoLiked?.includes(userUID);
+  const hasShared = userUID && content.sharedBy?.includes(userUID);
+  const hasBookmarked = userUID && content.bookmarkedBy?.includes(userUID);

  return (
    // ...
          <span className='stat-item'>
-            {userUID && content.peopleWhoLiked?.includes(userUID) ? (
+            {hasLiked ? (
              <HeartIconFilled className='stat-icon' />
            ) : (
              <HeartIconOutline className='stat-icon' />
            )}
            <p className='stat-number'>{content.likes || 0}</p>
          </span>
          // ... Apply similar changes to other interaction checks
```

</blockquote></details>
<details>
<summary>frontend/src/app/page.tsx (4)</summary><blockquote>

`27-62`: **Use independent loading states for better UX**

Currently, all content sections show the same loading state and only render when everything is loaded.

Consider using separate loading states for each content section to improve the user experience. This would allow sections to appear as they load:

```diff
- const [isLoading, setIsLoading] = useState(true);
+ const [isTrendingLoading, setIsTrendingLoading] = useState(true);
+ const [isLatestLoading, setIsLatestLoading] = useState(true);
+ const [isPersonalizedLoading, setIsPersonalizedLoading] = useState(true);

// Then update the fetch functions
async function fetchTrendingContent(): Promise<boolean> {
+  setIsTrendingLoading(true);
  try {
    // ...
  } catch (trendingError) {
    // ...
  }
+  setIsTrendingLoading(false);
  return false;
}

// And in the JSX
<h2>Top Trending</h2>
<div className='content-list'>
+  {isTrendingLoading ? <p>Loading trending content...</p> : 
    trendingContent.map((content, index) => (
      <ContentTile key={content.id} content={content} index={index} />
    ))
+  }
</div>
```

---

`90-90`: **Use optional chaining for cleaner code**

The current checks can be simplified using optional chaining.

Apply this change to all similar patterns:

```diff
- if (trendingResponse.data && trendingResponse.data.success) {
+ if (trendingResponse.data?.success) {
```


Also applies to: 109-109, 129-129

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 90-90: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`66-68`: **Extract common configuration values**

The timeout value of 5000ms is duplicated across all API calls.

Define a constant for API timeouts:

```diff
+ const API_TIMEOUT_MS = 5000;

async function fetchUser(): Promise<boolean> {
  try {
    const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
-     timeout: 5000,
+     timeout: API_TIMEOUT_MS,
    });
```


Also applies to: 85-88, 105-107, 124-127

---

`144-152`: **Add retry mechanism for failed fetches**

The error messages don't provide users a way to retry failed fetches.

Add a retry button:

```diff
- {errorTrending && <p className='error'>{errorTrending}</p>}
+ {errorTrending && (
+   <div className='error-container'>
+     <p className='error'>{errorTrending}</p>
+     <button 
+       className='retry-button'
+       onClick={() => fetchTrendingContent()}
+     >
+       Retry
+     </button>
+   </div>
+ )}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3b0be87b356c2f7a8077be2834f1ac3b503b43b7 and 2a1e9573a5ab4eaa2b88ed3f797f8294896bd167.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `frontend/src/app/page.tsx` (1 hunks)
* `frontend/src/app/profile/[id]/page.tsx` (19 hunks)
* `frontend/src/app/styles/colors.scss` (2 hunks)
* `frontend/src/app/styles/content/contentTile.scss` (1 hunks)
* `frontend/src/app/styles/feed.scss` (1 hunks)
* `frontend/src/app/styles/profile/profile.scss` (2 hunks)
* `frontend/src/components/content/ContentTile.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>frontend/src/app/page.tsx (4)</summary><blockquote>

<details>
<summary>frontend/src/models/Content.ts (1)</summary>

* `Content` (1-17)

</details>
<details>
<summary>frontend/src/hooks/AuthProvider.tsx (1)</summary>

* `useAuth` (103-105)

</details>
<details>
<summary>frontend/src/models/User.ts (1)</summary>

* `User` (1-22)

</details>
<details>
<summary>frontend/src/components/content/ContentTile.tsx (1)</summary>

* `ContentTile` (30-118)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>frontend/src/app/page.tsx</summary>

[error] 90-90: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 109-109: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 129-129: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (25)</summary><blockquote>

<details>
<summary>frontend/src/app/styles/feed.scss (1)</summary>

`1-5`: **Good implementation of a responsive grid layout.**

The content list grid with three columns provides a clean, modern layout. The 1rem gap ensures adequate spacing between items for good visual separation.

</details>
<details>
<summary>frontend/src/app/profile/[id]/page.tsx (6)</summary>

`10-15`: **LGTM: Improved import organization.**

The imports are now better organized using multi-line format, which improves readability.

---

`25-25`: **Good addition of ContentTile component.**

The ContentTile import enables more modular and reusable content rendering.

---

`47-49`: **LGTM: Better state declaration formatting.**

The multi-line format for the followUsernames state improves code clarity.

---

`207-267`: **Better organized handleFollow function.**

The improved organization with better comments and consistent indentation enhances readability.

---

`579-586`: **Good implementation of modular ContentTile component.**

The ContentTile component is properly implemented to replace the previous hardcoded render logic, improving code modularity and reusability.

---

`599-612`: **Well-implemented ContentTile with conditional props.**

Good use of the ContentTile component with appropriate conditional props for deletion functionality based on user permissions.

</details>
<details>
<summary>frontend/src/app/styles/profile/profile.scss (3)</summary>

`53-56`: **Good use of CSS variables for icon color.**

Replacing hardcoded colors with CSS variables improves maintainability and theme consistency.

---

`58-60`: **Good use of CSS variables for selected icon state.**

Using a variable for the selected icon state ensures consistent styling across components.

---

`83-85`: **LGTM: Improved text color with CSS variable.**

Using the new --text-color-light variable improves theme consistency.

</details>
<details>
<summary>frontend/src/app/styles/colors.scss (4)</summary>

`5-5`: **Good addition of text color variant.**

The --text-color-light variable enables more nuanced typography with appropriate contrast for secondary text.

---

`12-14`: **Good improvement to icon colors and states.**

The updated --icon-color and new --icon-selected-color variables provide better visual indication of interactive states.

---

`36-36`: **Appropriate dark mode text color variant.**

The dark mode --text-color-light has appropriate contrast while being visually distinct from the primary text color.

---

`43-45`: **Good dark mode icon color scheme.**

The icon colors for dark mode provide good contrast and visual hierarchy.

</details>
<details>
<summary>frontend/src/app/styles/content/contentTile.scss (6)</summary>

`1-6`: **Well-structured layout for content headers**

The flex layout with space-between justification is a good approach for positioning title and action elements.

---

`12-18`: **Good use of flex for statistics layout**

The centered alignment with substantial gap (50px) provides good spacing between stat items, enhancing readability.

---

`26-34`: **Excellent use of CSS variables for theming**

Using `var(--icon-color)` for the icon color makes the component more maintainable and theme-friendly.

---

`36-49`: **Well-defined container styles with proper spacing**

The content item styling has appropriate padding, border properties, and uses CSS variables for colors, making it themeable and visually consistent.

---

`62-77`: **Effective thumbnail hover animation**

The scale transformation on hover with a transition provides a polished user experience. The overflow handling in the container prevents the scaled image from breaking layout.

---

`79-103`: **Well-designed icon button styles with hover states**

The icon button implementation removes default button styling and creates a consistent design for all interaction icons. The specialized styles for delete actions appropriately use a different hover color (likely red) for destructive actions.

</details>
<details>
<summary>frontend/src/components/content/ContentTile.tsx (4)</summary>

`22-28`: **Clear and comprehensive props interface**

The props interface is well-defined with proper typing and optional properties clearly marked.

---

`30-39`: **Good use of default values for optional props**

The component properly handles optional props with sensible default values, including an empty function for `handleUnshare`.

---

`40-61`: **Effective event handling for the unshare button**

Using `e.stopPropagation()` in the unshare button's onClick handler correctly prevents triggering the container's navigation handler.

---

`63-82`: **Clean date formatting and conditional thumbnail rendering**

The date formatting is concise, and the conditionally rendered thumbnail using Next.js's optimized Image component is well implemented.

</details>
<details>
<summary>frontend/src/app/page.tsx (1)</summary>

`13-26`: **Well-organized state management**

The state variables are properly organized for content, loading state, user data, and error handling.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T20:56:12Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>frontend/src/app/page.tsx (3)</summary><blockquote>

`27-62`: **Consider optimizing fetch operations.**

The current implementation fetches data sequentially, which may not be optimal for performance. Consider using Promise.all() to fetch content in parallel:

```diff
-      let userFetched = await fetchUser();
-      let latestFetched = await fetchLatestContent();
-      let trendingFetched = await fetchTrendingContent();
-      let personalizedFetched = await fetchPersonalizedContent();
+      // Fetch user first since it might be needed for personalized content
+      let userFetched = await fetchUser();
+      
+      // Fetch other content in parallel
+      const [latestFetched, trendingFetched, personalizedFetched] = await Promise.all([
+        fetchLatestContent(),
+        fetchTrendingContent(),
+        fetchPersonalizedContent()
+      ]);
```

Also, consider adding a conditional check for userUID before fetching personalized content to avoid unnecessary API calls.

---

`143-168`: **Enhance UX for loading and empty states.**

The current implementation shows a single loading message for all content sections. Consider implementing:

1. More granular loading states for each content section.
2. Empty state messages when content arrays are empty.
3. A retry mechanism for failed fetches.

```diff
- {isLoading && <p>Loading...</p>}
+ {isLoading ? (
+   <div className="loading-container">
+     <p>Loading your content...</p>
+   </div>
+ ) : (
+   <>
+     {user && <h1>Welcome, {user.firstName}</h1>}
+     <h2>Top Trending</h2>
+     <div className='content-list'>
+       {trendingContent.length > 0 ? (
+         trendingContent.map((content, index) => (
+           <ContentTile key={content.id} content={content} index={index} />
+         ))
+       ) : (
+         <p className="empty-state">No trending content available</p>
+       )}
+     </div>
+     {errorTrending && (
+       <div className='error-container'>
+         <p className='error'>{errorTrending}</p>
+         <button onClick={() => fetchTrendingContent()}>Retry</button>
+       </div>
+     )}
+     
+     {/* Similar pattern for other content sections */}
+   </>
+ )}
```

---

`145-145`: **Remove redundant optional chaining.**

Since you're already checking if `user` exists with the `&&` operator, the optional chaining (`?.`) is redundant.

```diff
- {user && <h1>Welcome, {user?.firstName}</h1>}
+ {user && <h1>Welcome, {user.firstName}</h1>}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2a1e9573a5ab4eaa2b88ed3f797f8294896bd167 and 87ef83219ccc64e4fa95bda35c29ee729405fe6c.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `frontend/src/app/page.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>frontend/src/app/page.tsx (4)</summary><blockquote>

<details>
<summary>frontend/src/models/Content.ts (1)</summary>

* `Content` (1-17)

</details>
<details>
<summary>frontend/src/hooks/AuthProvider.tsx (1)</summary>

* `useAuth` (103-105)

</details>
<details>
<summary>frontend/src/models/User.ts (1)</summary>

* `User` (1-22)

</details>
<details>
<summary>frontend/src/components/content/ContentTile.tsx (1)</summary>

* `ContentTile` (30-118)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>frontend/src/app/page.tsx</summary>

[error] 90-90: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 109-109: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 129-129: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>frontend/src/app/page.tsx (3)</summary>

`13-25`: **Well-structured state management setup.**

The component has a good organization of state variables with appropriate types for managing content, loading state, user data, and error messages.

---

`83-101`: **Refactor duplicate fetch logic.**

There's significant duplication in structure and error handling across all fetch functions. Consider extracting the common patterns into a reusable function as suggested in the previous review.


Additionally, use optional chaining as suggested by static analysis to simplify the condition checks:

```diff
- if (trendingResponse.data && trendingResponse.data.success) {
+ if (trendingResponse.data?.success) {
```

```diff
- if (contentResponse.data && contentResponse.data.success) {
+ if (contentResponse.data?.success) {
```

```diff
- if (personalizedResponse.data && personalizedResponse.data.success) {
+ if (personalizedResponse.data?.success) {
```



Also applies to: 103-120, 122-139

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 90-90: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`3-11`: **Good import organization.**

The imports are well-organized and include all necessary dependencies for the component functionality.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T20:58:30Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>frontend/src/app/page.tsx (1)</summary><blockquote>

`87-105`: _🛠️ Refactor suggestion_

**Refactor duplicate fetch logic.**

The fetch functions have significant duplication in structure and error handling.



Extract the common patterns into a reusable function as suggested in the previous review:

```typescript
async function fetchData<T>(
  url: string, 
  setter: (data: T) => void, 
  defaultValue: T,
  successPath: string[] = ['success']
): Promise<boolean> {
  try {
    const response = await axios.get(url, { timeout: 5000 });
    
    // Navigate to the success flag using the provided path
    let data = response.data;
    let successFlag = true;
    for (const key of successPath) {
      if (data && typeof data === 'object' && key in data) {
        if (key === successPath[successPath.length - 1]) {
          successFlag = data[key];
        } else {
          data = data[key];
        }
      } else {
        successFlag = false;
        break;
      }
    }
    
    if (successFlag) {
      setter(data);
      return true;
    } else {
      setter(defaultValue);
    }
  } catch (error) {
    console.error("Error fetching data:", error);
    setter(defaultValue);
  }
  return false;
}

// Example usage:
async function fetchTrendingContent(): Promise<boolean> {
  return fetchData<Content[]>(
    `${apiURL}/content/feed/trending`,
    setTrendingContent,
    [],
    ['data', 'trendingContent']
  );
}
```


Also applies to: 107-124, 126-148

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>frontend/src/app/page.tsx (7)</summary><blockquote>

`27-62`: **useEffect implementation is solid but could be improved.**

The useEffect hook correctly manages the data fetching flow and dependencies. However, the sequential fetching of data could be optimized with Promise.all for parallel requests.


Consider using Promise.all to fetch data in parallel rather than sequentially:

```diff
- let userFetched = await fetchUser();
- let latestFetched = await fetchLatestContent();
- let trendingFetched = await fetchTrendingContent();
- let personalizedFetched = await fetchPersonalizedContent();
+ const [userFetched, latestFetched, trendingFetched, personalizedFetched] = await Promise.all([
+   fetchUser(),
+   fetchLatestContent(),
+   fetchTrendingContent(),
+   fetchPersonalizedContent()
+ ]);
```

This would improve performance by running requests concurrently rather than waiting for each one to complete before starting the next.

---

`94-95`: **Use optional chaining for nested object properties.**

When accessing nested properties in response objects, use optional chaining to prevent potential errors.


```diff
- if (trendingResponse.data && trendingResponse.data.success) {
+ if (trendingResponse.data?.success) {
    setTrendingContent(trendingResponse.data.trendingContent);
  
- if (contentResponse.data && contentResponse.data.success) {
+ if (contentResponse.data?.success) {
    setLatestContent(contentResponse.data.content);
  
- if (personalizedResponse.data && personalizedResponse.data.success) {
+ if (personalizedResponse.data?.success) {
    setPersonalizedContent(personalizedResponse.data.personalizedContent);
```


Also applies to: 113-114, 138-139

---

`100-101`: **Improve error logging.**

The catch blocks discard error information, making debugging more difficult.


```diff
  } catch (trendingError) {
+   console.error("Error fetching trending content:", trendingError);
    setTrendingContent([]);
  
  } catch (contentError) {
+   console.error("Error fetching latest content:", contentError);
    setLatestContent([]);
  
  } catch (personalizedError) {
+   console.error("Error fetching personalized content:", personalizedError);
    setPersonalizedContent([]);
```


Also applies to: 119-120, 144-145

---

`154-154`: **Remove redundant optional chaining.**

Since there's already a check for `user` with the logical AND operator, the optional chaining on `user?.firstName` is unnecessary.


```diff
- {user && <h1>Welcome, {user?.firstName}</h1>}
+ {user && <h1>Welcome, {user.firstName}</h1>}
```

---

`152-152`: **Enhance loading state visualization.**

The current loading indicator is minimal and doesn't provide context about what's loading.


Consider implementing a more descriptive loading state or skeleton UI:

```diff
- {isLoading && <p>Loading...</p>}
+ {isLoading && (
+   <div className="loading-container">
+     <div className="loading-spinner"></div>
+     <p>Loading your personalized content...</p>
+   </div>
+ )}
```

---

`156-160`: **Add empty state handling.**

The component doesn't handle the case when content arrays are empty but there's no error.


```diff
<div className='content-list'>
  {trendingContent.map((content, index) => (
    <ContentTile key={content.id} content={content} index={index} />
  ))}
+ {!isLoading && trendingContent.length === 0 && !errorTrending && (
+   <p className="empty-state">No trending content available</p>
+ )}
</div>

<div className='content-list'>
  {latestContent.map((content, index) => (
    <ContentTile key={content.id} content={content} index={index} />
  ))}
+ {!isLoading && latestContent.length === 0 && !errorLatest && (
+   <p className="empty-state">No latest content available</p>
+ )}
</div>

<div className='content-list'>
  {personalizedContent.map((content, index) => (
    <ContentTile key={content.id} content={content} index={index} />
  ))}
+ {!isLoading && personalizedContent.length === 0 && !errorPersonalized && (
+   <p className="empty-state">No personalized content available</p>
+ )}
</div>
```


Also applies to: 164-168, 172-176

---

`70-72`: **Make API timeout configurable.**

The timeout value of 5000ms is hardcoded in multiple places.


Extract the timeout to a constant for easier maintenance:

```diff
+ const API_TIMEOUT = 5000; // 5 seconds

// Then update all instances
- timeout: 5000,
+ timeout: API_TIMEOUT,
```


Also applies to: 90-92, 109-111, 133-136

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Lite**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 87ef83219ccc64e4fa95bda35c29ee729405fe6c and 41fd6ae3457a19619d799dc7a4af51a85caff8b5.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `frontend/src/app/page.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>frontend/src/app/page.tsx (4)</summary><blockquote>

<details>
<summary>frontend/src/models/Content.ts (1)</summary>

* `Content` (1-17)

</details>
<details>
<summary>frontend/src/hooks/AuthProvider.tsx (1)</summary>

* `useAuth` (103-105)

</details>
<details>
<summary>frontend/src/models/User.ts (1)</summary>

* `User` (1-22)

</details>
<details>
<summary>frontend/src/components/content/ContentTile.tsx (1)</summary>

* `ContentTile` (30-118)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>frontend/src/app/page.tsx (2)</summary>

`3-10`: **Import organization looks good.**

The imports are well-organized, grouped by functionality (hooks, models, external libraries, and components), and include all the necessary dependencies for the implemented features.

---

`13-25`: **State management is well-structured.**

The component properly separates concerns with distinct state variables for different content types, loading state, user data, and error messages. This approach makes the state management clear and maintainable.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T02:55:37Z
user: login: Mindful-Developer
body: Looks great!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T03:03:30Z

Review comments:
diff_hunk: @@ -390,27 +413,29 @@ export default function Page() {
   const followingCount = user?.following ? user.following.length : 0;
   const createdCount = user?.content ? user.content.length : 0;
   const sharedCount = user?.sharedContent ? user.sharedContent.length : 0;
-  const canViewFullProfile = !user?.isPrivate || userUID === id || (userUID && user?.followers?.includes(userUID));
+  const canViewFullProfile =
+    !user?.isPrivate ||
+    userUID === id ||
+    (userUID && user?.followers?.includes(userUID));
 
-    // Helper function to get username by ID (Simplified)
-    async function getUsername(userId: string): Promise<string> {
-      try {
-        const userResponse = await axios.get(`${apiURL}/user/${userId}`);
-        return userResponse.data.username || "Unknown User"; // Provide a fallback
-      } catch (error) {
-        console.error("Error fetching username:", error);
-        return "Unknown User"; // Fallback in case of error
-      }
-    }
-  
-  
-    if (isLoading) {
-      return <div>Loading profile...</div>;
-    }
-  
-    if (!user) {
-      return <div>User not found.</div>;
+  // Helper function to get username by ID (Simplified)
+  async function getUsername(userId: string): Promise<string> {
+    try {
+      const userResponse = await axios.get(`${apiURL}/user/${userId}`);
+      return userResponse.data.username || "Unknown User"; // Provide a fallback
+    } catch (error) {
+      console.error("Error fetching username:", error);
+      return "Unknown User"; // Fallback in case of error
     }
+  }
+
+  if (isLoading) {
+    return <div>Loading profile...</div>;
+  }
+
+  if (!user) {
+    return <div>User not found.</div>;
+  }
path: frontend/src/app/profile/[id]/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Remove duplicate loading and error checks.**

These checks for isLoading and user are duplicates of lines 400-406 and should be removed.

```diff
-  if (isLoading) {
-    return <div>Loading profile...</div>;
-  }
-
-  if (!user) {
-    return <div>User not found.</div>;
-  }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-25T20:56:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,171 @@
 "use client";
 
+import { useAuth } from "@/hooks/AuthProvider";
+import { Content } from "@/models/Content";
+import { useEffect, useState } from "react";
+import { apiURL } from "./scripts/api";
+import axios from "axios";
+import { HeartIcon } from "@heroicons/react/24/outline";
+import ContentTile from "@/components/content/ContentTile";
+import { User } from "@/models/User";
+
 export default function Page() {
+  const [trendingContent, setTrendingContent] = useState<Content[]>([]);
+  const [latestContent, setLatestContent] = useState<Content[]>([]);
+  const [personalizedContent, setPersonalizedContent] = useState<Content[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+
+  const { userUID } = useAuth();
+  const [user, setUser] = useState<User | null>(null);
+
+  const [errorTrending, setErrorTrending] = useState<string | null>(null);
+  const [errorLatest, setErrorLatest] = useState<string | null>(null);
+  const [errorPersonalized, setErrorPersonalized] = useState<string | null>(
+    null
+  );
+
+  useEffect(() => {
+    const fetchContent = async () => {
+      setIsLoading(true);
+
+      setErrorTrending(null);
+      setErrorLatest(null);
+      setErrorPersonalized(null);
+
+      let userFetched = await fetchUser();
+      let latestFetched = await fetchLatestContent();
+      let trendingFetched = await fetchTrendingContent();
+      let personalizedFetched = await fetchPersonalizedContent();
+
+      if (!latestFetched) {
+        setErrorLatest(
+          "Failed to fetch latest content. Please reload the page or contact support."
+        );
+      }
+
+      if (!trendingFetched) {
+        setErrorTrending(
+          "Failed to fetch trending content. Please reload the page or contact support."
+        );
+      }
+
+      if (!personalizedFetched) {
+        setErrorPersonalized(
+          "Failed to fetch personalized content. Please reload the page or contact support."
+        );
+      }
+
+      setIsLoading(false);
+    };
+
+    fetchContent();
+  }, [userUID]);
+
+  async function fetchUser(): Promise<boolean> {
+    try {
+      const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
+        timeout: 5000,
+      });
+
+      if (userResponse.status === 200 || userResponse.status === 201) {
+        setUser(userResponse.data);
+        return true;
+      } else {
+        setUser(null);
+      }
+    } catch (userError) {
+      setUser(null);
+    }
+
+    return false;
+  }
+
+  async function fetchTrendingContent(): Promise<boolean> {
+    try {
+      const trendingResponse = await axios.get(
+        `${apiURL}/content/feed/trending`,
+        { timeout: 5000 }
+      );
+
+      if (trendingResponse.data && trendingResponse.data.success) {
+        setTrendingContent(trendingResponse.data.trendingContent);
+        return true;
+      } else {
+        setTrendingContent([]);
+      }
+    } catch (trendingError) {
+      setTrendingContent([]);
+    }
+
+    return false;
+  }
+
+  async function fetchLatestContent(): Promise<boolean> {
+    try {
+      const contentResponse = await axios.get(`${apiURL}/content`, {
+        timeout: 5000,
+      });
+
+      if (contentResponse.data && contentResponse.data.success) {
+        setLatestContent(contentResponse.data.content);
+        return true;
+      } else {
+        setLatestContent([]);
+      }
+    } catch (contentError) {
+      setLatestContent([]);
+    }
+
+    return false;
+  }
+
+  async function fetchPersonalizedContent(): Promise<boolean> {
+    try {
+      const personalizedResponse = await axios.get(
+        `${apiURL}/content/feed/personalized`,
+        { timeout: 5000 }
+      );
+
+      if (personalizedResponse.data && personalizedResponse.data.success) {
+        setPersonalizedContent(personalizedResponse.data.personalizedContent);
+        return true;
+      } else {
+        setPersonalizedContent([]);
+      }
+    } catch (personalizedError) {
+      setPersonalizedContent([]);
+    }
+    return false;
+  }
path: frontend/src/app/page.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Refactor duplicate fetch logic**

The fetch functions have significant duplication in structure and error handling.

Extract the common patterns into a reusable function:

```typescript
async function fetchData<T>(
  url: string, 
  setter: (data: T) => void, 
  defaultValue: T,
  successPath: string[] = ['success']
): Promise<boolean> {
  try {
    const response = await axios.get(url, { timeout: 5000 });
    
    // Navigate to the success flag using the provided path
    let data = response.data;
    let successFlag = true;
    for (const key of successPath) {
      if (data && typeof data === 'object' && key in data) {
        if (key === successPath[successPath.length - 1]) {
          successFlag = data[key];
        } else {
          data = data[key];
        }
      } else {
        successFlag = false;
        break;
      }
    }
    
    if (successFlag) {
      setter(data);
      return true;
    } else {
      setter(defaultValue);
    }
  } catch (error) {
    setter(defaultValue);
  }
  return false;
}

// Example usage:
async function fetchTrendingContent(): Promise<boolean> {
  return fetchData<Content[]>(
    `${apiURL}/content/feed/trending`,
    setTrendingContent,
    [],
    ['data', 'trendingContent']
  );
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 90-90: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 109-109: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 129-129: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-25T20:56:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,171 @@
 "use client";
 
+import { useAuth } from "@/hooks/AuthProvider";
+import { Content } from "@/models/Content";
+import { useEffect, useState } from "react";
+import { apiURL } from "./scripts/api";
+import axios from "axios";
+import { HeartIcon } from "@heroicons/react/24/outline";
+import ContentTile from "@/components/content/ContentTile";
+import { User } from "@/models/User";
+
 export default function Page() {
+  const [trendingContent, setTrendingContent] = useState<Content[]>([]);
+  const [latestContent, setLatestContent] = useState<Content[]>([]);
+  const [personalizedContent, setPersonalizedContent] = useState<Content[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+
+  const { userUID } = useAuth();
+  const [user, setUser] = useState<User | null>(null);
+
+  const [errorTrending, setErrorTrending] = useState<string | null>(null);
+  const [errorLatest, setErrorLatest] = useState<string | null>(null);
+  const [errorPersonalized, setErrorPersonalized] = useState<string | null>(
+    null
+  );
+
+  useEffect(() => {
+    const fetchContent = async () => {
+      setIsLoading(true);
+
+      setErrorTrending(null);
+      setErrorLatest(null);
+      setErrorPersonalized(null);
+
+      let userFetched = await fetchUser();
+      let latestFetched = await fetchLatestContent();
+      let trendingFetched = await fetchTrendingContent();
+      let personalizedFetched = await fetchPersonalizedContent();
+
+      if (!latestFetched) {
+        setErrorLatest(
+          "Failed to fetch latest content. Please reload the page or contact support."
+        );
+      }
+
+      if (!trendingFetched) {
+        setErrorTrending(
+          "Failed to fetch trending content. Please reload the page or contact support."
+        );
+      }
+
+      if (!personalizedFetched) {
+        setErrorPersonalized(
+          "Failed to fetch personalized content. Please reload the page or contact support."
+        );
+      }
+
+      setIsLoading(false);
+    };
+
+    fetchContent();
+  }, [userUID]);
+
+  async function fetchUser(): Promise<boolean> {
+    try {
+      const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
+        timeout: 5000,
+      });
+
+      if (userResponse.status === 200 || userResponse.status === 201) {
+        setUser(userResponse.data);
+        return true;
+      } else {
+        setUser(null);
+      }
+    } catch (userError) {
+      setUser(null);
+    }
+
+    return false;
+  }
path: frontend/src/app/page.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add safeguard against undefined userUID.**

The function attempts to fetch user data without checking if userUID is defined. If userUID is null/undefined, the API call will fail unnecessarily.

```diff
async function fetchUser(): Promise<boolean> {
+  if (!userUID) {
+    setUser(null);
+    return false;
+  }
  
  try {
    const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
      timeout: 5000,
    });
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  async function fetchUser(): Promise<boolean> {
    if (!userUID) {
      setUser(null);
      return false;
    }
    try {
      const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
        timeout: 5000,
      });

      if (userResponse.status === 200 || userResponse.status === 201) {
        setUser(userResponse.data);
        return true;
      } else {
        setUser(null);
      }
    } catch (userError) {
      setUser(null);
    }

    return false;
  }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-25T20:58:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,171 @@
 "use client";
 
+import { useAuth } from "@/hooks/AuthProvider";
+import { Content } from "@/models/Content";
+import { useEffect, useState } from "react";
+import { apiURL } from "./scripts/api";
+import axios from "axios";
+import { HeartIcon } from "@heroicons/react/24/outline";
+import ContentTile from "@/components/content/ContentTile";
+import { User } from "@/models/User";
+
 export default function Page() {
+  const [trendingContent, setTrendingContent] = useState<Content[]>([]);
+  const [latestContent, setLatestContent] = useState<Content[]>([]);
+  const [personalizedContent, setPersonalizedContent] = useState<Content[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+
+  const { userUID } = useAuth();
+  const [user, setUser] = useState<User | null>(null);
+
+  const [errorTrending, setErrorTrending] = useState<string | null>(null);
+  const [errorLatest, setErrorLatest] = useState<string | null>(null);
+  const [errorPersonalized, setErrorPersonalized] = useState<string | null>(
+    null
+  );
+
+  useEffect(() => {
+    const fetchContent = async () => {
+      setIsLoading(true);
+
+      setErrorTrending(null);
+      setErrorLatest(null);
+      setErrorPersonalized(null);
+
+      let userFetched = await fetchUser();
+      let latestFetched = await fetchLatestContent();
+      let trendingFetched = await fetchTrendingContent();
+      let personalizedFetched = await fetchPersonalizedContent();
+
+      if (!latestFetched) {
+        setErrorLatest(
+          "Failed to fetch latest content. Please reload the page or contact support."
+        );
+      }
+
+      if (!trendingFetched) {
+        setErrorTrending(
+          "Failed to fetch trending content. Please reload the page or contact support."
+        );
+      }
+
+      if (!personalizedFetched) {
+        setErrorPersonalized(
+          "Failed to fetch personalized content. Please reload the page or contact support."
+        );
+      }
+
+      setIsLoading(false);
+    };
+
+    fetchContent();
+  }, [userUID]);
+
+  async function fetchUser(): Promise<boolean> {
+    try {
+      const userResponse = await axios.get(`${apiURL}/user/${userUID}`, {
+        timeout: 5000,
+      });
+
+      if (userResponse.status === 200 || userResponse.status === 201) {
+        setUser(userResponse.data);
+        return true;
+      } else {
+        setUser(null);
+      }
+    } catch (userError) {
+      setUser(null);
+    }
+
+    return false;
+  }
+
+  async function fetchTrendingContent(): Promise<boolean> {
+    try {
+      const trendingResponse = await axios.get(
+        `${apiURL}/content/feed/trending`,
+        { timeout: 5000 }
+      );
+
+      if (trendingResponse.data && trendingResponse.data.success) {
+        setTrendingContent(trendingResponse.data.trendingContent);
+        return true;
+      } else {
+        setTrendingContent([]);
+      }
+    } catch (trendingError) {
+      setTrendingContent([]);
+    }
+
+    return false;
+  }
+
+  async function fetchLatestContent(): Promise<boolean> {
+    try {
+      const contentResponse = await axios.get(`${apiURL}/content`, {
+        timeout: 5000,
+      });
+
+      if (contentResponse.data && contentResponse.data.success) {
+        setLatestContent(contentResponse.data.content);
+        return true;
+      } else {
+        setLatestContent([]);
+      }
+    } catch (contentError) {
+      setLatestContent([]);
+    }
+
+    return false;
+  }
+
+  async function fetchPersonalizedContent(): Promise<boolean> {
+    try {
+      const personalizedResponse = await axios.get(
+        `${apiURL}/content/feed/${userUID}`,
+        { timeout: 5000 }
+      );
+
+      if (personalizedResponse.data && personalizedResponse.data.success) {
+        setPersonalizedContent(personalizedResponse.data.personalizedContent);
+        return true;
+      } else {
+        setPersonalizedContent([]);
+      }
+    } catch (personalizedError) {
+      setPersonalizedContent([]);
+    }
+    return false;
+  }
path: frontend/src/app/page.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add null check for userUID in personalized content fetch.**

The function attempts to fetch personalized content using the userUID without checking if it's defined.

```diff
async function fetchPersonalizedContent(): Promise<boolean> {
+  if (!userUID) {
+    setPersonalizedContent([]);
+    return false;
+  }
  
  try {
    const personalizedResponse = await axios.get(
      `${apiURL}/content/feed/${userUID}`,
      { timeout: 5000 }
    );
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  async function fetchPersonalizedContent(): Promise<boolean> {
    if (!userUID) {
      setPersonalizedContent([]);
      return false;
    }
  
    try {
      const personalizedResponse = await axios.get(
        `${apiURL}/content/feed/${userUID}`,
        { timeout: 5000 }
      );
  
      if (personalizedResponse.data && personalizedResponse.data.success) {
        setPersonalizedContent(personalizedResponse.data.personalizedContent);
        return true;
      } else {
        setPersonalizedContent([]);
      }
    } catch (personalizedError) {
      setPersonalizedContent([]);
    }
    return false;
  }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 129-129: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-25T20:58:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-25T20:50:44Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces dynamic content fetching and enhanced interactivity in the application’s main Page component. It includes new state variables, asynchronous API calls, and error handling for fetching trending, latest, and personalized data based on user authentication. Additionally, the pull request adds a reusable ContentTile component for modular content rendering and adjusts various SCSS files to improve styling consistency, grid layouts, and cleanup of unused styles, thereby refining the overall UI and code organization.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `frontend/src/app/page.tsx` | Added multiple state variables and asynchronous functions (`fetchUser`, `fetchTrendingContent`, etc.) to dynamically fetch and render content with loading and error handling. |
| `frontend/src/app/profile/[id]/page.tsx` | Reformatted code for clarity, added an import for `ContentTile`, and improved content rendering modularity. |
| `frontend/src/app/styles/colors.scss`, `frontend/src/app/styles/feed.scss`, `frontend/src/app/styles/profile/profile.scss` | Updated and added CSS variables for text and icon colors; introduced a new grid layout class; removed unused styles to utilize CSS variables for color definitions. |
| `frontend/src/app/styles/content/contentTile.scss` | Introduced new SCSS styles for content tile components including header layout, hover effects, and responsive design for images and icons. |
| `frontend/src/components/content/ContentTile.tsx` | Added a new React component that encapsulates content display logic, navigation handling, and interactive features like an “Unshare” button and statistics display. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant P as Page Component
    participant Auth as useAuth Hook
    participant API as API Server
    Note over P: UserUID update triggers content fetch
    P->>Auth: Retrieve userUID
    alt userUID exists
        P->>P: Trigger useEffect
        P->>API: Call fetchUser()
        API-->>P: Return user data
        P->>API: Call fetchTrendingContent()
        API-->>P: Return trending content
        P->>API: Call fetchLatestContent()
        API-->>P: Return latest content
        P->>API: Call fetchPersonalizedContent()
        API-->>P: Return personalized content
        P->>P: Update state and render UI
    else No userUID
        P->>P: Display loading or error state
    end
```

```mermaid
sequenceDiagram
    participant CT as ContentTile
    participant R as useRouter
    participant U as User
    U->>CT: Click on content tile
    CT->>R: Navigate to detailed view (push URL)
```

## Possibly related PRs
- **Turing-Sandbox/COMP313-402-Team3-W25#12**: Involves modifications in content statistics management with additional state variables and API functions similar to the changes here.
- **Turing-Sandbox/COMP313-402-Team3-W25#24**: Focuses on restructuring the Page component’s logic for user and content data fetching, directly aligning with the enhancements in this PR.
- **Turing-Sandbox/COMP313-402-Team3-W25#52**: Introduces new state variables and functions for managing user-specific data, particularly in content fetching and follow requests, which are mirrored in the current changes.

## Poem
> I'm a happy rabbit, tiptoeing by the code,  
> Hoping through state variables in my vibrant abode.  
> Async paths lead me to fetch data so bright,  
> With ContentTile guiding me like a beam of light.  
> CSS colors and grids make my world complete—  
> Hoppy rabbit cheers in every committed beat!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Turing-Sandbox/COMP313-402-Team3-W25&utm_content=66):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: netlify[bot]
created_at: 2025-03-25T20:50:50Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *summarizz-app* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 41fd6ae3457a19619d799dc7a4af51a85caff8b5 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/summarizz-app/deploys/67e36c34cf29db0008145b00 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-66--summarizz-app.netlify.app](https://deploy-preview-66--summarizz-app.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTY2LS1zdW1tYXJpenotYXBwLm5ldGxpZnkuYXBwIn0.INOoTcU_8n0VnPaEq46nh-sZ-9yzgLFGV788kpE5Q5Q)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [summarizz-app Preview](https://deploy-preview-66--summarizz-app.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/summarizz-app/configuration/notifications#deploy-webhooks)._
author_association: NONE