Repository: open-sauced/insights
Pull request #1524 (2023-08-08 19:24:15)

chore: release 1.59.0


Patch:
diff --git a/.env b/.env
index 940cbc57..cef85d2f 100644
--- a/.env
+++ b/.env
@@ -5,4 +5,4 @@ NEXT_PUBLIC_POSTHOG_ID=phc_Y0xz6nK55MEwWjobJsI2P8rsiomZJ6eZLoXehmMy9tt
 NEXT_PUBLIC_CLOUD_NAME=dgxgziswe
 NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=test
 NEXT_PUBLIC_OPENGRAPH_URL=https://beta.opengraph.opensauced.pizza/v1
-NEXT_PUBLIC_SENTRY_DSN=https://3f5bb9023ff0407299dd22a6454558f9@o4504872488927232.ingest.sentry.io/4505082236960768
+SENTRY_DSN=https://3f5bb9023ff0407299dd22a6454558f9@o4504872488927232.ingest.sentry.io/4505082236960768
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c083d3d0..4576773a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -89,10 +89,18 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 10
     steps:
+      - name: Generate token
+        id: generate_token
+        uses: tibdex/github-app-token@v1
+        with:
+          app_id: ${{ secrets.OS_GITHUB_APP_ID }}
+          private_key: ${{ secrets.OS_GITHUB_APP_PRIVATE_KEY }}
+
       - name: "☁️ checkout repository"
         uses: actions/checkout@v2
         with:
           fetch-depth: 0
+          token: ${{ steps.generate_token.outputs.token }}
 
       - name: "📂 download build artifacts"
         uses: actions/download-artifact@v2
@@ -104,7 +112,7 @@ jobs:
         id: semantic-release
         uses: open-sauced/release@v1
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
           HUSKY: 0
 
   cleanup:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4ef9d3d..91b556b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,90 @@
 
 > All notable changes to this project will be documented in this file
 
+## [1.59.0-beta.9](https://github.com/open-sauced/insights/compare/v1.59.0-beta.8...v1.59.0-beta.9) (2023-08-08)
+
+
+### 🐛 Bug Fixes
+
+* update environment variable for Sentry ([#1521](https://github.com/open-sauced/insights/issues/1521)) ([56ac14b](https://github.com/open-sauced/insights/commit/56ac14b6261eeb9cac440930ff581d392c6e4e5e))
+
+## [1.59.0-beta.8](https://github.com/open-sauced/insights/compare/v1.59.0-beta.7...v1.59.0-beta.8) (2023-08-08)
+
+
+### 🍕 Features
+
+* add support for highlight.new ([#1487](https://github.com/open-sauced/insights/issues/1487)) ([3daa5c0](https://github.com/open-sauced/insights/commit/3daa5c0a0963f2dd4c8facd8e309cc66952f69d7))
+* improved the UX on the feeds page and scroll behaviour ([#1506](https://github.com/open-sauced/insights/issues/1506)) ([31c1593](https://github.com/open-sauced/insights/commit/31c15937e196cb2eab44f81b60682e5d30931334))
+
+## [1.59.0-beta.7](https://github.com/open-sauced/insights/compare/v1.59.0-beta.6...v1.59.0-beta.7) (2023-08-08)
+
+
+### 🐛 Bug Fixes
+
+* on page reload Insights page redirecting to Dashboard ([#1517](https://github.com/open-sauced/insights/issues/1517)) ([397c36e](https://github.com/open-sauced/insights/commit/397c36eab096f5a43ef09a8c528f13ada5cc4940))
+
+
+### 🍕 Features
+
+* add a user notifications page ([#1478](https://github.com/open-sauced/insights/issues/1478)) ([022dc69](https://github.com/open-sauced/insights/commit/022dc695067049874a4ae1f8fcdd57a21a266220))
+
+## [1.59.0-beta.6](https://github.com/open-sauced/insights/compare/v1.59.0-beta.5...v1.59.0-beta.6) (2023-08-07)
+
+
+### 🍕 Features
+
+* add github link to profile ([#1459](https://github.com/open-sauced/insights/issues/1459)) ([d42bc6d](https://github.com/open-sauced/insights/commit/d42bc6d031eb4e3dd656f759adc500049b6ccf52))
+
+## [1.59.0-beta.5](https://github.com/open-sauced/insights/compare/v1.59.0-beta.4...v1.59.0-beta.5) (2023-08-07)
+
+
+### 🐛 Bug Fixes
+
+* Feed page responsiveness  ([#1490](https://github.com/open-sauced/insights/issues/1490)) ([67662b2](https://github.com/open-sauced/insights/commit/67662b224d22898d97e6f8cb7a67e6a9ca13c6f9))
+
+
+### 🍕 Features
+
+* onboarding auto fetch timezone ([#1488](https://github.com/open-sauced/insights/issues/1488)) ([ae5cdd7](https://github.com/open-sauced/insights/commit/ae5cdd7e2a9ee18798db1da051cacf41139b85e8))
+
+## [1.59.0-beta.4](https://github.com/open-sauced/insights/compare/v1.59.0-beta.3...v1.59.0-beta.4) (2023-08-04)
+
+
+### 🐛 Bug Fixes
+
+* uses session to update user info for notifications check ([#1486](https://github.com/open-sauced/insights/issues/1486)) ([60d787e](https://github.com/open-sauced/insights/commit/60d787ef4c9a6867ab0159c5576fed429f9a48c6))
+
+## [1.59.0-beta.3](https://github.com/open-sauced/insights/compare/v1.59.0-beta.2...v1.59.0-beta.3) (2023-08-04)
+
+
+### 🧑‍💻 Code Refactoring
+
+* replace supabase/ui in design system typography components ([#1438](https://github.com/open-sauced/insights/issues/1438)) ([38cfb30](https://github.com/open-sauced/insights/commit/38cfb305f3abad32ffbac7acff3dbb9b08498b4e))
+
+
+### 🐛 Bug Fixes
+
+* update release workflow to use GitHub app for semantic versioning ([#1484](https://github.com/open-sauced/insights/issues/1484)) ([3f1ce84](https://github.com/open-sauced/insights/commit/3f1ce845790e7d6e9bb522756d24346319384ee1))
+
+## [1.59.0-beta.2](https://github.com/open-sauced/insights/compare/v1.59.0-beta.1...v1.59.0-beta.2) (2023-08-04)
+
+
+### 🐛 Bug Fixes
+
+* feeds page typography and styles improvements ([#1467](https://github.com/open-sauced/insights/issues/1467)) ([a3b289e](https://github.com/open-sauced/insights/commit/a3b289e97300a6225c002d87d19a882ee09323ff))
+
+## [1.59.0-beta.1](https://github.com/open-sauced/insights/compare/v1.58.0...v1.59.0-beta.1) (2023-08-03)
+
+
+### 🐛 Bug Fixes
+
+* update user interest logo error for machine learning ([#1474](https://github.com/open-sauced/insights/issues/1474)) ([a286eda](https://github.com/open-sauced/insights/commit/a286eda7478e1ba03898a1086e0d86e9eb18f692))
+
+
+### 🍕 Features
+
+* add conditional routing to single highlight dialog close action ([#1473](https://github.com/open-sauced/insights/issues/1473)) ([1341cba](https://github.com/open-sauced/insights/commit/1341cba30eea245fe2755f072e0bd49c33b84667))
+
 ## [1.58.0](https://github.com/open-sauced/insights/compare/v1.57.0...v1.58.0) (2023-08-02)
 
 
diff --git a/components/atoms/LanguagePill/LanguagePill.tsx b/components/atoms/LanguagePill/LanguagePill.tsx
index 9c542288..166dc874 100644
--- a/components/atoms/LanguagePill/LanguagePill.tsx
+++ b/components/atoms/LanguagePill/LanguagePill.tsx
@@ -4,7 +4,7 @@ import JavascriptIcon from "img/icons/interests/javascript.svg";
 import ReactIcon from "/img/icons/interests/react.svg";
 import PythonIcon from "/img/icons/interests/python.svg";
 import AIIcon from "/img/icons/interests/ai.svg";
-import MLIcon from "/img/icons//interests/machine-learning.svg";
+import MLIcon from "/img/icons/interests/machine-learning.svg";
 import RustIcon from "/img/icons/interests/rust.svg";
 import PhpIcon from "/img/icons/interests/php.svg";
 import CsharpIcon from "/img/icons/interests/c-sharp.svg";
diff --git a/components/atoms/NotificationsCard/notification-card.tsx b/components/atoms/NotificationsCard/notification-card.tsx
index 4324dcfd..3272248b 100644
--- a/components/atoms/NotificationsCard/notification-card.tsx
+++ b/components/atoms/NotificationsCard/notification-card.tsx
@@ -2,21 +2,14 @@ import Link from "next/link";
 import React from "react";
 import { FaRegSmile, FaUserCircle } from "react-icons/fa";
 
+import { getNotificationURL } from "lib/utils/get-notification-url";
+
 interface NotificationCard {
   type: "highlight_reaction" | "follow" | "collaboration";
   message: string;
   id: string;
 }
 
-const getSourceURL = (type: string, id: string) => {
-  switch (type) {
-    case "highlight_reaction":
-      return `/feed/${id}`;
-    case "follow":
-      return `/user/${id}`;
-  }
-};
-
 const NotificationCard = ({ type, message, id }: NotificationCard) => {
   const Icons = {
     highlight_reaction: FaRegSmile,
@@ -25,7 +18,7 @@ const NotificationCard = ({ type, message, id }: NotificationCard) => {
   };
 
   const Icon = Icons[type];
-  const linkURL = getSourceURL(type, id);
+  const linkURL = getNotificationURL(type, id);
 
   return (
     <div className="flex items-center gap-3 p-2 transition cursor-pointer rounded-xl group hover:text-sauced-orange hover:bg-sauced-light">
diff --git a/components/atoms/TopContributorCard/top-contributor-card.tsx b/components/atoms/TopContributorCard/top-contributor-card.tsx
index d6fded5a..c74a9a16 100644
--- a/components/atoms/TopContributorCard/top-contributor-card.tsx
+++ b/components/atoms/TopContributorCard/top-contributor-card.tsx
@@ -44,7 +44,7 @@ const TopContributorCard = ({ login }: TopContributorCardProps) => {
       <Link href={`/user/${login}`}>
         <div className="flex items-center gap-2">
           <Avatar isCircle size={35} avatarURL={getAvatarByUsername(login)} />
-          <p className="font-semibold text-light-slate-12">{login}</p>
+          <p className="text-sm font-semibold text-light-slate-12">{login}</p>
         </div>
       </Link>
       {sessionToken && !notFollowing ? (
diff --git a/components/atoms/Typography/text.tsx b/components/atoms/Typography/text.tsx
index 7c22860c..26ed31a3 100644
--- a/components/atoms/Typography/text.tsx
+++ b/components/atoms/Typography/text.tsx
@@ -1,18 +1,70 @@
+/* eslint-disable prettier/prettier */
 import React from "react";
-import { Typography } from "@supabase/ui";
+import clsx from "clsx";
 
-const { Text: SupabaseTextComponent } = Typography;
+import TypographyWrapper from "./wrapper";
 
-interface TextProps extends React.ComponentProps<typeof SupabaseTextComponent> {
-  //Add additional prop definitions here
+interface TextProps {
+  children: React.ReactNode;
   title?: string;
+  className?: string;
+  type?: "default" | "secondary" | "success" | "warning" | "danger";
+  disabled?: boolean;
+  mark?: boolean;
+  code?: boolean;
+  keyboard?: boolean;
+  underline?: boolean;
+  strikethrough?: boolean;
+  strong?: boolean;
+  small?: boolean;
 }
 
-const Text: React.FC<TextProps> = (props) => {
+const Text: React.FC<TextProps> = ({
+  children,
+  title,
+  className,
+  type = "default",
+  disabled,
+  mark,
+  code,
+  keyboard,
+  underline,
+  strikethrough,
+  strong,
+  small,
+  ...props
+}) => {
+  const TextTag = (() => {
+    if (mark) return "mark";
+    if (code) return "code";
+    if (keyboard) return "kbd";
+    if (strong) return "strong";
+    if (small) return "small";
+    return "span";
+  })() as keyof JSX.IntrinsicElements;
+
+  const getTypeClass = (type: string) => {
+    if (type === "secondary") return "!text-light-slate-5";
+    if (type === "success") return "!text-dark-grass-10";
+    if (type === "warning") return "!text-yellow-500";
+    if (type === "danger") return "!text-dark-red-10";
+    return "";
+  };
+
   return (
-    <SupabaseTextComponent className={`${props.className && props.className}`} {...props}>
-      <span title={props.title}>{props.children}</span>
-    </SupabaseTextComponent>
+    <TypographyWrapper
+      className={clsx(
+        className,
+        disabled && "cursor-not-allowed select-none text-light-slate-11/50",
+        keyboard && "bg-light-slate-4/50 rounded px-2 border border-b-2 border-light-slate-8/50",
+        underline && "underline",
+        strikethrough && "line-through",
+        getTypeClass(type)
+      )}
+      {...props}
+    >
+      <TextTag title={title}>{children}</TextTag>
+    </TypographyWrapper>
   );
 };
 
diff --git a/components/atoms/Typography/title.tsx b/components/atoms/Typography/title.tsx
index 06184082..0f32ca61 100644
--- a/components/atoms/Typography/title.tsx
+++ b/components/atoms/Typography/title.tsx
@@ -1,17 +1,25 @@
+/* eslint-disable prettier/prettier */
 import React from "react";
-import { Typography } from "@supabase/ui";
+import clsx from "clsx";
 
-const { Title: SupabaseTitleComponent } = Typography;
+import TypographyWrapper from "./wrapper";
 
-interface TitleProps extends React.ComponentProps<typeof SupabaseTitleComponent> {
-  //Add additional prop definitions here
+interface TitleProps {
+  children: React.ReactNode;
+  level?: 1 | 2 | 3 | 4 | 5;
+  weight?: "thin" | "extralight" | "light" | "normal" | "medium" | "semibold" | "bold" | "extrabold" | "black";
+  className?: string;
 }
 
-const Title: React.FC<TitleProps> = (props) => {
+const Title: React.FC<TitleProps> = ({ children, level = 1, weight = "medium", className, ...props }) => {
+  const TitleTag = `h${level}` as keyof JSX.IntrinsicElements;
+
   return (
-    <SupabaseTitleComponent className={`${props.className && props.className}`} {...props}>
-      {props.children}
-    </SupabaseTitleComponent>
+    <TypographyWrapper>
+      <TitleTag className={clsx(className, `font-${weight}`)} {...props}>
+        {children}
+      </TitleTag>
+    </TypographyWrapper>
   );
 };
 
diff --git a/components/atoms/Typography/wrapper.tsx b/components/atoms/Typography/wrapper.tsx
new file mode 100644
index 00000000..06501633
--- /dev/null
+++ b/components/atoms/Typography/wrapper.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+import clsx from "clsx";
+
+interface TypographyWrapperProps {
+  children: React.ReactNode;
+  className?: string;
+  size?: "sm" | "base" | "lg" | "xl" | "2xl";
+}
+
+const TypographyWrapper: React.FC<TypographyWrapperProps> = ({ children, className, size = "sm", ...props }) => {
+  return (
+    <span className={clsx("prose", `prose-${size}`, className)} {...props}>
+      {children}
+    </span>
+  );
+};
+
+export default TypographyWrapper;
diff --git a/components/atoms/UserCard/user-card.tsx b/components/atoms/UserCard/user-card.tsx
index 04e873ee..3d1eb60a 100644
--- a/components/atoms/UserCard/user-card.tsx
+++ b/components/atoms/UserCard/user-card.tsx
@@ -18,13 +18,13 @@ const UserCard = ({ username, name, meta, loading }: UserCardProps) => {
   const avatarUrl = getAvatarByUsername(username);
 
   return (
-    <div className="pb-6 border bg-light-slate-1 w-max rounded-2xl border-zinc-200">
+    <div className="pb-6 border bg-light-slate-1 w-max rounded-xl border-zinc-200">
       {loading ? (
         <div className="flex items-center justify-center h-32 w-72">
           <Spinner className="mt-6 " />
         </div>
       ) : (
-        <div className="flex flex-col items-center gap-6 px-6 ">
+        <div className="flex flex-col items-center gap-6 px-9">
           <div className="flex flex-col items-center gap-2 -mt-10">
             <Image
               className="border border-white rounded-full "
@@ -34,14 +34,14 @@ const UserCard = ({ username, name, meta, loading }: UserCardProps) => {
               alt={`${username}'s avatar image`}
             />
             <div className="text-center">
-              <h3>{name}</h3>
-              <Link className="text-light-slate-9" href={`/user/${username}`}>{`@${username}`}</Link>
+              <h3 className="text-lg ">{name}</h3>
+              <Link className="text-lg text-slate-700" href={`/user/${username}`}>{`@${username}`}</Link>
             </div>
           </div>
           <div className="flex items-center gap-5 text-base text-center ">
             {meta.map(({ name, count }, i) => (
               <div key={i.toLocaleString()}>
-                <p className="text-center text-light-slate-9">{name}</p>
+                <p className="text-xs text-center text-light-slate-9">{name}</p>
                 {count > 0 ? count : "-"}
               </div>
             ))}
diff --git a/components/molecules/AnnouncementCard/announcement-card.tsx b/components/molecules/AnnouncementCard/announcement-card.tsx
index 16c4de35..d0b94c3e 100644
--- a/components/molecules/AnnouncementCard/announcement-card.tsx
+++ b/components/molecules/AnnouncementCard/announcement-card.tsx
@@ -9,7 +9,7 @@ interface AnnouncementCardProps {
 }
 const AnnouncementCard = ({ title, description, bannerSrc, url }: AnnouncementCardProps) => {
   return (
-    <div className="overflow-hidden border bg-light-slate-1 rounded-xl">
+    <div className="overflow-hidden border max-w-xs  rounded-lg bg-light-slate-1">
       <div className="w-full p-0.5 ">
         <AspectRatio.Root ratio={1.85 / 1}>
           <picture>
@@ -18,8 +18,8 @@ const AnnouncementCard = ({ title, description, bannerSrc, url }: AnnouncementCa
         </AspectRatio.Root>
       </div>
       <div className="flex flex-col gap-3 p-5">
-        <h1>{title}</h1>
-        <p className="font-light">{description}</p>
+        <h1 className="text-lg">{title}</h1>
+        <p className="text-sm font-normal text-light-slate-11">{description}</p>
         <a
           href={url}
           target="_blank"
diff --git a/components/molecules/AuthSection/auth-section.tsx b/components/molecules/AuthSection/auth-section.tsx
index ec45a4e7..23565f33 100644
--- a/components/molecules/AuthSection/auth-section.tsx
+++ b/components/molecules/AuthSection/auth-section.tsx
@@ -17,9 +17,6 @@ import Button from "components/atoms/Button/button";
 import Text from "components/atoms/Typography/text";
 import GitHubIcon from "img/icons/github-icon.svg";
 import Icon from "components/atoms/Icon/icon";
-import NotificationCard from "components/atoms/NotificationsCard/notification-card";
-import { Spinner } from "components/atoms/SpinLoader/spin-loader";
-import { Popover, PopoverContent, PopoverTrigger } from "../Popover/popover";
 import DropdownList from "../DropdownList/dropdown-list";
 import OnboardingButton from "../OnboardingButton/onboarding-button";
 import userAvatar from "../../../img/ellipse-1.png";
@@ -30,47 +27,22 @@ const AuthSection: React.FC = ({}) => {
   const router = useRouter();
   const currentPath = router.asPath;
 
-  const { signIn, signOut, user, sessionToken } = useSupabaseAuth();
+  const { signIn, signOut, user } = useSupabaseAuth();
   const { onboarded, session } = useSession(true);
-  const [notifications, setNotifications] = useState<DbUserNotification[]>([]);
-  const [loading, setLoading] = useState(false);
   const [userInfo, setUserInfo] = useState<DbUser | undefined>(undefined);
   const [host, setHost] = useState<string>("");
+
   useEffect(() => {
     if (typeof window !== "undefined") {
       setHost(window.location.origin as string);
     }
   }, []);
 
-  // Fetch user notifications
-  const fetchNotifications = async () => {
-    if (userInfo && userInfo.notification_count > 0) {
-      setLoading(true);
-      const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/notifications`, {
-        headers: {
-          accept: "application/json",
-          Authorization: `Bearer ${sessionToken}`,
-        },
-      });
-      setLoading(false);
-      if (req.ok) {
-        const notifications = await req.json();
-        setNotifications(notifications.data as DbUserNotification[]);
-      }
-    } else {
-      return;
-    }
-  };
-
   useEffect(() => {
-    const getUser = async () => {
-      if (session && !userInfo) {
-        setUserInfo(session);
-      }
-    };
-
-    getUser();
-  }, [userInfo]);
+    if (session && !userInfo) {
+      setUserInfo(session);
+    }
+  }, [session]);
 
   const authMenu = {
     authed: [
@@ -132,42 +104,12 @@ const AuthSection: React.FC = ({}) => {
             ) : (
               ""
             )}
-            <Popover
-              onOpenChange={(state) => {
-                // reset the notification state to empty when the popover is closed
-                if (!loading && !state) setUserInfo(undefined);
-              }}
-            >
-              <PopoverTrigger onClick={async () => await fetchNotifications()} asChild>
-                <button className="relative cursor-pointer">
-                  {userInfo && userInfo.notification_count > 0 && (
-                    <span className="absolute right-0 block w-2 h-2 bg-orange-300 rounded-full"></span>
-                  )}
-                  <IoNotifications className="text-xl text-light-slate-9" />
-                </button>
-              </PopoverTrigger>
-              <PopoverContent align="end" className="bg-white !rounded-xl p-1  ">
-                {loading ? (
-                  <div className="flex items-center justify-center py-2">
-                    <Spinner />
-                  </div>
-                ) : (
-                  <>
-                    {notifications.length > 0 ? (
-                      <div className="space-y-1">
-                        {notifications.map(({ type, message, id, meta_id }) => (
-                          <NotificationCard key={id} message={message} type={type} id={meta_id} />
-                        ))}
-                      </div>
-                    ) : (
-                      <div className="px-3 py-2 text-sm text-center text-light-slate-9">
-                        You do not have any unread notification
-                      </div>
-                    )}
-                  </>
-                )}
-              </PopoverContent>
-            </Popover>
+            <button className="relative cursor-pointer" onClick={() => router.push(`/user/notifications`)}>
+              {userInfo && userInfo.notification_count > 0 && (
+                <span className="absolute right-0 block w-2 h-2 bg-orange-300 rounded-full"></span>
+              )}
+              <IoNotifications className="text-xl text-light-slate-9" />
+            </button>
 
             <DropdownList menuContent={authMenu.authed}>
               <div className="flex justify-end min-w-[60px] gap-2">
diff --git a/components/molecules/ContributorProfileInfo/contributor-profile-info.tsx b/components/molecules/ContributorProfileInfo/contributor-profile-info.tsx
index 0d6a86e7..3f0df824 100644
--- a/components/molecules/ContributorProfileInfo/contributor-profile-info.tsx
+++ b/components/molecules/ContributorProfileInfo/contributor-profile-info.tsx
@@ -54,8 +54,6 @@ const ContributorProfileInfo = ({
           {isConnected && <Badge text="beta" />}
         </div>
         <div className="flex items-center text-sm gap-3">
-          <span className="text-light-slate-11 text-sm">{`@${githubName}`}</span>
-
           {isConnected && (
             <>
               {displayLocalTime && (
@@ -93,6 +91,18 @@ const ContributorProfileInfo = ({
                 </span>
               )}
 
+              <span className="flex gap-2 items-center">
+                <FiGithub className="text-light-slate-9" />
+                <Link
+                  href={`https://github.com/${githubName}`}
+                  target="_blank"
+                  rel="noreferrer"
+                  className="w-max hover:text-orange-500 "
+                >
+                  {githubName}
+                </Link>
+              </span>
+
               {twitterUsername && (
                 <span className="flex gap-2 items-center">
                   <FiTwitter className="text-light-slate-9" />
diff --git a/components/molecules/FeaturedHighlightsPanel/featured-highlights-panel.tsx b/components/molecules/FeaturedHighlightsPanel/featured-highlights-panel.tsx
index 12201050..f9af16a6 100644
--- a/components/molecules/FeaturedHighlightsPanel/featured-highlights-panel.tsx
+++ b/components/molecules/FeaturedHighlightsPanel/featured-highlights-panel.tsx
@@ -18,7 +18,7 @@ const FeaturedHighlightsPanel = ({ highlights }: FeaturedHighlightsPanelProps) =
       <h2 className="pb-2 text-lg border-b">Featured Highlights</h2>
       <div className="flex flex-col gap-4 text-sm">
         {highlights.map(({ id, title }) => (
-          <Link className="transition hover:text-light-orange-10" key={id} href={`${host}/feed/${id}`}>
+          <Link className="font-normal transition hover:text-light-orange-10" key={id} href={`${host}/feed/${id}`}>
             {title}
           </Link>
         ))}
diff --git a/components/molecules/HighlightInput/highlight-input-form.tsx b/components/molecules/HighlightInput/highlight-input-form.tsx
index e42eccf1..8239e5e4 100644
--- a/components/molecules/HighlightInput/highlight-input-form.tsx
+++ b/components/molecules/HighlightInput/highlight-input-form.tsx
@@ -155,9 +155,10 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
                 value={title}
                 maxLength={50}
                 onChange={(e) => setTitle(e.target.value)}
-                className="flex-1 focus:outline-none"
+                className="flex-1 font-normal placeholder:text-sm focus:outline-none"
                 type="text"
                 placeholder={isDivFocused ? "Add title (optional)" : "Post a highlight to show your work!"}
+                id="highlight-create-input"
               />
             </div>
           </CollapsibleTrigger>
@@ -264,6 +265,7 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
                 className="flex-1 focus:outline-none"
                 type="text"
                 placeholder={"Add title (optional)"}
+                id="highlight-create-input"
               />
             </div>
             <TypeWriterTextArea
@@ -337,6 +339,7 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
         <div
           onClick={() => setIsFormOpenMobile(true)}
           className="p-3 text-white rounded-full shadow-lg bg-light-orange-10"
+          id="mobile-highlight-create-button"
         >
           <RxPencil1 className="text-3xl" />
         </div>
diff --git a/components/molecules/HighlightsFeedCard/highlights-filter-card.tsx b/components/molecules/HighlightsFeedCard/highlights-filter-card.tsx
index a2763684..4036b213 100644
--- a/components/molecules/HighlightsFeedCard/highlights-filter-card.tsx
+++ b/components/molecules/HighlightsFeedCard/highlights-filter-card.tsx
@@ -25,7 +25,7 @@ const HighlightsFilterCard = ({ repos, setSelected, selectedFilter }: Highlights
 
   return (
     <div className="py-4 px-6 rounded-lg bg-light-slate-1  border w-[18.625rem] h-max">
-      <Title className="!font-normal" level={3}>
+      <Title className="!text-lg" level={3}>
         Repositories
       </Title>
       <p className="mb-2 text-sm font-normal text-light-slate-11">Click to filter the highlights</p>
diff --git a/components/molecules/NewsletterForm/newsletter-form.tsx b/components/molecules/NewsletterForm/newsletter-form.tsx
index 9037df04..8cfaafa1 100644
--- a/components/molecules/NewsletterForm/newsletter-form.tsx
+++ b/components/molecules/NewsletterForm/newsletter-form.tsx
@@ -68,7 +68,9 @@ const NewsletterForm = () => {
         <div className="flex flex-col w-full gap-3 px-4 py-6 border rounded-lg bg-light-slate-1">
           <div className="w-64 space-y-1">
             <h2 className="text-lg">Subscribe to our newsletter</h2>
-            <p className="text-sm text-light-slate-11">Stay up to date with the latest OpenSauced news and trends!</p>
+            <p className="text-sm font-normal text-light-slate-11">
+              Stay up to date with the latest OpenSauced news and trends!
+            </p>
           </div>
           <form
             name="newsletter"
diff --git a/components/molecules/TopContributorsPanel/top-contributors-panel.tsx b/components/molecules/TopContributorsPanel/top-contributors-panel.tsx
index 557e8de4..bc6dd70d 100644
--- a/components/molecules/TopContributorsPanel/top-contributors-panel.tsx
+++ b/components/molecules/TopContributorsPanel/top-contributors-panel.tsx
@@ -13,8 +13,8 @@ const TopContributorsPanel = ({ loggedInUserLogin }: TopContributorsPanelProps)
   const top3Contributors = topContributorsWithoutLoggedInUser.slice(0, 3).map((user) => user.login);
 
   return (
-    <div className="flex flex-col max-w-xs gap-6 p-6 border bg-light-slate-1 rounded-xl">
-      <h2 className="pb-2 text-2xl border-b">Top Contributors</h2>
+    <div className="flex flex-col max-w-xs gap-5 p-6 border rounded-lg bg-light-slate-1">
+      <h2 className="pb-2 text-lg border-b">Top Contributors</h2>
 
       {isLoading &&
         Array.from({ length: 3 }).map((_, i) => (
diff --git a/components/organisms/TopNav/top-nav.tsx b/components/organisms/TopNav/top-nav.tsx
index 21d5ea2f..7fc06374 100644
--- a/components/organisms/TopNav/top-nav.tsx
+++ b/components/organisms/TopNav/top-nav.tsx
@@ -18,7 +18,7 @@ const TopNav: React.FC = () => {
   const router = useRouter();
 
   return (
-    <header className="top-nav-container flex justify-between items-center z-50 py-0.5 bg-light-slate-3 border-b">
+    <header className="top-nav-container w-full fixed top-0 left-0 flex justify-between items-center z-50 py-0.5 bg-light-slate-3 border-b">
       <div className="flex justify-between items-start sm:items-center mx-auto container px-2 md:px-16">
         <div className="flex gap-3 md:gap-8 items-center flex-wrap">
           <HeaderLogo withBg={false} textIsBlack />
diff --git a/layouts/private-wrapper.tsx b/layouts/private-wrapper.tsx
index 170d51be..0167981a 100644
--- a/layouts/private-wrapper.tsx
+++ b/layouts/private-wrapper.tsx
@@ -1,4 +1,4 @@
-import { useUser } from "@supabase/auth-helpers-react";
+import { useSessionContext, useUser } from "@supabase/auth-helpers-react";
 import { useRouter } from "next/router";
 import React, { useEffect } from "react";
 import useSession from "lib/hooks/useSession";
@@ -11,12 +11,13 @@ interface PrivateWrapperProps {
 const PrivateWrapper = ({ isPrivateRoute = false, children }: PrivateWrapperProps) => {
   const user = useUser();
   const router = useRouter();
+  const { isLoading } = useSessionContext();
   const { session: isValid } = useSession(true);
 
   async function checkSession() {
     if (router.asPath?.includes("login")) return;
 
-    if (!isValid) {
+    if (!isValid && !isLoading) {
       router.replace("/javascript/dashboard/filter/recent");
     }
   }
@@ -25,7 +26,7 @@ const PrivateWrapper = ({ isPrivateRoute = false, children }: PrivateWrapperProp
     if (isPrivateRoute && !user) {
       checkSession();
     }
-  }, [user]);
+  }, [user, isLoading]);
 
   return <>{children}</>;
 };
diff --git a/layouts/profile.tsx b/layouts/profile.tsx
index cb7c733e..8c7eaa85 100644
--- a/layouts/profile.tsx
+++ b/layouts/profile.tsx
@@ -8,7 +8,9 @@ const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
     <div className="flex flex-col min-h-screen">
       <TopNav />
       <div className="page-container flex grow flex-col items-center">
-        <main className="flex pb-16 w-full flex-1 flex-col items-center bg-light-slate-2">{children}</main>
+        <main className="container px-2 flex md:px-16 pb-16 w-full flex-1 flex-col items-center bg-light-slate-2">
+          {children}
+        </main>
       </div>
       <Footer />
     </div>
diff --git a/lib/utils/get-notification-url.ts b/lib/utils/get-notification-url.ts
new file mode 100644
index 00000000..4f5fc4d8
--- /dev/null
+++ b/lib/utils/get-notification-url.ts
@@ -0,0 +1,16 @@
+/**
+ * Returns the URL for a notification based on its type and ID or username.
+ * @param type - The type of the notification.
+ * @param id - The ID or username associated with the notification.
+ * @returns The URL for the notification.
+ */
+export const getNotificationURL = (type: string, id: string | number) => {
+  switch (type) {
+    case "highlight_reaction":
+      return `/feed/${id}`;
+    case "follow":
+      return `/user/${id}`;
+    default:
+      return "/";
+  }
+};
diff --git a/middleware.ts b/middleware.ts
index b31db5ad..6f770434 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -20,10 +20,16 @@ export async function middleware(req: NextRequest) {
   // Auth condition not met, redirect to home page.
   const redirectUrl = req.nextUrl.clone();
   redirectUrl.pathname = "/feed";
+  if (req.nextUrl.pathname === "/feed" && req.nextUrl.searchParams.has("new")) {
+    redirectUrl.searchParams.set("signIn", "true");
+  }
   redirectUrl.searchParams.set("redirectedFrom", req.nextUrl.pathname);
-  return NextResponse.redirect(redirectUrl);
+
+  if (!req.nextUrl.searchParams.has("redirectedFrom")) {
+    return NextResponse.redirect(redirectUrl);
+  }
 }
 
 export const config = {
-  matcher: ["/hub/insights/:path*", "/user/:path"],
+  matcher: ["/hub/insights/:path*", "/user/:path", "/feed/"],
 };
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index e80b2c45..833bbe4e 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1,12 +1,12 @@
 {
   "name": "@open-sauced/insights",
-  "version": "1.58.0",
+  "version": "1.59.0-beta.9",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@open-sauced/insights",
-      "version": "1.58.0",
+      "version": "1.59.0-beta.9",
       "hasInstallScript": true,
       "license": "Apache 2.0",
       "dependencies": {
@@ -86,6 +86,7 @@
         "@storybook/nextjs": "^7.2.0",
         "@storybook/react": "^7.2.0",
         "@storybook/testing-library": "^0.2.0",
+        "@tailwindcss/typography": "^0.5.9",
         "@testing-library/jest-dom": "^5.16.5",
         "@testing-library/react": "^14.0.0",
         "@types/babel__core": "^7.20.0",
@@ -13759,6 +13760,21 @@
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
       "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
     },
+    "node_modules/@tailwindcss/typography": {
+      "version": "0.5.9",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
+      "integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
+      "dev": true,
+      "dependencies": {
+        "lodash.castarray": "^4.4.0",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.merge": "^4.6.2",
+        "postcss-selector-parser": "6.0.10"
+      },
+      "peerDependencies": {
+        "tailwindcss": ">=3.0.0 || insiders"
+      }
+    },
     "node_modules/@testing-library/dom": {
       "version": "9.3.1",
       "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz",
@@ -25547,6 +25563,12 @@
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
+    "node_modules/lodash.castarray": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+      "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+      "dev": true
+    },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -25569,6 +25591,12 @@
       "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
       "dev": true
     },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+      "dev": true
+    },
     "node_modules/lodash.isstring": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
diff --git a/package.json b/package.json
index b5a0b841..e7f095fa 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "@open-sauced/insights",
   "description": "🍕The dashboard for open source discovery.",
   "keywords": [],
-  "version": "1.58.0",
+  "version": "1.59.0-beta.9",
   "author": "Brian Douglas <brian@opensauced.pizza>",
   "private": true,
   "license": "Apache 2.0",
@@ -121,6 +121,7 @@
     "@storybook/nextjs": "^7.2.0",
     "@storybook/react": "^7.2.0",
     "@storybook/testing-library": "^0.2.0",
+    "@tailwindcss/typography": "^0.5.9",
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^14.0.0",
     "@types/babel__core": "^7.20.0",
diff --git a/pages/feed/[id].tsx b/pages/feed/[id].tsx
index de73215e..204e5c22 100644
--- a/pages/feed/[id].tsx
+++ b/pages/feed/[id].tsx
@@ -10,8 +10,9 @@ export const getServerSideProps = async (context: HighlightSSRPropsContext) => {
 
 export type HighlightSSRPropsContext = GetServerSidePropsContext<{ id: string }>;
 
-export async function handleHighlightSSR({ params }: GetServerSidePropsContext<{ id: string }>) {
+export async function handleHighlightSSR({ req, params }: GetServerSidePropsContext<{ id: string }>) {
   const { id } = params!;
+  const { referer } = req.headers;
 
   async function fetchHighlight() {
     const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/highlights/${id}`, {
@@ -30,6 +31,9 @@ export async function handleHighlightSSR({ params }: GetServerSidePropsContext<{
   const highlight = await fetchHighlight();
 
   return {
-    props: { highlight },
+    props: {
+      highlight,
+      referer: referer ?? null,
+    },
   };
 }
diff --git a/pages/feed/index.tsx b/pages/feed/index.tsx
index b501a5b5..bf4b8e49 100644
--- a/pages/feed/index.tsx
+++ b/pages/feed/index.tsx
@@ -1,9 +1,11 @@
+/* eslint-disable prettier/prettier */
 import { useEffect, useState, useRef } from "react";
 import { useRouter } from "next/router";
 import Link from "next/link";
 import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
 import clsx from "clsx";
 
+import { AiOutlineClose } from "react-icons/ai";
 import TopContributorsPanel from "components/molecules/TopContributorsPanel/top-contributors-panel";
 import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
 import { useFetchAllHighlights } from "lib/hooks/useFetchAllHighlights";
@@ -19,6 +21,7 @@ import SEO from "layouts/SEO/SEO";
 
 import { Dialog, DialogCloseButton, DialogContent } from "components/molecules/Dialog/dialog";
 import Avatar from "components/atoms/Avatar/avatar";
+import Button from "components/atoms/Button/button";
 import { Tabs, TabsContent, TabsList, TabsTrigger } from "components/atoms/Tabs/tabs";
 import ContributorHighlightCard from "components/molecules/ContributorHighlight/contributor-highlight-card";
 import HighlightInputForm from "components/molecules/HighlightInput/highlight-input-form";
@@ -37,10 +40,11 @@ type highlightReposType = { repoName: string; repoIcon: string; full_name: strin
 
 interface HighlightSSRProps {
   highlight: DbHighlight | null;
+  referer: string;
 }
 
 const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
-  const { user } = useSupabaseAuth();
+  const { user, signIn } = useSupabaseAuth();
   const { data: repos } = useFetchHighlightRepos();
 
   const { data: featuredHighlights } = useFetchFeaturedHighlights();
@@ -81,6 +85,46 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
     { name: "Highlights", count: highlights_count ?? 0 },
   ];
 
+  useEffect(() => {
+    const queryParams = new URLSearchParams(window.location.search);
+    const newHighlight = queryParams.get("new");
+    const signInRequired = queryParams.get("signIn");
+
+    if (newHighlight && signInRequired) {
+      signIn({ provider: "github", options: { redirectTo: `${window.location.origin}/feed?new=${newHighlight}` } });
+    }
+
+    // no need to create intervals for checking the highlight creation input if there is no new highlight
+    if (!newHighlight) {
+      return;
+    }
+
+    let focusOnHighlighCreationInput: NodeJS.Timeout;
+
+    if (window.innerWidth > 768) {
+      focusOnHighlighCreationInput = setInterval(() => {
+        const highlightCreationInput = document.getElementById("highlight-create-input");
+        if (newHighlight && highlightCreationInput) {
+          highlightCreationInput.click();
+          highlightCreationInput.focus();
+          clearInterval(focusOnHighlighCreationInput);
+        }
+      }, 1000);
+    } else {
+      // for mobile. No need to focus on input, just click on the button as it opens up a form anyway.
+      focusOnHighlighCreationInput = setInterval(() => {
+        const mobileHighlightCreateButton = document.getElementById("mobile-highlight-create-button");
+        if (newHighlight && mobileHighlightCreateButton) {
+          mobileHighlightCreateButton.click();
+          clearInterval(focusOnHighlighCreationInput);
+        }
+      }, 1000);
+    }
+    return () => {
+      clearInterval(focusOnHighlighCreationInput);
+    };
+  }, []);
+
   useEffect(() => {
     if (activeTab === "home") {
       setRepoList(repoTofilterList(repos));
@@ -91,7 +135,6 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
 
   useEffect(() => {
     if (singleHighlight && !openSingleHighlight) {
-      router.push(`/feed/${props.highlight?.id}`);
       setOpenSingleHighlight(true);
     }
   }, [singleHighlight]);
@@ -123,41 +166,47 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
         image={ogImage}
         twitterCard="summary_large_image"
       />
+
       <div
-        className="container flex flex-col gap-16 px-2 pt-12 mx-auto md:px-16 lg:justify-end md:flex-row"
+        className=" w-full gap-[2rem] flex flex-col justify-center md:gap-6 xl:gap-16 px-2 pt-12 md:items-start md:justify-between mx-auto md:px-16 lg:justify-end md:flex-row"
         ref={topRef}
       >
-        <div className="flex-col flex-1 hidden gap-6 mt-12 md:flex">
-          {user && (
-            <div>
-              <UserCard
-                loading={loggedInUserLoading}
-                username={loggedInUser?.login as string}
-                meta={userMetaArray as MetaObj[]}
-                name={loggedInUser?.name as string}
-              />
-            </div>
-          )}
-          <TopContributorsPanel loggedInUserLogin={loggedInUser?.login ?? ""} />
-
-          <AnnouncementCard
-            title="#100DaysOfOSS 🚀 "
-            description={
-              "Join us for 100 days of supporting, sharing knowledge, and exploring the open source ecosystem together."
-            }
-            bannerSrc={
-              "https://user-images.githubusercontent.com/5713670/254358937-8e9aa76d-4ed3-4616-a58a-2283796b10e1.png"
-            }
-            url={"https://dev.to/opensauced/100daysofoss-growing-skills-and-real-world-experience-3o5k"}
-          />
+        <div className="sticky top-8">
+          <div className="flex-col flex-1 xl:flex hidden gap-6 mt-12">
+            {user && (
+              <div className="md:w-1/2 lg:w-1/3">
+                <UserCard
+                  loading={loggedInUserLoading}
+                  username={loggedInUser?.login as string}
+                  meta={userMetaArray as MetaObj[]}
+                  name={loggedInUser?.name as string}
+                />
+              </div>
+            )}
+            <TopContributorsPanel loggedInUserLogin={loggedInUser?.login ?? ""} />
+            <AnnouncementCard
+              title="#100DaysOfOSS 🚀 "
+              description={
+                "Join us for 100 days of supporting, sharing knowledge, and exploring the open source ecosystem together."
+              }
+              bannerSrc={
+                "https://user-images.githubusercontent.com/5713670/254358937-8e9aa76d-4ed3-4616-a58a-2283796b10e1.png"
+              }
+              url={"https://dev.to/opensauced/100daysofoss-growing-skills-and-real-world-experience-3o5k"}
+            />
+          </div>
         </div>
         {singleHighlight && (
           <Dialog
             open={openSingleHighlight}
             onOpenChange={(open) => {
-              if (!open) {
-                setOpenSingleHighlight(false);
-                router.push("/feed");
+              if (openSingleHighlight && !open) {
+                if (props.referer !== null && !props.referer.includes("/feed")) {
+                  router.back();
+                } else {
+                  setOpenSingleHighlight(false);
+                  router.replace("/feed");
+                }
               }
             }}
           >
@@ -179,7 +228,18 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
                         addSuffix: true,
                       })}
                     </span>
-                    <DialogCloseButton onClick={() => router.push("/feed")} />
+                    {props.referer !== null && !props.referer.includes("/feed") ? (
+                      <Button
+                        variant="text"
+                        onClick={() => router.back()}
+                        className="!p-0 !border-0 absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100"
+                      >
+                        <AiOutlineClose size={20} />
+                        <span className="sr-only">Close</span>
+                      </Button>
+                    ) : (
+                      <DialogCloseButton onClick={() => router.replace("/feed")} />
+                    )}
                   </div>
 
                   <div className="w-full px-2 py-6 border bg-light-slate-1 md:px-6 lg:px-12 rounded-xl">
@@ -204,7 +264,7 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
             setActiveTab(value as activeTabType);
           }}
           defaultValue="home"
-          className="md:flex-[2] "
+          className="grow"
         >
           <TabsList className={clsx("justify-start  w-full border-b", !user && "hidden")}>
             <TabsTrigger
@@ -266,13 +326,16 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
             <FollowingHighlightWrapper selectedFilter={selectedRepo} emojis={emojis} />
           </TabsContent>
         </Tabs>
-        <div className="hidden gap-6 mt-10 md:flex-1 md:flex md:flex-col">
+        <div className="md:hidden gap-6 mt-10 md:flex-1 lg:flex md:flex-col sticky top-20">
           {repoList && repoList.length > 0 && (
             <HighlightsFilterCard
               selectedFilter={selectedRepo}
               setSelected={(repo) => {
                 if (!openSingleHighlight) {
-                  router.push(`/feed${repo ? `?repo=${repo}` : ""}`);
+                  const queryParams = new URLSearchParams(window.location.search);
+                  router.push(
+                    `/feed${repo ? `?repo=${repo}` : queryParams.toString() ? `?${queryParams.toString()}` : ""}`
+                  );
                   setPage(1);
                   setSelectedRepo(repo);
                 }
diff --git a/pages/start.tsx b/pages/start.tsx
index 45013a41..254134e0 100644
--- a/pages/start.tsx
+++ b/pages/start.tsx
@@ -183,6 +183,14 @@ const LoginStep3: React.FC<LoginStep3Props> = ({ interests, user }) => {
   const [timezone, setTimezone] = useState("");
   const [loading, setLoading] = useState(false);
 
+  useEffect(() => {
+    const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+    const timezone = timezones.find((timezone) => timezone.utc.includes(userTimezone));
+    if (timezone) {
+      setTimezone(timezone.value);
+    }
+  }, []);
+
   const handleUpdateTimezone = async () => {
     setLoading(true);
     try {
diff --git a/pages/user/notifications.tsx b/pages/user/notifications.tsx
new file mode 100644
index 00000000..9ccc78f9
--- /dev/null
+++ b/pages/user/notifications.tsx
@@ -0,0 +1,215 @@
+import { useEffect, useMemo, useRef, useState } from "react";
+import { useRouter } from "next/router";
+import clsx from "clsx";
+import Link from "next/link";
+
+import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
+import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
+import Title from "components/atoms/Typography/title";
+import Button from "components/atoms/Button/button";
+import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper";
+import PaginationResults from "components/molecules/PaginationResults/pagination-result";
+import Pagination from "components/molecules/Pagination/pagination";
+import DashContainer from "components/atoms/DashedContainer/DashContainer";
+import Avatar from "components/atoms/Avatar/avatar";
+import { getAvatarByUsername } from "lib/utils/github";
+import { getNotificationURL } from "lib/utils/get-notification-url";
+import changeCapitalization from "lib/utils/change-capitalization";
+import ProfileLayout from "layouts/profile";
+import { WithPageLayout } from "interfaces/with-page-layout";
+
+interface NotificationResponse {
+  data: DbUserNotification[];
+  meta: Meta;
+}
+
+const Notifications: WithPageLayout = () => {
+  const [page, setPage] = useState(1);
+  const [loading, setLoading] = useState(false);
+  const [notificationsResponse, setNotificationsResponse] = useState<NotificationResponse>();
+  const [filter, setFilter] = useState<"all" | "follow" | "highlight_reaction">("all");
+
+  const topRef = useRef<HTMLDivElement>(null);
+  const { sessionToken, user } = useSupabaseAuth(true);
+
+  const router = useRouter();
+  const username = user?.user_metadata.user_name as string;
+
+  const fetchNotifications = async (page = 1) => {
+    if (!sessionToken) return;
+    setLoading(true);
+    const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/notifications?limit=10&page=${page}`, {
+      headers: {
+        accept: "application/json",
+        Authorization: `Bearer ${sessionToken}`,
+      },
+    });
+    const notifications = await req.json();
+    setNotificationsResponse(notifications);
+    setLoading(false);
+  };
+
+  const notifications = useMemo(() => {
+    if (!notificationsResponse?.data) return [];
+    if (filter === "all")
+      return notificationsResponse?.data?.sort(
+        (a, b) => new Date(b.notified_at).getTime() - new Date(a.notified_at).getTime()
+      );
+    return notificationsResponse?.data
+      .filter((notification) => notification.type === filter)
+      ?.sort((a, b) => new Date(b.notified_at).getTime() - new Date(a.notified_at).getTime());
+  }, [notificationsResponse?.data, filter]);
+
+  useEffect(() => {
+    fetchNotifications();
+
+    if (sessionToken) {
+      router.push(`/user/notifications?filter=all`);
+    }
+  }, [sessionToken]);
+
+  const handlePageChange = (page: number) => {
+    setPage(page);
+    fetchNotifications(page);
+    if (topRef.current) {
+      topRef.current.scrollIntoView({
+        behavior: "smooth",
+      });
+    }
+  };
+
+  return (
+    <div className="w-full ">
+      <div
+        className="container flex flex-col justify-between w-full px-2 pt-16 mx-auto overflow-hidden md:px-16 md:flex-row md:gap-20 lg:gap-40"
+        ref={topRef}
+      >
+        <div className="flex flex-col gap-4 w-80">
+          <Title className="!text-2xl !text-light-slate-12" level={3}>
+            Notifications
+          </Title>
+          <aside className="flex flex-col gap-2">
+            <Button
+              variant="link"
+              className={clsx("hover:text-orange-600", filter === "all" ? "text-orange-600" : "text-light-slate-11")}
+              onClick={() => {
+                setFilter("all");
+                router.push(`/user/notifications?filter=all`);
+              }}
+            >
+              All
+            </Button>
+            <Button
+              variant="link"
+              className={clsx("hover:text-orange-600", filter === "follow" ? "text-orange-600" : "text-light-slate-11")}
+              onClick={() => {
+                setFilter("follow");
+                router.push(`/user/notifications?filter=follow`);
+              }}
+            >
+              Follows
+            </Button>
+            <Button
+              variant="link"
+              className={clsx(
+                "hover:text-orange-600",
+                filter === "highlight_reaction" ? "text-orange-600" : "text-light-slate-11"
+              )}
+              onClick={() => {
+                setFilter("highlight_reaction");
+                router.push(`/user/notifications?filter=highlight_reaction`);
+              }}
+            >
+              Reactions
+            </Button>
+          </aside>
+        </div>
+        <div className="flex-1 mt-10 md:mt-0">
+          {loading ? (
+            <div className="flex flex-col gap-4">
+              {Array.from({ length: 8 }).map((_, index) => (
+                <div className="flex gap-2" key={index}>
+                  <SkeletonWrapper width={50} height={50} />
+                  <div className="">
+                    <SkeletonWrapper height={20} width={500} classNames="mb-2" />
+                    <SkeletonWrapper height={10} width={100} />
+                  </div>
+                </div>
+              ))}
+            </div>
+          ) : notifications?.length <= 0 && !loading ? (
+            <DashContainer>
+              <div className="text-center">
+                <p>
+                  You don&apos;t have any{" "}
+                  {changeCapitalization(filter === "highlight_reaction" ? "reaction" : filter, true)} notifications yet!{" "}
+                  <br />
+                </p>
+              </div>
+            </DashContainer>
+          ) : (
+            <div className="flex flex-col gap-2 mb-10">
+              {notifications?.map((notification) => (
+                <div className="p-2 border bg-light-slate-2 rounded-lg flex items-center gap-4" key={notification.id}>
+                  <Avatar
+                    initialsClassName="text-[100px] leading-none"
+                    initials={notification.meta_id.charAt(0)}
+                    hasBorder
+                    avatarURL={getAvatarByUsername(notification.meta_id, 300)}
+                    size={50}
+                    isCircle
+                  />
+                  <div className="flex flex-col gap-2">
+                    <p className="text-light-slate-12 flex gap-2">
+                      <Link href={getNotificationURL(notification.type, notification.meta_id)} className="font-bold">
+                        {notification.meta_id}
+                      </Link>
+                      <span>{notification.message.replace(notification.meta_id, " ")}</span>
+                    </p>
+                    <span className="text-xs font-normal text-light-slate-11">
+                      {formatDistanceToNowStrict(new Date(notification.notified_at), { addSuffix: true })}
+                    </span>
+                  </div>
+                </div>
+              ))}
+            </div>
+          )}
+          {notifications?.length > 0 && (notificationsResponse?.meta?.pageCount ?? 0) > 1 && (
+            <div className="my-10 flex px-2 items-center justify-between">
+              <div className="flex items-center w-max gap-x-4">
+                <PaginationResults
+                  metaInfo={
+                    notificationsResponse?.meta ?? {
+                      itemCount: 0,
+                      limit: 0,
+                      page: 0,
+                      hasNextPage: false,
+                      hasPreviousPage: false,
+                      pageCount: 0,
+                    }
+                  }
+                  total={notificationsResponse?.meta?.itemCount ?? 0}
+                  entity={"notifications"}
+                />
+              </div>
+              <Pagination
+                pages={[]}
+                totalPage={notificationsResponse?.meta?.pageCount ?? 0}
+                page={notificationsResponse?.meta?.page ?? 0}
+                pageSize={notificationsResponse?.meta?.itemCount ?? 0}
+                goToPage
+                hasNextPage={notificationsResponse?.meta?.hasNextPage ?? false}
+                hasPreviousPage={notificationsResponse?.meta?.hasPreviousPage ?? false}
+                onPageChange={(pageNumber) => handlePageChange(pageNumber)}
+              />
+            </div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+Notifications.PageLayout = ProfileLayout;
+Notifications.isPrivateRoute = true;
+export default Notifications;
diff --git a/stories/atoms/text.stories.tsx b/stories/atoms/text.stories.tsx
index bfae08c5..2634a535 100644
--- a/stories/atoms/text.stories.tsx
+++ b/stories/atoms/text.stories.tsx
@@ -1,24 +1,60 @@
-import { ComponentStory } from "@storybook/react";
+import type { Meta, StoryObj } from "@storybook/react";
 import Text from "../../components/atoms/Typography/text";
 
-const storyConfig = {
-  title: "Design System/Atoms/Text",
-  component: "Text",
+const meta: Meta<typeof Text> = {
+  title: "Design System/Atoms/Typography/Text",
+  component: Text,
+  argTypes: {
+    type: {
+      options: ["default", "secondary", "success", "warning", "danger"],
+      control: { type: "select" },
+    },
+  },
 };
 
-export default storyConfig;
+export default meta;
 
-//Text Template
-const TextTemplate: ComponentStory<typeof Text> = (args) => <Text {...args} />;
+type Story = StoryObj<typeof Text>;
 
-export const Default = TextTemplate.bind({});
+export const Default: Story = {
+  args: {
+    children: "Test",
+    type: "default",
+    strong: false,
+    underline: false,
+    strikethrough: false,
+    mark: false,
+    code: false,
+    keyboard: false,
+    small: false,
+    disabled: false,
+  },
+};
+
+export const KeyboardText: Story = {
+  args: {
+    ...Default.args,
+    keyboard: true,
+  },
+};
+
+export const CodeText: Story = {
+  args: {
+    ...Default.args,
+    code: true,
+  },
+};
+
+export const DisbaledText: Story = {
+  args: {
+    ...Default.args,
+    disabled: true,
+  },
+};
 
-Default.args = {
-  children: "Test",
-  strong: false,
-  type: "default",
-  strikethrough: false,
-  underline: false,
-  small: false,
-  disabled: false,
+export const SmallText: Story = {
+  args: {
+    ...Default.args,
+    small: true,
+  },
 };
diff --git a/stories/atoms/title.stories.tsx b/stories/atoms/title.stories.tsx
index 9149d1fe..9e91752e 100644
--- a/stories/atoms/title.stories.tsx
+++ b/stories/atoms/title.stories.tsx
@@ -1,19 +1,26 @@
-import { ComponentStory } from "@storybook/react";
+import type { Meta, StoryObj } from "@storybook/react";
 import Title from "../../components/atoms/Typography/title";
 
-const storyConfig = {
-  title: "Design System/Atoms/Title",
-  component: "Title",
+const meta: Meta = {
+  title: "Design System/Atoms/Typography/Title",
+  component: Title,
+  argTypes: {
+    level: {
+      options: [1, 2, 3, 4, 5],
+      control: { type: "select" },
+    },
+    weight: {
+      options: ["thin", "extralight", "light", "normal", "medium", "semibold", "bold", "extrabold", "black"],
+      control: { type: "select" },
+    },
+  },
 };
 
-export default storyConfig;
+export default meta;
 
-//Title Template
-const TitleTemplate: ComponentStory<typeof Title> = (args) => <Title {...args} />;
-
-export const Default = TitleTemplate.bind({});
-
-Default.args = {
-  children: "Test",
-  level: 1,
+export const Default: StoryObj<typeof Title> = {
+  args: {
+    children: "Test",
+    level: 1,
+  },
 };
diff --git a/stories/atoms/typography-wrapper.stories.tsx b/stories/atoms/typography-wrapper.stories.tsx
new file mode 100644
index 00000000..b1aec7df
--- /dev/null
+++ b/stories/atoms/typography-wrapper.stories.tsx
@@ -0,0 +1,231 @@
+/* eslint-disable prettier/prettier */
+import type { Meta, StoryFn } from "@storybook/react";
+import TypographyWrapper from "../../components/atoms/Typography/wrapper";
+
+const meta: Meta<typeof TypographyWrapper> = {
+  title: "Design System/Atoms/Typography/Wrapper",
+  component: TypographyWrapper,
+  argTypes: {
+    size: {
+      options: ["sm", "base", "lg", "xl", "2xl"],
+      control: { type: "select" },
+    },
+  },
+};
+
+export default meta;
+
+const TypographyTemplate: StoryFn<typeof TypographyWrapper> = (args) => (
+  <TypographyWrapper {...args}>
+    <p className="lead">
+      Until now, trying to style an article, document, or blog post in OpenSauced Insights has been a tedious task that
+      required a keen eye for typography and a lot of complex custom CSS.
+    </p>
+    <p>
+      By default, OpenSauced Insights removes all of the default browser styling from paragraphs, headings, lists and
+      more. This ends up being really useful for building application UIs because you spend less time undoing user-agent
+      styles, but when you <em>really are</em> just trying to style some content that came from a rich-text editor in a
+      CMS or a markdown file, it can be surprising and unintuitive.
+    </p>
+    <p>We get lots of complaints about it actually, with people regularly asking us things like:</p>
+    <blockquote>
+      <p>
+        Why is OpenSauced Insights removing the default styles on my <code>h1</code> elements? How do I disable this?
+        What do you mean I lose all the other base styles too?
+      </p>
+    </blockquote>
+    <p>
+      We hear you, but we&apos;re not convinced that simply disabling our base styles is what you really want. You
+      don&apos;t want to have to remove annoying margins every time you use a <code>p</code> element in a piece of your
+      dashboard UI. And I doubt you really want your blog posts to use the user-agent styles either — you want them to
+      look <em>awesome</em>, not awful.
+    </p>
+    <p>
+      The <code>@tailwindcss/typography</code> plugin is our attempt to give you what you <em>actually</em> want,
+      without any of the downsides of doing something stupid like disabling our base styles.
+    </p>
+    <p>
+      It adds a new <code>prose</code> class that you can slap on any block of vanilla HTML content and turn it into a
+      beautiful, well-formatted document:
+    </p>
+    <pre>
+      <code className="language-html">
+        &lt;article class=&quot;prose&quot;&gt; &lt;h1&gt;Garlic bread with cheese: What the science tells us&lt;/h1&gt;
+        &lt;p&gt; For years parents have espoused the health benefits of eating garlic bread with cheese to their
+        children, with the food earning such an iconic status in our culture that kids will often dress up as warm,
+        cheesy loaf for Halloween. &lt;/p&gt; &lt;p&gt; But a recent study shows that the celebrated appetizer may be
+        linked to a series of rabies cases springing up around the country. &lt;/p&gt; &lt;!-- ... --&gt;
+        &lt;/article&gt;
+      </code>
+    </pre>
+    <p>Hopefully that looks good enough to you.</p>
+    <h3>What about nested lists?</h3>
+    <p>
+      Nested lists basically always look bad which is why editors like Medium don&apos;t even let you do it, but I guess
+      since some of you goofballs are going to do it we have to carry the burden of at least making it work.
+    </p>
+    <ol>
+      <li>
+        <strong>Nested lists are rarely a good idea.</strong>
+        <ul>
+          <li>
+            You might feel like you are being really &quot;organized&quot; or something but you are just creating a
+            gross shape on the screen that is hard to read.
+          </li>
+          <li>Nested navigation in UIs is a bad idea too, keep things as flat as possible.</li>
+          <li>Nesting tons of folders in your source code is also not helpful.</li>
+        </ul>
+      </li>
+      <li>
+        <strong>Since we need to have more items, here&apos;s another one.</strong>
+        <ul>
+          <li>I&apos;m not sure if we&apos;ll bother styling more than two levels deep.</li>
+          <li>Two is already too much, three is guaranteed to be a bad idea.</li>
+          <li>If you nest four levels deep you belong in prison.</li>
+        </ul>
+      </li>
+      <li>
+        <strong>Two items isn&apos;t really a list, three is good though.</strong>
+        <ul>
+          <li>Again please don&apos;t nest lists if you want people to actually read your content.</li>
+          <li>Nobody wants to look at this.</li>
+          <li>I&apos;m upset that we even have to bother styling this.</li>
+        </ul>
+      </li>
+    </ol>
+    <p>
+      The most annoying thing about lists in Markdown is that <code>&lt;li&gt;</code> elements aren&apos;t given a child{" "}
+      <code>&lt;p&gt;</code> tag unless there are multiple paragraphs in the list item. That means I have to worry about
+      styling that annoying situation too.
+    </p>
+    <ul>
+      <li>
+        <p>
+          <strong>For example, here&apos;s another nested list.</strong>
+        </p>
+        <p>But this time with a second paragraph.</p>
+        <ul>
+          <li>
+            These list items won&apos;t have <code>&lt;p&gt;</code> tags
+          </li>
+          <li>Because they are only one line each</li>
+        </ul>
+      </li>
+      <li>
+        <p>
+          <strong>But in this second top-level list item, they will.</strong>
+        </p>
+        <p>This is especially annoying because of the spacing on this paragraph.</p>
+        <ul>
+          <li>
+            <p>
+              As you can see here, because I&apos;ve added a second line, this list item now has a{" "}
+              <code>&lt;p&gt;</code> tag.
+            </p>
+            <p>This is the second line I&apos;m talking about by the way.</p>
+          </li>
+          <li>
+            <p>Finally here&apos;s another list item so it&apos;s more like a list.</p>
+          </li>
+        </ul>
+      </li>
+      <li>
+        <p>A closing list item, but with no nested list, because why not?</p>
+      </li>
+    </ul>
+    <p>And finally a sentence to close off this section.</p>
+    <h2>There are other elements we need to style</h2>
+    <p>
+      I almost forgot to mention links, like{" "}
+      <a href="https://insights.opensauced.pizza">this link to the OpenSauced Insights website</a>. We almost made them
+      blue but that&apos;s so yesterday, so we went with dark gray, feels edgier.
+    </p>
+    <p>We even included table styles, check it out:</p>
+    <table>
+      <thead>
+        <tr>
+          <th>Wrestler</th>
+          <th>Origin</th>
+          <th>Finisher</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td>Bret &quot;The Hitman&quot; Hart</td>
+          <td>Calgary, AB</td>
+          <td>Sharpshooter</td>
+        </tr>
+        <tr>
+          <td>Stone Cold Steve Austin</td>
+          <td>Austin, TX</td>
+          <td>Stone Cold Stunner</td>
+        </tr>
+        <tr>
+          <td>Randy Savage</td>
+          <td>Sarasota, FL</td>
+          <td>Elbow Drop</td>
+        </tr>
+        <tr>
+          <td>Vader</td>
+          <td>Boulder, CO</td>
+          <td>Vader Bomb</td>
+        </tr>
+        <tr>
+          <td>Razor Ramon</td>
+          <td>Chuluota, FL</td>
+          <td>Razor&apos;s Edge</td>
+        </tr>
+      </tbody>
+    </table>
+    <p>
+      We also need to make sure inline code looks good, like if I wanted to talk about <code>&lt;span&gt;</code>{" "}
+      elements or tell you the good news about <code>@tailwindcss/typography</code>.
+    </p>
+    <h3>
+      Sometimes I even use <code>code</code> in headings
+    </h3>
+    <p>
+      Even though it&apos;s probably a bad idea, and historically I&apos;ve had a hard time making it look good. This{" "}
+      <em>&quot;wrap the code blocks in backticks&quot;</em> trick works pretty well though really.
+    </p>
+    <p>
+      Another thing I&apos;ve done in the past is put a <code>code</code> tag inside of a link, like if I wanted to tell
+      you about the{" "}
+      <a href="https://github.com/tailwindcss/docs">
+        <code>tailwindcss/docs</code>
+      </a>{" "}
+      repository. I don&apos;t love that there is an underline below the backticks but it is absolutely not worth the
+      madness it would require to avoid it.
+    </p>
+    <h4>
+      We haven&apos;t used an <code>h4</code> yet
+    </h4>
+    <p>
+      But now we have. Please don&apos;t use <code>h5</code> or <code>h6</code> in your content, Medium only supports
+      two heading levels for a reason, you animals. I honestly considered using a <code>before</code> pseudo-element to
+      scream at you if you use an <code>h5</code> or <code>h6</code>.
+    </p>
+    <p>
+      We don&apos;t style them at all out of the box because <code>h4</code> elements are already so small that they are
+      the same size as the body copy. What are we supposed to do with an <code>h5</code>, make it <em>smaller</em> than
+      the body copy? No thanks.
+    </p>
+    <h3>We still need to think about stacked headings though.</h3>
+    <h4>
+      Let&apos;s make sure we don&apos;t screw that up with <code>h4</code> elements, either.
+    </h4>
+    <p>Phew, with any luck we have styled the headings above this text and they look pretty good.</p>
+    <p>
+      Let&apos;s add a closing paragraph here so things end with a decently sized block of text. I can&apos;t explain
+      why I want things to end that way but I have to assume it&apos;s because I think things will look weird or
+      unbalanced if there is a heading too close to the end of the document.
+    </p>
+    <p>What I&apos;ve written here is probably long enough, but adding this final sentence can&apos;t hurt.</p>
+  </TypographyWrapper>
+);
+
+export const Default = TypographyTemplate.bind({});
+
+Default.args = {
+  size: "sm",
+};
diff --git a/tailwind.config.js b/tailwind.config.js
index 661300ff..216e2026 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -216,5 +216,13 @@ module.exports = {
       },
     },
   },
-  plugins: [require("tailwindcss-radix")()],
+  safelist: [
+    {
+      pattern: /prose-(sm|base|lg|xl|2xl)/,
+    },
+    {
+      pattern: /font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)/,
+    },
+  ],
+  plugins: [require("tailwindcss-radix")(), require("@tailwindcss/typography")],
 };


Test patch:
diff --git a/tests/lib/utils/get-notifications-url.test.ts b/tests/lib/utils/get-notifications-url.test.ts
new file mode 100644
index 00000000..19d3c5e0
--- /dev/null
+++ b/tests/lib/utils/get-notifications-url.test.ts
@@ -0,0 +1,24 @@
+import { getNotificationURL } from "lib/utils/get-notification-url";
+
+describe("[lib] getNotificationURL()", () => {
+  it("should return the correct URL for a highlight reaction notification", () => {
+    const type = "highlight_reaction";
+    const idOrUsername = 30;
+    const result = getNotificationURL(type, idOrUsername);
+    expect(result).toBe("/feed/30");
+  });
+
+  it("should return the correct URL for a follow notification", () => {
+    const type = "follow";
+    const idOrUsername = "takanome-dev";
+    const result = getNotificationURL(type, idOrUsername);
+    expect(result).toBe("/user/takanome-dev");
+  });
+
+  it("should return the correct URL for an unknown notification", () => {
+    const type = "unknown";
+    const idOrUsername = "unknown";
+    const result = getNotificationURL(type, idOrUsername);
+    expect(result).toBe("/");
+  });
+});