Repository: open-sauced/insights
Pull request #723 (2022-12-22 22:28:11)

v1.22.0-beta.12 -> main


Patch:
diff --git a/.env b/.env
index 76fd877e..baf88347 100644
--- a/.env
+++ b/.env
@@ -4,3 +4,4 @@ NEXT_PUBLIC_GS_API_URL=https://beta.gs-api.opensauced.pizza/v1
 NEXT_PUBLIC_API_URL=https://beta.api.opensauced.pizza/v1
 NEXT_PUBLIC_POSTHOG_ID=phc_Y0xz6nK55MEwWjobJsI2P8rsiomZJ6eZLoXehmMy9tt
 NEXT_PUBLIC_CLOUD_NAME=dgxgziswe
+NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=test
diff --git a/.github/ISSUE_TEMPLATE/TOP5.md b/.github/ISSUE_TEMPLATE/TOP5.md
deleted file mode 100644
index f7650710..00000000
--- a/.github/ISSUE_TEMPLATE/TOP5.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-name: Weekly TOP 5
-about: This is a template to kick start the weekly conversation on what the insights project is
-  working on.
-title: TOP 5 Week {{ weekNumber }} {{ date | date('MMMM Do') }}
-labels: top5
-assignees: SunGoldTech, 0-vortex, brandonroberts
-
----
-
-What are you planning to get done by the end of this week? (Remember to link out to issues that these activities might be driving towards)
diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
deleted file mode 100644
index e2f60c29..00000000
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-name: 🐛 Report a bug
-about: Use this template for reporting new bugs.
-title: "bug: "
-labels: ["🐛 bug", "👀 needs triage"]
----
-
-### Describe the bug
-
-A clear and concise description of what the bug is.
-
-<!--
-Tell us what you were doing when you encountered the bug.
--->
-
-### Steps to reproduce
-
-Describe how to reproduce the behavior.
-
-<!--
-1. Go to '...' 
-2. Click on '...' 
-3. Scroll down to '...' 
-4. See error
--->
-
-### Environment
-
-Select the places where the issue can be reproduced (that you know of).
-
-- [ ] Local
-- [ ] Testing
-- [ ] Development
-- [ ] Production
-
-### Code of Conduct
-
-By submitting this issue, you agree to follow our [Code of Conduct](https://docs.opensauced.pizza/contributing/code-of-conduct/).
-
-- [ ] I agree to follow this project's Code of Conduct
-
-### Contributing Docs
-
-If you plan on contributing code please read - [Contribution Guide](https://docs.opensauced.pizza/contributing/introduction-to-contributing/)
-
-- [ ]  I agree to follow this project's Contribution Docs
diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md
deleted file mode 100644
index 6d7e395a..00000000
--- a/.github/ISSUE_TEMPLATE/feature.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-name: 🚀 Feature request
-about: Use this template for adding a new feature request.
-title: "feature: "
-labels: ["💡 feature", "👀 needs triage"]
----
-
-### Type of feature
-
-Select the type of feature request, the lowercase should also be the PR prefix.
-
-- [ ] 🍕 Feature
-- [ ] 🐛 Bug Fix
-- [ ] 📝 Documentation Update
-- [ ] 🎨 Style
-- [ ] 🧑‍💻 Code Refactor
-- [ ] 🔥 Performance Improvements
-- [ ] ✅ Test
-- [ ] 🤖 Build
-- [ ] 🔁 CI
-- [ ] 📦 Chore (Release)
-- [ ] ⏩ Revert
-
-### Current behavior
-
-Is your feature request related to a problem? Please describe.
-
-<!--
-1. Go to '...' 
-2. Click on '...' 
-3. Scroll down to '...' 
-4. See error
--->
-
-### Suggested solution
-
-Describe the solution you'd like.
-
-<!--
-Tell us what you think is the best way forward 
--->
-
-### Code of Conduct
-
-By submitting this issue, you agree to follow our [Code of Conduct](https://docs.opensauced.pizza/contributing/code-of-conduct/).
-
-- [ ] I agree to follow this project's Code of Conduct
-
-### Contributing Docs
-
-If you plan on contributing code please read - [Contribution Guide](https://docs.opensauced.pizza/contributing/introduction-to-contributing/)
-
-- [ ]  I agree to follow this project's Contribution Docs
diff --git a/.github/workflows/close-top5.yml b/.github/workflows/close-top5.yml
deleted file mode 100644
index 5c568106..00000000
--- a/.github/workflows/close-top5.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-on:
-  schedule:
-  - cron: 00 08 * * 6
-name: Weekly Top5 Issue Closure
-jobs:
-  cycle-weekly-close:
-    runs-on: ubuntu-latest
-    timeout-minutes: 10
-    steps:
-    - uses: actions/checkout@master
-    - name: weekly-issue-closure
-      uses: bdougie/close-issues-based-on-label@master
-      env:
-        LABEL: top5
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/open-top5.yml b/.github/workflows/open-top5.yml
deleted file mode 100644
index f9671aab..00000000
--- a/.github/workflows/open-top5.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-on:
-  schedule:
-  - cron: 03 09 * * 1
-name: Top 5
-jobs:
-  createAnIssue:
-    name: Create an issue
-    runs-on: ubuntu-latest
-    timeout-minutes: 10
-    steps:
-    - uses: actions/checkout@v2
-    - name: Create an issue
-      uses: bdougie/create-an-issue@e43b083ea71e22e77a81ffb4a55dacb2addb71ed
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      with:
-        args: .github/ISSUE_TEMPLATE/TOP5.md
diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml
index 8f11e9c2..2c94a619 100644
--- a/.github/workflows/triage.yml
+++ b/.github/workflows/triage.yml
@@ -7,5 +7,14 @@ on:
       - edited
 
 jobs:
-  assign:
-    uses: open-sauced/open-sauced/.github/workflows/triage.yml@main
+  take-issue:
+    name: Disable take issue
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    steps:
+      - name: disable take an issue
+        uses: bdougie/take-action@main
+        with:
+          message: The auto-take issues workflow is not enabled here, we can only assign issues to @open-sauced/triage, @open-sauced/committers, @open-sauced/maintainers in this repo for now. We suggest folks not on the team open issues and take a look at the [Bugs](https://github.com/orgs/open-sauced/projects/6) or [Papercuts](https://github.com/open-sauced/insights/issues?q=is%3Aopen+is%3Aissue+milestone%3APapercuts). If you would like to find good first issues, please check out the companion project to this, [open-sauced/hot](https://github.com/open-sauced/hot).
+          issueCurrentlyAssignedMessage: Thanks for being interested in this issue. It looks like this ticket is already assigned to a contributor.
+          token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f8e713d..419e3dff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,95 @@
 
 > All notable changes to this project will be documented in this file
 
+## [1.22.0-beta.12](https://github.com/open-sauced/insights/compare/v1.22.0-beta.11...v1.22.0-beta.12) (2022-12-22)
+
+
+### 🍕 Features
+
+* add Stripe checkout integration ([#702](https://github.com/open-sauced/insights/issues/702)) ([f4da7af](https://github.com/open-sauced/insights/commit/f4da7afc4de577de869da292c35b3cbd43d801c6)), closes [#394](https://github.com/open-sauced/insights/issues/394)
+
+## [1.22.0-beta.11](https://github.com/open-sauced/insights/compare/v1.22.0-beta.10...v1.22.0-beta.11) (2022-12-22)
+
+
+### 🍕 Features
+
+* add support for goto page functionality ([#708](https://github.com/open-sauced/insights/issues/708)) ([1902a98](https://github.com/open-sauced/insights/commit/1902a983420bde31173771b6fcba97b708cf616e)), closes [#675](https://github.com/open-sauced/insights/issues/675)
+
+## [1.22.0-beta.10](https://github.com/open-sauced/insights/compare/v1.22.0-beta.9...v1.22.0-beta.10) (2022-12-21)
+
+
+### 🍕 Features
+
+* add toggle group component ([#666](https://github.com/open-sauced/insights/issues/666)) ([3bc7d8c](https://github.com/open-sauced/insights/commit/3bc7d8cda7047df921b8fcda30380287988595e9))
+
+## [1.22.0-beta.9](https://github.com/open-sauced/insights/compare/v1.22.0-beta.8...v1.22.0-beta.9) (2022-12-21)
+
+
+### 🐛 Bug Fixes
+
+* fixed status footer 404 error ([#699](https://github.com/open-sauced/insights/issues/699)) ([e14a976](https://github.com/open-sauced/insights/commit/e14a9769a8a9715aad7a0e9651ae166bed41d8ac)), closes [#656](https://github.com/open-sauced/insights/issues/656)
+
+
+### 🍕 Features
+
+* add sorting by column type on repositories screen ([#662](https://github.com/open-sauced/insights/issues/662)) ([d588032](https://github.com/open-sauced/insights/commit/d5880321ed82d5c505c74c6f797e70d5895803b6)), closes [#627](https://github.com/open-sauced/insights/issues/627)
+
+## [1.22.0-beta.8](https://github.com/open-sauced/insights/compare/v1.22.0-beta.7...v1.22.0-beta.8) (2022-12-21)
+
+
+### 🐛 Bug Fixes
+
+* update generic profile page styles to match design ([#695](https://github.com/open-sauced/insights/issues/695)) ([3eabf0f](https://github.com/open-sauced/insights/commit/3eabf0f95fb57acb5b1510ee0e32946c9e9d01eb)), closes [#692](https://github.com/open-sauced/insights/issues/692)
+
+## [1.22.0-beta.7](https://github.com/open-sauced/insights/compare/v1.22.0-beta.6...v1.22.0-beta.7) (2022-12-20)
+
+
+### 🍕 Features
+
+* filter recent contributions array by last updated time ([#690](https://github.com/open-sauced/insights/issues/690)) ([063b07c](https://github.com/open-sauced/insights/commit/063b07c239590b93bd1f202362296aa691f3a281)), closes [#682](https://github.com/open-sauced/insights/issues/682)
+
+## [1.22.0-beta.6](https://github.com/open-sauced/insights/compare/v1.22.0-beta.5...v1.22.0-beta.6) (2022-12-20)
+
+
+### 🍕 Features
+
+* implement image caching on avatar images ([#691](https://github.com/open-sauced/insights/issues/691)) ([b6fbeb4](https://github.com/open-sauced/insights/commit/b6fbeb407a5d933b91e5b885270ecd9da9a387c8))
+
+## [1.22.0-beta.5](https://github.com/open-sauced/insights/compare/v1.22.0-beta.4...v1.22.0-beta.5) (2022-12-20)
+
+
+### ✅ Tests
+
+* implement automatic notifications for foss ([#679](https://github.com/open-sauced/insights/issues/679)) ([75f3aaf](https://github.com/open-sauced/insights/commit/75f3aaf3e97f79a88a1e032302e50d78b92f5e61))
+
+## [1.22.0-beta.4](https://github.com/open-sauced/insights/compare/v1.22.0-beta.3...v1.22.0-beta.4) (2022-12-19)
+
+
+### 🍕 Features
+
+* implement hover card functionality on scatterchart ([#643](https://github.com/open-sauced/insights/issues/643)) ([8249eb8](https://github.com/open-sauced/insights/commit/8249eb8ae822592f64cc5bc07836583d16641606))
+
+## [1.22.0-beta.3](https://github.com/open-sauced/insights/compare/v1.22.0-beta.2...v1.22.0-beta.3) (2022-12-19)
+
+
+### 🍕 Features
+
+* link repos, prs, and contributors to GitHub ([#658](https://github.com/open-sauced/insights/issues/658)) ([79d16d0](https://github.com/open-sauced/insights/commit/79d16d0f7e76485b9ed9288e18753eac866516cc)), closes [#670](https://github.com/open-sauced/insights/issues/670) [#620](https://github.com/open-sauced/insights/issues/620) [#669](https://github.com/open-sauced/insights/issues/669)
+
+## [1.22.0-beta.2](https://github.com/open-sauced/insights/compare/v1.22.0-beta.1...v1.22.0-beta.2) (2022-12-19)
+
+
+### 🐛 Bug Fixes
+
+* handle caching issues with local storage ([#676](https://github.com/open-sauced/insights/issues/676)) ([e332d84](https://github.com/open-sauced/insights/commit/e332d84bafbe830bf269a62e14a4a9e93fb34eb2)), closes [#617](https://github.com/open-sauced/insights/issues/617)
+
+## [1.22.0-beta.1](https://github.com/open-sauced/insights/compare/v1.21.0...v1.22.0-beta.1) (2022-12-16)
+
+
+### 🍕 Features
+
+* remove the top5 ([#674](https://github.com/open-sauced/insights/issues/674)) ([d0720ef](https://github.com/open-sauced/insights/commit/d0720efea7f1932fbc68f8a8af8af13e89b698ff))
+
 ## [1.21.0](https://github.com/open-sauced/insights/compare/v1.20.0...v1.21.0) (2022-12-16)
 
 
diff --git a/components/atoms/Avatar/avatar.tsx b/components/atoms/Avatar/avatar.tsx
index c2b2f1a1..1f0e5358 100644
--- a/components/atoms/Avatar/avatar.tsx
+++ b/components/atoms/Avatar/avatar.tsx
@@ -1,6 +1,7 @@
 import React from "react";
 import Image, { StaticImageData } from "next/image";
 import AvatarImage from "../../../public/hacktoberfest-icon.png";
+import cachedImage from "lib/utils/cachedImages";
 
 interface AvatarProps {
   className?: string;
@@ -33,10 +34,12 @@ export default Avatar;
 const CustomAvatar = ({ className, avatarURL, initials, alt, size, hasBorder, isCircle }: AvatarProps): JSX.Element => {
   return (
     <div
-      className={`inline-flex ${avatarURL ? "" : "bg-orange-500"} justify-center relative items-center w-max h-max overflow-hidden
-        ${ className ?? " " }
-        ${ isCircle ? "rounded-full " : "rounded-lg "}
-        ${ hasBorder ? "ring-2 ring-slate-200 " : ""}
+      className={`inline-flex ${
+        avatarURL ? "" : "bg-orange-500"
+      } justify-center relative items-center w-max h-max overflow-hidden
+        ${className ?? " "}
+        ${isCircle ? "rounded-full " : "rounded-lg "}
+        ${hasBorder ? "ring-2 ring-slate-200 " : ""}
       `}
     >
       {avatarURL ? (
@@ -48,18 +51,31 @@ const CustomAvatar = ({ className, avatarURL, initials, alt, size, hasBorder, is
           /* loader={avatarLoader} */ src={avatarURL ? avatarURL : AvatarImage}
         />
       ) : (
-        <div className={`flex items-center justify-center font-bold leading-none text-slate-50 mb-0.25 text-${size}`} style={{width: size, height: size}}>{initials}</div>
+        <div
+          className={`flex items-center justify-center font-bold leading-none text-slate-50 mb-0.25 text-${size}`}
+          style={{ width: size, height: size }}
+        >
+          {initials}
+        </div>
       )}
     </div>
   );
 };
 
-const DefaultAvatar = ({ className, avatarURL, initials, alt, size, hasBorder, isCircle }: AvatarProps): JSX.Element => {
+const DefaultAvatar = ({
+  className,
+  avatarURL,
+  initials,
+  alt,
+  size,
+  hasBorder,
+  isCircle
+}: AvatarProps): JSX.Element => {
   return (
     <div
       className={`inline-flex ${avatarURL ? "" : "bg-orange-500"} justify-center relative items-center overflow-hidden
-        ${ isCircle ? "rounded-full " : "rounded-lg "}
-        ${ hasBorder ? "ring-2 ring-slate-200 " : ""} 
+        ${isCircle ? "rounded-full " : "rounded-lg "}
+        ${hasBorder ? "ring-2 ring-slate-200 " : ""}
         ${size === "sm" ? "w-6 h-6 " : size === "base" ? "w-8 h-8 " : size === "lg" ? "w-12 h-12 " : "w-8 h-8 "}
       `}
     >
@@ -68,14 +84,18 @@ const DefaultAvatar = ({ className, avatarURL, initials, alt, size, hasBorder, i
           className={`${className ? className : ""} object-cover`}
           alt={alt ? alt : "Avatar"}
           layout="fill"
-          /* loader={avatarLoader} */ src={avatarURL ? avatarURL : AvatarImage}
+          /* loader={avatarLoader} */ src={
+            avatarURL && typeof avatarURL === "string"
+              ? cachedImage(avatarURL, process.env.NEXT_PUBLIC_CLOUD_NAME)
+              : avatarURL || AvatarImage
+          }
         />
       ) : (
         <div
           className={`flex items-center justify-center font-bold leading-none text-slate-50 mb-0.25 ${
             size === "sm" ? "text-xs" : size === "base" ? "text-sm" : size === "lg" ? "text-lg" : "text-sm"
           }`}
-          style={{width: size, height: size}}
+          style={{ width: size, height: size }}
         >
           {initials}
         </div>
diff --git a/components/atoms/TableTitle/table-title.tsx b/components/atoms/TableTitle/table-title.tsx
index 558d86ad..8932e3ed 100644
--- a/components/atoms/TableTitle/table-title.tsx
+++ b/components/atoms/TableTitle/table-title.tsx
@@ -3,14 +3,16 @@ import React from "react";
 interface TableTitleProps {
     text: string;
     className?: string;
+    handleClick?: () => void;
 }
 
-const TableTitle: React.FC<TableTitleProps> = ({ className, text }) => {
+const TableTitle: React.FC<TableTitleProps> = ({ className, text, handleClick }) => {
+
   return (
-    <div className={`${className ? className : ""} font-semibold tracking-wide text-xs text-light-slate-11 uppercase`}>
+    <div className={`${className ? className : ""} font-semibold tracking-wide text-xs text-light-slate-11 uppercase`} onClick={handleClick}>
       {text}
     </div>
   );
 };
 
-export default TableTitle;
\ No newline at end of file
+export default TableTitle;
diff --git a/components/atoms/ToggleGroup/toggle-group.tsx b/components/atoms/ToggleGroup/toggle-group.tsx
new file mode 100644
index 00000000..37bb79b5
--- /dev/null
+++ b/components/atoms/ToggleGroup/toggle-group.tsx
@@ -0,0 +1,61 @@
+import * as GroupToggle from "@radix-ui/react-toggle-group";
+import { useState } from "react";
+import ToggleGroupItem from "../ToggleGroupItem/toggle-group-item";
+
+interface ToggleGroupProps {
+    children: React.ReactNode,
+    /** Allows the user to deselect an option, resulting in none being selected. */
+    allowNone?: boolean,
+    /** Decides which option will be selected by default. Provide the position of the element to be selected starting from 0. */
+    defaultSelection?: string | number,
+    /** Callback function that is called when the user selects an option, and the position of the selected element is passed into as string, starting with '0'. */
+    handleChange?: (value: string) => void,
+    className: string
+}
+
+/** A ToggleGroup component that allows the user to select one option from a list of options.
+ * Warning: Do NOT re-order the children after the component has been rendered.
+ *
+ * @example
+ * <ToggleGroup>
+ *      <>Option 1</> // React Fragment. Will use our styling
+ *      <>Option <span>2</span></> // Can be a nested component
+ *      <div>Option 3</div> // Any type of component can be used
+ * </ToggleGroup>
+ */
+const ToggleGroup = ({
+  children,
+  allowNone = false,
+  defaultSelection = "0",
+  handleChange,
+  className
+}: ToggleGroupProps) => {
+  const [value, setValue] = useState(defaultSelection+"");
+
+  const handleValueChange = (value: string) => {
+    if(value || allowNone) {
+      setValue(value);
+      handleChange && handleChange(value);
+    }
+  };
+
+  if(!children) return null;
+
+  return (
+    <GroupToggle.Root type="single" value={value} onValueChange={handleValueChange} className={`bg-light-slate-6 rounded-lg p-0.25 ${className && className}`}>
+      {
+        Array.isArray(children) ? children.map((child, index) => (
+          <ToggleGroupItem checked={value==`${index}`} value={`${index}`} key={index}>
+            {child}
+          </ToggleGroupItem>
+        ))
+          : <ToggleGroupItem checked={value=="0"} value={"0"}>
+            {children}
+          </ToggleGroupItem>
+      }
+    </GroupToggle.Root>
+  );
+
+};
+
+export default ToggleGroup;
diff --git a/components/atoms/ToggleGroupItem/toggle-group-item.tsx b/components/atoms/ToggleGroupItem/toggle-group-item.tsx
new file mode 100644
index 00000000..3ede2dcc
--- /dev/null
+++ b/components/atoms/ToggleGroupItem/toggle-group-item.tsx
@@ -0,0 +1,26 @@
+import * as GroupToggle from "@radix-ui/react-toggle-group";
+import clsx from "clsx";
+
+interface ToggleGroupItemProps {
+    checked: boolean;
+    value: string,
+    children: React.ReactNode
+}
+
+const ToggleGroupItem = ({
+  checked,
+  value,
+  children
+}: ToggleGroupItemProps) => {
+
+  return (
+    <GroupToggle.Item value={value}
+      className={clsx("text-sm text-light-slate-11 px-4 py-2 m-0.5 rounded-md font-semibold",
+        checked && "bg-light-slate-1 text-light-slate-12")}>
+      {children}
+    </GroupToggle.Item>
+  );
+};
+
+export default ToggleGroupItem
+;
diff --git a/components/molecules/CardProfile/card-profile.tsx b/components/molecules/CardProfile/card-profile.tsx
index 73d0fcdd..6516135f 100644
--- a/components/molecules/CardProfile/card-profile.tsx
+++ b/components/molecules/CardProfile/card-profile.tsx
@@ -14,7 +14,7 @@ interface CardProfileProps {
 
 const CardProfile = ({ githubAvatar, githubName, totalPRs, dateOfFirstPR }: CardProfileProps): JSX.Element => {
   return (
-    <div className="flex items-center gap-2">
+    <a href={`https://www.github.com/${githubName}`} target="_blank" className="flex items-center gap-2" rel="noreferrer">
       <Avatar size={40} avatarURL={githubAvatar ? githubAvatar : undefined} />
       <div>
         <div>
@@ -22,14 +22,19 @@ const CardProfile = ({ githubAvatar, githubName, totalPRs, dateOfFirstPR }: Card
         </div>
         <div className="flex gap-2   text-xs">
           <div className="flex text-xs items-center gap-1 text-light-slate-11">
-            <Icon size={12} alt="Total Pull-Requests" IconImage={ForkIcon} /> {totalPRs} PR{totalPRs === 1 ? "" : "s"}
+            {totalPRs !== undefined && (
+              <>
+                <Icon size={12} alt="Total Pull-Requests" IconImage={ForkIcon} /> {totalPRs} PR
+                {totalPRs === 1 ? "" : "s"}
+              </>
+            )}
           </div>
           <div className="flex items-center gap-1 text-light-slate-11">
             <Icon size={12} alt="Time from First Pull Request" IconImage={FirstPRIcon} /> {dateOfFirstPR}
           </div>
         </div>
       </div>
-    </div>
+    </a>
   );
 };
 
diff --git a/components/molecules/CardRepoList/card-repo-list.tsx b/components/molecules/CardRepoList/card-repo-list.tsx
index 84d1091a..1c5d6c6f 100644
--- a/components/molecules/CardRepoList/card-repo-list.tsx
+++ b/components/molecules/CardRepoList/card-repo-list.tsx
@@ -22,12 +22,18 @@ const CardRepoList = ({ repoList, limit = 5, fontSizeClassName }: CardRepoListPr
           {sanitizedRepoList
             .filter((repo, arrCount) => arrCount < limit)
             .map(({ repoName, repoIcon }, index) => (
-              <Tooltip key={index} content={repoName}>
-                <div className="flex gap-1  p-1 pr-2 border-[1px] border-light-slate-6 rounded-lg text-light-slate-12">
-                  <Icon IconImage={repoIcon} className="rounded-[4px] overflow-hidden" />
-                  <span className={`max-w-[45px] md:max-w-[100px] truncate ${fontSizeClassName}`}>{repoName}</span>
-                </div>
-              </Tooltip>
+              <>
+                {repoName && repoIcon ? (
+                  <Tooltip key={index} content={repoName}>
+                    <div className="flex gap-1  p-1 pr-2 border-[1px] border-light-slate-6 rounded-lg text-light-slate-12">
+                      <Icon IconImage={repoIcon} className="rounded-[4px] overflow-hidden" />
+                      <span className={`max-w-[45px] md:max-w-[100px] truncate ${fontSizeClassName}`}>{repoName}</span>
+                    </div>
+                  </Tooltip>
+                ) : (
+                  ""
+                )}
+              </>
             ))}
           <div>{repoList.length > limit ? `+${repoList.length - limit}` : null}</div>
         </>
diff --git a/components/molecules/ContributorHoverCard/contributor-hover-card.tsx b/components/molecules/ContributorHoverCard/contributor-hover-card.tsx
index 9023848c..3bd4ad3a 100644
--- a/components/molecules/ContributorHoverCard/contributor-hover-card.tsx
+++ b/components/molecules/ContributorHoverCard/contributor-hover-card.tsx
@@ -1,66 +1,54 @@
-import { VscGitMerge, VscGitPullRequest } from "react-icons/vsc";
+import { useRouter } from "next/router";
 
 import Text from "components/atoms/Typography/text";
 import CardProfile from "../CardProfile/card-profile";
-import CardRepoList from "../CardRepoList/card-repo-list";
-import TestRepoAvatar from "public/icons/test-repo-avatar.svg";
+import CardRepoList, { RepoList } from "../CardRepoList/card-repo-list";
+import { calcDistanceFromToday } from "lib/utils/date-utils";
+import ContributorTable from "../ContributorTable/contributor-table";
 
-const ContributorHoverCard = () => {
-  const randomGithubHistory = Array.apply(null, Array(4));
-  const testRepoList = [
-    {
-      repoName: "test4",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test5",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test6",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test6",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test6",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test6",
-      repoIcon: TestRepoAvatar
-    }
-  ];
+export interface ContributorsProfileType {
+  githubAvatar: string;
+  githubName: string;
+  totalPR: number;
+}
+interface ContributorHoverCardProps {
+  repoList: RepoList[];
+  githubAvatar: string;
+  githubName: string;
+  totalPR: number;
+  dateOfFirstPr: string;
+  topic?: string;
+  repositories?: number[];
+}
+const ContributorHoverCard = ({
+  repoList,
+  githubName,
+  totalPR,
+  dateOfFirstPr,
+  githubAvatar,
+  repositories
+}: ContributorHoverCardProps) => {
+  const router = useRouter();
+  const { filterName } = router.query;
+  const topic = filterName as string;
+
+  const calculatedDateFromToday = dateOfFirstPr ? calcDistanceFromToday(new Date(parseInt(dateOfFirstPr))) : "-";
   return (
-    <div className="w-[314px] gap-4 p-3 rounded-lg shadow-superlative flex flex-col">
+    <div className="w-[364px] bg-white gap-4 p-3 rounded-lg shadow-superlative flex flex-col">
       <div>
-        <CardProfile dateOfFirstPR="" githubName="Sungoldtech" totalPRs={45} />
-      </div>
-      <div className="bg-light-slate-3 text-xs px-2 py-1 text-light-slate-11 rounded">
-        <span>Latest PRs</span>
+        <CardProfile
+          dateOfFirstPR={calculatedDateFromToday}
+          githubAvatar={githubAvatar}
+          githubName={githubName}
+          totalPRs={totalPR}
+        />
       </div>
-      <div className="flex flex-col gap-1.5">
-        {randomGithubHistory.map((item, index) => (
-          <div key={index} className="flex gap-1 text-xs justify-between items-center">
-            <div>
-              <VscGitPullRequest className="text-[14px]" color="green" />
-            </div>
-            <div>
-              <Text className="!text-light-slate-12">fix: increase paginated response lim </Text>
-            </div>
-            <div className="text-light-slate-11">2mo</div>
-          </div>
-        ))}
+      <div className="">
+        <ContributorTable isHoverCard repositories={repositories} limit={5} contributor={githubName} topic={topic} />
       </div>
+
       <div>
-        <CardRepoList repoList={testRepoList} limit={3} />
-      </div>
-      <div className="w-full ">
-        <button className="w-full text-light-slate-11 text-xs bg-light-slate-1 rounded-lg border py-2">
-          Add a Filter
-        </button>
+        <CardRepoList repoList={repoList} limit={3} />
       </div>
     </div>
   );
diff --git a/components/molecules/ContributorTable/contributor-table.tsx b/components/molecules/ContributorTable/contributor-table.tsx
index 46e69c67..fd99b8c1 100644
--- a/components/molecules/ContributorTable/contributor-table.tsx
+++ b/components/molecules/ContributorTable/contributor-table.tsx
@@ -1,5 +1,6 @@
 import { useTopicContributorPRs } from "lib/hooks/useTopicContributorPRs";
 
+import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper";
 import LatestPrTableHeader from "../LatestPrTableHeader/latest-pr-table-header";
 import LatestPrTableRow from "../LatestPrTableRow/latest-pr-table-row";
 
@@ -16,15 +17,17 @@ interface CardTableProps {
   contributor: string;
   topic: string;
   repositories?: number[];
+  limit?: number;
+  isHoverCard?: boolean;
 }
 
-const ContributorTable = ({ contributor, topic, repositories }: CardTableProps): JSX.Element => {
-  const { data, isLoading } = useTopicContributorPRs(contributor, topic, repositories);
+const ContributorTable = ({ contributor, topic, repositories, limit, isHoverCard }: CardTableProps): JSX.Element => {
+  const { data, isLoading } = useTopicContributorPRs(contributor, topic, repositories, limit);
 
   return data.length > 0 ? (
     <>
       <div className="flex flex-col">
-        <LatestPrTableHeader />
+        <LatestPrTableHeader isHoverCard={isHoverCard} />
         <div className="flex flex-col gap-0.5">
           {data.map(
             (
@@ -35,7 +38,11 @@ const ContributorTable = ({ contributor, topic, repositories }: CardTableProps):
                 merged_at: prMergedTime,
                 created_at: prIssuedTime,
                 filesCount: noOfFilesChanged,
-                linesCount: noOfLinesChanged
+                linesCount: noOfLinesChanged,
+                number: prNumber,
+                repo_name: repoName,
+                repo_owner: repoOwner,
+                updated_at: prUpdatedTime
               },
               index
             ) => {
@@ -46,17 +53,21 @@ const ContributorTable = ({ contributor, topic, repositories }: CardTableProps):
                 prStatus,
                 merged,
                 noOfFilesChanged,
-                noOfLinesChanged
+                noOfLinesChanged,
+                repoName,
+                repoOwner,
+                prNumber,
+                prUpdatedTime
               };
 
-              return <LatestPrTableRow key={index} {...latestPrs} />;
+              return <LatestPrTableRow isHoverCard={isHoverCard} key={index} {...latestPrs} />;
             }
           )}
         </div>
       </div>
     </>
   ) : (
-    <div className="px-2 py-1">{isLoading ? "Loading..." : "There are currently no PRs..."}</div>
+    <div className="px-2 py-1">{isLoading ? <SkeletonWrapper height={20} /> : "There are currently no PRs..."}</div>
   );
 };
 
diff --git a/components/molecules/FilterHeader/filter-header.tsx b/components/molecules/FilterHeader/filter-header.tsx
index 3e66a271..6de3edff 100644
--- a/components/molecules/FilterHeader/filter-header.tsx
+++ b/components/molecules/FilterHeader/filter-header.tsx
@@ -7,6 +7,7 @@ import Title from "components/atoms/Typography/title";
 import ContextThumbnail from "components/atoms/ContextThumbnail/context-thumbnail";
 import FilterCard from "components/atoms/FilterCard/filter-card";
 import SuperativeSelector from "components/molecules/SuperlativeSelector/superlative-selector";
+import SortedBySelector from "components/molecules/SortedBySelector/sorted-by-selector";
 
 import useFilterOptions from "lib/hooks/useFilterOptions";
 import { captureAnayltics } from "lib/utils/analytics";
@@ -18,16 +19,38 @@ const HeaderFilter = () => {
   const filterOptions = useFilterOptions();
 
   const { filterValues } = useFilterPrefetch();
-  const { filterName, toolName, selectedFilter } = router.query;
+  const { filterName, toolName, selectedFilter, orderBy, sort } = router.query;
   const isHacktoberfest = filterName === "hacktoberfest";
+
   const filterBtnRouting = (filter: string) => {
     captureAnayltics("Filters", "toolsFilter", `${filter} applied`);
-    router.push(`/${filterName}/${toolName}/filter/${filter.toLocaleLowerCase()}`);
+
+    if (!orderBy && !sort) {
+      router.push(`/${filterName}/${toolName}/filter/${filter.toLocaleLowerCase()}`);
+      return;
+    }
+
+    router.push(`/${filterName}/${toolName}/filter/${filter.toLocaleLowerCase()}?orderBy=${orderBy}&sort=${sort}`);
   };
 
   const cancelFilterRouting = () => {
-    router.push(`/${filterName}/${toolName}`);
-  };  
+    if (!orderBy && !sort) {
+      router.push(`/${filterName}/${toolName}`);
+      return;
+    }
+
+    router.push(`/${filterName}/${toolName}?orderBy=${orderBy}&sort=${sort}`);
+  };
+
+  const cancelSorting = () => {
+    if (!selectedFilter) {
+      router.push(`/${filterName}/${toolName}`);
+      return;
+    }
+
+    router.push(`/${filterName}/${toolName}/filter/${selectedFilter}`);
+  };
+
   return (
     <>
       <div className="header-image mr-2 p-2 min-w-[130px]">
@@ -50,10 +73,11 @@ const HeaderFilter = () => {
             handleCancelClick={cancelFilterRouting}
             selected={Array.isArray(selectedFilter) ? selectedFilter.join("/") : selectedFilter}
           />
+          <SortedBySelector selected={orderBy as string} handleCancelClick={cancelSorting} />
         </div>
-      </div>    
+      </div>
     </>
   );
 };
 
-export default HeaderFilter;
\ No newline at end of file
+export default HeaderFilter;
diff --git a/components/molecules/HoverCardWrapper/hover-card-wrapper.tsx b/components/molecules/HoverCardWrapper/hover-card-wrapper.tsx
new file mode 100644
index 00000000..3ad4e43e
--- /dev/null
+++ b/components/molecules/HoverCardWrapper/hover-card-wrapper.tsx
@@ -0,0 +1,48 @@
+import { useRouter } from "next/router";
+
+import { useSingleContributor } from "lib/hooks/useSingleContributor";
+
+import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper";
+import ContributorHoverCard, { ContributorsProfileType } from "../ContributorHoverCard/contributor-hover-card";
+
+interface HoverCardWrapperProps {
+  username: string;
+  repositories?: number[];
+}
+const HoverCardWrapper = ({ username, repositories }: HoverCardWrapperProps) => {
+  const router = useRouter();
+  const { filterName } = router.query;
+  const topic = filterName as string;
+  const { data: contributor, isLoading: contributorLoading, isError } = useSingleContributor(username);
+
+  const repoList = (contributor[0]?.recent_repo_list || "").split(",").map((repo) => {
+    const [repoOwner, repoName] = repo.split("/");
+
+    return {
+      repoName,
+      repoIcon: `https://www.github.com/${repoOwner ?? "github"}.png?size=460`
+    };
+  });
+
+  const profile: ContributorsProfileType = {
+    githubAvatar: `https://www.github.com/${username}.png?size=60`,
+    githubName: username,
+    totalPR: contributor[0]?.recent_pr_total
+  };
+
+  return (
+    <>
+      <ContributorHoverCard
+        dateOfFirstPr={contributor[0]?.first_commit_time}
+        totalPR={profile.totalPR}
+        githubAvatar={profile.githubAvatar}
+        githubName={profile.githubName}
+        repoList={repoList}
+        topic={topic}
+        repositories={repositories}
+      />
+    </>
+  );
+};
+
+export default HoverCardWrapper;
diff --git a/components/molecules/NivoScatterChart/nivo-scatter-chart.tsx b/components/molecules/NivoScatterChart/nivo-scatter-chart.tsx
index 3e60aa34..4bb6b018 100644
--- a/components/molecules/NivoScatterChart/nivo-scatter-chart.tsx
+++ b/components/molecules/NivoScatterChart/nivo-scatter-chart.tsx
@@ -1,11 +1,14 @@
+import { useRouter } from "next/router";
 import { MouseEvent, useCallback, useState } from "react";
 
 import { ResponsiveScatterPlot } from "@nivo/scatterplot";
 import { animated } from "@react-spring/web";
 
 import humanizeNumber from "lib/utils/humanizeNumber";
+
 import ToggleOption from "components/atoms/ToggleOption/toggle-option";
 import Title from "components/atoms/Typography/title";
+import HoverCardWrapper from "../HoverCardWrapper/hover-card-wrapper";
 
 export interface ScatterChartDataItems {
   x: string | number;
@@ -23,8 +26,17 @@ interface ScatterPlotProps {
     data: ScatterChartDataItems[];
   }[];
   isMobile?: boolean;
+  repositories?: number[];
 }
-const NivoScatterPlot = ({ data, maxFilesModified, title, setShowBots, showBots, isMobile }: ScatterPlotProps) => {
+const NivoScatterPlot = ({
+  data,
+  maxFilesModified,
+  title,
+  setShowBots,
+  showBots,
+  isMobile,
+  repositories
+}: ScatterPlotProps) => {
   const [showMembers, setShowMembers] = useState<boolean>(false);
   const [isLogarithmic, setIsLogarithmic] = useState<boolean>(false);
 
@@ -80,9 +92,7 @@ const NivoScatterPlot = ({ data, maxFilesModified, title, setShowBots, showBots,
       </div>
       <div className="h-[400px]">
         <ResponsiveScatterPlot
-          tooltip={({ node }) => (
-            <div className="bg-light-slate-4 rounded px-3 py-0.5">{`${node.data.contributor} - ${node.data.y}`}</div>
-          )}
+          tooltip={({ node }) => <HoverCardWrapper repositories={repositories} username={node.data.contributor} />}
           nodeSize={isMobile ? 25 : 35}
           data={isMobile ? filteredData : data}
           margin={{ top: 30, right: isMobile ? 30 : 60, bottom: 70, left: isMobile ? 75 : 90 }}
@@ -133,25 +143,27 @@ const NivoScatterPlot = ({ data, maxFilesModified, title, setShowBots, showBots,
 };
 
 const CustomNode = (props: any) => {
+  const router = useRouter();
   const handleMouseEnter = useCallback(
     (event: React.MouseEvent) => props.onMouseEnter?.(props.node, event),
-    [props.node, props.onMouseEnter]
+    [props.node.data.contributor, props.onMouseEnter]
   );
   const handleMouseMove = useCallback(
     (event: MouseEvent) => props.onMouseMove?.(props.node, event),
-    [props.node, props.onMouseMove]
+    [props.node.data.contributor, props.onMouseMove]
   );
   const handleMouseLeave = useCallback(
     (event: React.MouseEvent) => props.onMouseLeave?.(props.node, event),
     [props.node, props.onMouseLeave]
   );
   const handleClick = useCallback(
-    (event: React.MouseEvent) => props.onClick?.(props.node, event),
+    (event: React.MouseEvent) => router.push(`/user/${props.node.data.contributor}`),
     [props.node, props.onClick]
   );
 
   return (
     <animated.image
+      className="cursor-pointer"
       width={35}
       height={35}
       r={props.style.size.to((size: number) => size / 2)}
diff --git a/components/molecules/Pagination/pagination.tsx b/components/molecules/Pagination/pagination.tsx
index 7b0cf76f..b0a04204 100644
--- a/components/molecules/Pagination/pagination.tsx
+++ b/components/molecules/Pagination/pagination.tsx
@@ -40,46 +40,54 @@ const Pagination = ({
   };
 
   return (
-    <div className=" w-max flex gap-x-4 items-center ">
-      <div className="flex items-center gap-x-4">
-        <button
-          className="text-light-slate-9 disabled:text-light-slate-7"
-          disabled={!hasPreviousPage ? true : false}
-          onClick={() => handlePrev()}
-        >
-          <RiArrowLeftSLine onClick={() => handlePrev()} className="text-2xl md:text-lg" />
-        </button>
-        {pages.map((page, index) => {
-          return (
-            index < pageSize && (
-              <div
-                key={index}
-                onClick={() => handleSelected(page)}
-                className={`${
-                  // this check  will be updated from page to currentPage when the implemetation of logic is ready
-                  selected === page &&
-                  "border !text-light-slate-12 shadow-paginate border-light-orange-10 bg-light-orange-2 shadow-search"
-                } cursor-pointer text-light-slate-11 transition   text-sm px-[13px]  py-[6px] rounded-lg `}
-              >
-                {page}
-              </div>
-            )
-          );
-        })}
+    <>
+      <div className=" w-max flex gap-x-4 items-center ">
+        <div className="flex items-center gap-x-4">
+          <button
+            className="text-light-slate-9 disabled:text-light-slate-7"
+            disabled={!hasPreviousPage ? true : false}
+            onClick={() => handlePrev()}
+          >
+            <RiArrowLeftSLine onClick={() => handlePrev()} className="text-2xl md:text-lg" />
+          </button>
+          {pages.map((page, index) => {
+            return (
+              index < pageSize && (
+                <div
+                  key={index}
+                  onClick={() => handleSelected(page)}
+                  className={`${
+                    // this check  will be updated from page to currentPage when the implemetation of logic is ready
+                    selected === page &&
+                    "border !text-light-slate-12 shadow-paginate border-light-orange-10 bg-light-orange-2 shadow-search"
+                  } cursor-pointer text-light-slate-11 transition   text-sm px-[13px]  py-[6px] rounded-lg `}
+                >
+                  {page}
+                </div>
+              )
+            );
+          })}
 
-        <button
-          className="text-light-slate-9 disabled:text-light-slate-7"
-          disabled={!hasNextPage ? true : false}
-          onClick={() => handleNext()}
+          <button
+            className="text-light-slate-9 disabled:text-light-slate-7"
+            disabled={!hasNextPage ? true : false}
+            onClick={() => handleNext()}
+          >
+            <RiArrowRightSLine className="text-2xl md:text-lg" />
+          </button>
+        </div>
+        <div
+          className={`${divisor && "md:border-r-2 border-r-light-slate-6"} text-sm text-light-slate-9    py-1 md:pr-4`}
         >
-          <RiArrowRightSLine className="text-2xl md:text-lg" />
-        </button>
-      </div>
-      <div className={`${divisor && "border-r-2 border-r-light-slate-6"} text-sm text-light-slate-9    py-1 pr-4`}>
-        Total {totalPage > 999 ? humanizeNumber(totalPage, null) : totalPage} pages
+          Total {totalPage > 999 ? humanizeNumber(totalPage, null) : totalPage} pages
+        </div>
+        {goToPage && (
+          <div className="hidden md:block">
+            <PaginationGotoPage totalPage={totalPage} page={page} setPage={handleSelected} name={""} />
+          </div>
+        )}
       </div>
-      {goToPage && <PaginationGotoPage page={page} setPage={handleSelected} name={""} />}
-    </div>
+    </>
   );
 };
 export default Pagination;
diff --git a/components/molecules/PaginationGotoPage/pagination-goto-page.tsx b/components/molecules/PaginationGotoPage/pagination-goto-page.tsx
index 802e9e74..2129190d 100644
--- a/components/molecules/PaginationGotoPage/pagination-goto-page.tsx
+++ b/components/molecules/PaginationGotoPage/pagination-goto-page.tsx
@@ -4,16 +4,22 @@ interface PaginationGotoPageProps {
   page: number;
   name: string;
   setPage: Function;
+  totalPage: number;
 }
 
-const PaginationGotoPage = ({ page, name, setPage }: PaginationGotoPageProps): JSX.Element => {
+const PaginationGotoPage = ({ page, name, setPage, totalPage }: PaginationGotoPageProps): JSX.Element => {
   const [pageNumber, setPageNumber] = useState<number | string>(page);
+  const [value, setValue] = useState<number | string>("");
   const handleGotoPage = (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
 
     if (pageNumber === page) return;
+    if (pageNumber > totalPage) {
+      return;
+    }
 
     setPage(pageNumber);
+    setValue("");
   };
 
   useEffect(() => {
@@ -26,8 +32,12 @@ const PaginationGotoPage = ({ page, name, setPage }: PaginationGotoPageProps): J
         type="text"
         name={name}
         id={name}
+        value={value}
         placeholder={`${pageNumber}`}
-        onChange={(e) => setPageNumber(e.target.value)}
+        onChange={(e) => {
+          setPageNumber(e.target.value);
+          setValue(e.target.value);
+        }}
         className="min-w-4 w-10 text-center text-sm text-light-slate-10 focus:outline-none border rounded-lg py-1 px-2"
       />
       <button
diff --git a/components/molecules/RepoRow/repo-row.tsx b/components/molecules/RepoRow/repo-row.tsx
index fab1223c..67738074 100644
--- a/components/molecules/RepoRow/repo-row.tsx
+++ b/components/molecules/RepoRow/repo-row.tsx
@@ -17,6 +17,7 @@ import { useContributionsList } from "lib/hooks/useContributionsList";
 import { useRepositoryCommits } from "lib/hooks/useRepositoryCommits";
 import { getCommitsLast30Days } from "lib/utils/get-recent-commits";
 import { getRelativeDays } from "lib/utils/date-utils";
+import getTotalPrs from "lib/utils/get-total-prs";
 
 import StackedAvatar from "../StackedAvatar/stacked-avatar";
 import PullRequestOverview from "../PullRequestOverview/pull-request-overview";
@@ -44,26 +45,6 @@ const getActivity = (total?: number, loading?: boolean) => {
   return <Pill icon={<ArrowTrendingDownIcon color="red" className="h-4 w-4" />} text="Low" color="red" />;
 };
 
-const getTotalPrs = (
-  openPrsCount?: number,
-  mergedPrsCount?: number,
-  closedPrsCount?: number,
-  draftPrsCount?: number
-): number => {
-  const open = openPrsCount || 0;
-  const merged = mergedPrsCount || 0;
-  const closed = closedPrsCount || 0;
-  const drafts = draftPrsCount || 0;
-
-  const total = open + closed + merged - drafts;
-
-  if (total <= 0) {
-    return 0;
-  }
-
-  return total;
-};
-
 const getPrsMerged = (total: number, merged: number): number => {
   if (total <= 0) {
     return 0;
diff --git a/components/molecules/SortedBySelector/sorted-by-selector.tsx b/components/molecules/SortedBySelector/sorted-by-selector.tsx
new file mode 100644
index 00000000..c5939697
--- /dev/null
+++ b/components/molecules/SortedBySelector/sorted-by-selector.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+
+import ContextFilterButton from "components/atoms/ContextFilterButton/context-filter-button";
+import Icon from "components/atoms/Icon/icon";
+
+import cancelIcon from "public/x-circle.svg";
+
+interface SortedBySelectorProps {
+  handleCancelClick: () => void;
+  className?: string;
+  selected?: string;
+}
+
+const SortedOrders = {
+  "name": "Repository",
+  "prsCount": "PR Count",
+  "prVelocityCount": "PR Velocity",
+  "spamPrsCount": "Spam"
+};
+
+const SortedBySelector: React.FC<SortedBySelectorProps> = ({
+  handleCancelClick,
+  selected
+}) => {
+
+  if (!selected) return <></>;
+
+  return (
+    <div className="max-w-max relative">
+      <ContextFilterButton isSelected={!!selected}>
+        <div className="flex">
+          <span className="text-dark-slate-10">Sorted by:</span>
+          <div className="flex items-center ml-1 text-light-slate-12">
+            {SortedOrders[(selected as keyof typeof SortedOrders)]}
+
+            <Icon
+              className="ml-2 cursor-pointer"
+              IconImage={cancelIcon}
+              onClick={() => handleCancelClick()}
+            />
+          </div>
+        </div>
+      </ContextFilterButton>
+    </div>
+  );
+};
+
+export default SortedBySelector;
diff --git a/components/molecules/SuperlativeSelector/superlative-selector.tsx b/components/molecules/SuperlativeSelector/superlative-selector.tsx
index 676d3fbd..f571d1f4 100644
--- a/components/molecules/SuperlativeSelector/superlative-selector.tsx
+++ b/components/molecules/SuperlativeSelector/superlative-selector.tsx
@@ -48,7 +48,7 @@ const SuperativeSelector: React.FC<SuperlativeSelectorProps> = ({
 
   const filterOption = filterOptions.find((option) => getFilterKey(option) === selected);
   const filterDescription = filterOption ? filterOption : selected;
-  
+
   return (
     <div className="max-w-max relative" ref={ref}>
       <ContextFilterButton onClick={toggleFilter} isSelected={!!selected}>
diff --git a/components/molecules/TableRepositoryName/table-repository-name.tsx b/components/molecules/TableRepositoryName/table-repository-name.tsx
index 545afee6..338a3924 100644
--- a/components/molecules/TableRepositoryName/table-repository-name.tsx
+++ b/components/molecules/TableRepositoryName/table-repository-name.tsx
@@ -17,7 +17,9 @@ const TableRepositoryName = ({ avatarURL, name, handle, topic, user }: TableRepo
   return (
     <div className="flex items-center gap-2.5">
       {/* Avatar */}
-      <Avatar className="shrink-0 min-w-10 min-h-10" size={40} avatarURL={avatarURL} isCircle={false} />
+      <a href={`https://www.github.com/${handle}/${name}`} target="_blank" rel="noreferrer">
+        <Avatar className="shrink-0 min-w-10 min-h-10" size={40} avatarURL={avatarURL} isCircle={false} />
+      </a>
 
       {/* Text */}
       <div className="flex flex-col justify-center">
diff --git a/components/organisms/ContributorProfilePage/contributor-profile-page.tsx b/components/organisms/ContributorProfilePage/contributor-profile-page.tsx
index c5258f2d..0fa9e4cd 100644
--- a/components/organisms/ContributorProfilePage/contributor-profile-page.tsx
+++ b/components/organisms/ContributorProfilePage/contributor-profile-page.tsx
@@ -5,15 +5,19 @@ import Text from "components/atoms/Typography/text";
 import CardHorizontalBarChart, {
   LanguageObject
 } from "components/molecules/CardHorizontalBarChart/card-horizontal-bar-chart";
+import color from "lib/utils/color.json";
 import ContributorProfileHeader from "components/molecules/ContributorProfileHeader/contributor-profile-header";
 import { ContributorObject } from "../ContributorCard/contributor-card";
 import CardLineChart from "components/molecules/CardLineChart/card-line-chart";
-import CardRepoList from "components/molecules/CardRepoList/card-repo-list";
+import CardRepoList, { RepoList } from "components/molecules/CardRepoList/card-repo-list";
 import LatestPrTableRow from "components/molecules/LatestPrTableRow/latest-pr-table-row";
 import LatestPrTableHeader from "components/molecules/LatestPrTableHeader/latest-pr-table-header";
 
 import TestRepoAvatar from "public/icons/test-repo-avatar.svg";
+import ContributorTable from "components/molecules/ContributorTable/contributor-table";
+import { useTopicContributorCommits } from "lib/hooks/useTopicContributorCommits";
 
+const colorKeys = Object.keys(color);
 interface PrObjectType {
   prName: string;
   prStatus: string;
@@ -21,6 +25,9 @@ interface PrObjectType {
   prMergedTime: string;
   noOfFilesChanged: number;
   noOfLinesChanged: number;
+  repoName: string;
+  repoOwner: string;
+  prNumber: number;
 }
 
 interface ContributorProfilePageProps {
@@ -28,106 +35,56 @@ interface ContributorProfilePageProps {
   topic?: string;
   repositories?: number[];
   listOfPRs: PrObjectType[];
+  githubAvatar?: string;
+  githubName: string;
+  langList: string[];
+  repoList: RepoList[];
 }
-const ContributorProfilePage = ({ contributor, topic, repositories, listOfPRs }: ContributorProfilePageProps) => {
-  const languageList: LanguageObject[] = [
-    { languageName: "html", percentageUsed: 40 },
-    { languageName: "php", percentageUsed: 30 },
-    { languageName: "javascript", percentageUsed: 10 },
-    { languageName: "python", percentageUsed: 10 },
-    { languageName: "css", percentageUsed: 10 }
-  ];
-  const chart = {
-    xAxis: {
-      type: "category",
-      boundaryGap: false,
-      axisLabel: true,
-      data: ["Jan 1, 2022", "Jan 15, 2022", "Feb 1, 2022"]
-    },
-    yAxis: {
-      type: "value",
-      splitNumber: 1,
-      axisLabel: false,
-      splitLine: {
-        lineStyle: {
-          type: "dashed"
-        }
-      }
-    },
-    grid: {
-      height: 100,
-      top: 0,
-      bottom: 0,
-      right: 0,
-      left: 0
-    },
-    series: [
-      {
-        data: [820, 932, 901, 934, 1290, 1330, 1320],
-        type: "line",
-        smooth: true,
-        showSymbol: false,
-        lineStyle: {
-          color: "#ff9800"
-        },
-        areaStyle: {
-          color: "#FFB74D",
-          opacity: 0.6
-        }
-      }
-    ]
-  };
+const ContributorProfilePage = ({
+  contributor,
+  topic,
+  repositories,
+  listOfPRs,
+  githubAvatar,
+  githubName,
+  langList,
+  repoList
+}: ContributorProfilePageProps) => {
+  const languageList = langList?.map((language) => {
+    const preparedLanguageKey = colorKeys.find((key) => key.toLowerCase() === language.toLowerCase());
 
-  const repoList = [
-    {
-      repoName: "Nextjs",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "opensauced/hot",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "React/react-native",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test4",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test5",
-      repoIcon: TestRepoAvatar
-    },
-    {
-      repoName: "test6",
-      repoIcon: TestRepoAvatar
-    }
-  ];
+    return {
+      languageName: preparedLanguageKey ? preparedLanguageKey : language,
+      percentageUsed: Math.round((1 / langList.length) * 100)
+    };
+  });
+
+  const { chart } = useTopicContributorCommits(githubName, "javascript", repositories);
 
   return (
-    <div>
-      <ContributorProfileHeader avatarUrl="https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1064&q=80" />
-      <div className="pt-24 md:px-10 lg:px-16 flex flex-col lg:flex-row justify-between">
-        <div className="flex flex-col w-64 gap-4">
+    <div className=" w-full">
+      <ContributorProfileHeader avatarUrl={githubAvatar} />
+      <div className="pt-24 px-4 md:px-10 lg:px-16 flex flex-col lg:flex-row lg:gap-40 w-full overflow-hidden justify-between">
+        <div className="flex flex-col min-w-[270px] gap-4 ">
           <div className="pb-6 border-b">
             <Title className="!text-2xl !text-light-slate-12" level={3}>
-              Nate Moore{" "}
+              {githubName}
             </Title>
-            <span className="text-light-slate-11 text-sm">@natemoo-re</span>
+            <span className="text-light-slate-11 text-sm">{`@${githubName}`}</span>
           </div>
           <div>
+            <p className="mb-4">Languages</p>
             <CardHorizontalBarChart languageList={languageList} />
           </div>
         </div>
-        <div>
+        <div className="flex-1">
           <div>
             <Title className="!text-light-slate-12 !text-xl" level={4}>
               Contribution Insights
             </Title>
           </div>
           <div className="bg-white mt-4 rounded-2xl border p-2 md:p-6">
-            <div className="flex flex-col lg:flex-row gap-2 md:gap-12 lg:gap-16 justify-between">
+            <div className=" flex-col hidden lg:flex-row gap-2 md:gap-12 lg:gap-16 justify-between">
               <div>
                 <span className="text-xs text-light-slate-11">PRs opened</span>
                 <div className="flex justify-between gap-2 items-end pr-8 mt-1">
@@ -176,11 +133,8 @@ const ContributorProfilePage = ({ contributor, topic, repositories, listOfPRs }:
               <CardRepoList limit={7} repoList={repoList} />
             </div>
 
-            <LatestPrTableHeader />
-            <div>
-              {listOfPRs.map((prs, index) => (
-                <LatestPrTableRow key={`${(prs.noOfFilesChanged * index) / 55}`} {...prs} />
-              ))}
+            <div className="mt-6">
+              <ContributorTable limit={15} contributor={githubName} topic={"javascript"} repositories={undefined} />
             </div>
             <div className="mt-8 text-light-slate-9 text-sm">
               <p>The data for these contributions is from publicly available open source projects on GitHub.</p>
diff --git a/components/organisms/Contributors/contributors.tsx b/components/organisms/Contributors/contributors.tsx
index c83a1533..922cde63 100644
--- a/components/organisms/Contributors/contributors.tsx
+++ b/components/organisms/Contributors/contributors.tsx
@@ -24,9 +24,9 @@ const Contributors = ({ repositories }: ContributorProps): JSX.Element => {
   const { filterName } = router.query;
   const topic = filterName as string;
   const { data, setLimit, meta, setPage, page, isError, isLoading } = useTopicContributions(10, repositories);
-  const range = useStore(state => state.range);
+  const range = useStore((state) => state.range);
   const store = useStore();
-  
+
   const contributorArray = isError
     ? []
     : data?.map((contributor) => {
@@ -130,7 +130,8 @@ const Contributors = ({ repositories }: ContributorProps): JSX.Element => {
                 onPageChange={function (page: number): void {
                   setPage(page);
                 }}
-                divisor={false}
+                divisor={true}
+                goToPage
               />
             </div>
           </div>
diff --git a/components/organisms/Dashboard/dashboard.tsx b/components/organisms/Dashboard/dashboard.tsx
index 3784e641..ccc03874 100644
--- a/components/organisms/Dashboard/dashboard.tsx
+++ b/components/organisms/Dashboard/dashboard.tsx
@@ -14,7 +14,6 @@ import { calcDaysFromToday } from "lib/utils/date-utils";
 import roundedImage from "lib/utils/roundedImages";
 import { useTopicContributions } from "lib/hooks/useTopicContributions";
 
-
 type ContributorPrMap = { [contributor: string]: DbRepoPR };
 
 interface DashboardProps {
@@ -82,7 +81,6 @@ export const Dashboard = ({ repositories }: DashboardProps): JSX.Element => {
     <div className="flex flex-col w-full gap-4">
       <section className="flex flex-wrap gap-4 items-center lg:flex-row lg:flex-nowrap max-w-full">
         <HighlightCard
-
           label="Contributors"
           icon="contributors"
           metricIncreases={compare1.allContributors - compare2.allContributors >= 0}
@@ -129,6 +127,7 @@ export const Dashboard = ({ repositories }: DashboardProps): JSX.Element => {
               data={[{ id: "Contributors", data: scatterChartData }]}
               maxFilesModified={maxFilesModified}
               isMobile={isMobile}
+              repositories={repositories}
             />
           </Card>
         </div>
diff --git a/components/organisms/Footer/footer.tsx b/components/organisms/Footer/footer.tsx
index 249c49a3..e878d684 100644
--- a/components/organisms/Footer/footer.tsx
+++ b/components/organisms/Footer/footer.tsx
@@ -21,7 +21,7 @@ const footerContext = [
       url: "https://app.termly.io/document/terms-of-use-for-saas/03e4e1c1-53ad-4fc4-b415-5c3f0e8c25ef",
       text: "Terms"
     },
-    status: { url: "https://api.opensauced.pizza/docs#/Health%20check%20service/healthStatusWeb", text: "Status" }
+    status: { url: "http://status.opensauced.pizza/", text: "Status" }
   },
   {
     hot: { url: "https://hot.opensauced.pizza", text: "hot.opensauced.pizza" },
diff --git a/components/organisms/Reports/reports.tsx b/components/organisms/Reports/reports.tsx
index 52cb6d3a..176a7b06 100644
--- a/components/organisms/Reports/reports.tsx
+++ b/components/organisms/Reports/reports.tsx
@@ -7,14 +7,13 @@ import Icon from "components/atoms/Icon/icon";
 import Title from "components/atoms/Typography/title";
 import ReportsHistory from "components/molecules/ReportsHistory/reports-history";
 import SelectReportsFilter from "components/molecules/SelectReportsFilter/select-reports-filter";
-import WaitlistButton from "components/molecules/WaitlistButton/waitlist-button";
+import StripeCheckoutButton from "../StripeCheckoutButton/stripe-checkout-button";
 
 import { Report } from "interfaces/report-type";
 
 import useFilterOptions from "lib/hooks/useFilterOptions";
 import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
 import getCurrentDate from "lib/utils/get-current-date";
-import useStore from "lib/store";
 
 const USERDEVICESTORAGENAME = "reportState";
 
@@ -24,13 +23,11 @@ interface ReportsProps {
   repositories?: number[];
 }
 
-const Reports = ({ waitlisted, hasReports, repositories }: ReportsProps): JSX.Element => {
+const Reports = ({ hasReports, repositories }: ReportsProps): JSX.Element => {
   const userDeviceState = localStorage.getItem(USERDEVICESTORAGENAME);
   const initialState = userDeviceState ? JSON.parse(userDeviceState as string) : [];
   const [reports, setReports] = useState<Report[]>(initialState);
-  const { sessionToken, user, signIn } = useSupabaseAuth();
-  const store = useStore();
-  const [submitting, setSubmitting] = useState(false);
+  const { user, signIn } = useSupabaseAuth();
 
   const filterOptions = useFilterOptions();
   const filterList = filterOptions.map((filter) => {
@@ -55,27 +52,6 @@ const Reports = ({ waitlisted, hasReports, repositories }: ReportsProps): JSX.El
     });
   };
 
-  const handleJoinClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
-    e.preventDefault();
-
-    setSubmitting(true);
-
-    try {
-      await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/waitlist`, {
-        method: "POST",
-        headers: {
-          Authorization: `Bearer ${sessionToken}`
-        }
-      });
-
-      store.setWaitlisted();
-    } catch (e) {
-      // handle error
-    } finally {
-      setSubmitting(false);
-    }
-  };
-
   return (
     <section className="flex flex-col w-full py-4 px-2 md:px-4 justify-center items-center">
       <div className="max-w-4xl">
@@ -111,7 +87,7 @@ const Reports = ({ waitlisted, hasReports, repositories }: ReportsProps): JSX.El
             )}
           </div>
         ) : (
-          <WaitlistButton waitlisted={waitlisted} submitting={submitting} handleJoinClick={handleJoinClick} />
+          <StripeCheckoutButton />
         )}
       </div>
     </section>
diff --git a/components/organisms/Repositories/repositories.tsx b/components/organisms/Repositories/repositories.tsx
index f42c2649..f5382363 100644
--- a/components/organisms/Repositories/repositories.tsx
+++ b/components/organisms/Repositories/repositories.tsx
@@ -1,6 +1,7 @@
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
 import { useRouter } from "next/router";
 import clsx from "clsx";
+import { FaArrowDown, FaArrowUp } from "react-icons/fa";
 
 import Select from "components/atoms/Select/custom-select";
 import TableTitle from "components/atoms/TableTitle/table-title";
@@ -10,23 +11,39 @@ import TableHeader from "components/molecules/TableHeader/table-header";
 
 import { useRepositoriesList } from "lib/hooks/useRepositoriesList";
 import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
+import useStore from "lib/store";
+import useFilterRepos from "lib/hooks/useFilterRepos";
 
 import RepositoriesTable, { classNames } from "../RepositoriesTable/repositories-table";
 import RepoNotIndexed from "./repository-not-indexed";
-import useStore from "lib/store";
 
 interface RepositoriesProps {
   repositories?: number[];
 }
 
+type FilterOptions = keyof DbRepo | "prsCount" | "activity";
+
+const renderArrow = (order: string | undefined) => {
+  return order === "ASC" ? (
+    <FaArrowUp className="text-light-slate-11 ml-2" fontSize={16} />
+  ) : (
+    <FaArrowDown className="text-light-slate-11 ml-2" fontSize={16} />
+  );
+};
+
 const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
   const { user } = useSupabaseAuth();
   const router = useRouter();
-  const { filterName, toolName, selectedFilter, userOrg } = router.query;
+
+  const { filterName, toolName, selectedFilter, userOrg, orderBy: orderByQuery, sort } = router.query;
+
+  const [orderBy, setOrderBy] = useState<FilterOptions | undefined>(orderByQuery as FilterOptions);
+  const [orderDirection, setOrderDirection] = useState<string | undefined>(sort as string);
+
   const username = userOrg ? user?.user_metadata.user_name : undefined;
   const topic = filterName as string;
   const store = useStore();
-  const range = useStore(state => state.range);
+  const range = useStore((state) => state.range);
   const {
     data: repoListData,
     meta: repoMeta,
@@ -38,6 +55,28 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
   } = useRepositoriesList(false, repositories);
   const filteredRepoNotIndexed = selectedFilter && !repoListIsLoading && !repoListIsError && repoListData.length === 0;
 
+  const { filteredRepos } = useFilterRepos(orderBy, orderDirection, repoListData);
+
+  const toggleFilter = (filter: FilterOptions) => {
+    const sortOrder = orderDirection === "ASC" ? "DESC" : "ASC";
+
+    if (filter === "activity") {
+      router.push(
+        `/${filterName}/${toolName}/filter/most-active${orderBy ? `?orderBy=${orderBy}&sort=${orderDirection}` : ""}`
+      );
+      return;
+    }
+
+    setOrderBy(filter);
+    setOrderDirection(sortOrder);
+
+    if (selectedFilter) {
+      router.push(`/${topic}/${toolName}/filter/${selectedFilter}?orderBy=${filter}&sort=${sortOrder}`);
+    } else {
+      router.push(`/${topic}/${toolName}?orderBy=${filter}&sort=${sortOrder}`);
+    }
+  };
+
   const handleOnSearch = (search?: string) => {
     if (search && /^[a-zA-Z0-9\-\.]+\/[a-zA-Z0-9\-\.]+$/.test(search)) {
       router.push(`/${topic}/${toolName}/filter/${search}`);
@@ -46,6 +85,10 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
     }
   };
 
+  useEffect(() => {
+    if (!orderByQuery) setOrderBy(undefined);
+  }, [orderByQuery]);
+
   useEffect(() => {
     setPage(1);
   }, [selectedFilter, setPage]);
@@ -68,27 +111,46 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
       <div className="flex flex-col rounded-lg overflow-hidden border">
         <div className="flex md:hidden justify-between  py-4 px-6 bg-light-slate-3 gap-2">
           <div className="flex-1">
-            <TableTitle text="Repository"></TableTitle>
+            <TableTitle text="Repository" />
           </div>
           <div className="flex-1">
             <TableTitle text="Pr Overview"></TableTitle>
           </div>
         </div>
         <div className="hidden md:flex py-4 px-6 bg-light-slate-3 gap-2">
-          <div className={clsx(classNames.cols.repository)}>
+          <div
+            className={clsx(classNames.cols.repository, "flex items-center cursor-pointer")}
+            onClick={() => toggleFilter("name")}
+          >
             <TableTitle text="Repository"></TableTitle>
+            {orderBy === "name" ? renderArrow(orderDirection) : null}
           </div>
-          <div className={clsx(classNames.cols.activity)}>
+          <div
+            className={clsx(classNames.cols.activity, "flex items-center cursor-pointer")}
+            onClick={() => toggleFilter("activity")}
+          >
             <TableTitle text="Activity"></TableTitle>
           </div>
-          <div className={clsx(classNames.cols.prOverview)}>
+          <div
+            className={clsx(classNames.cols.prOverview, "flex items-center cursor-pointer")}
+            onClick={() => toggleFilter("prsCount")}
+          >
             <TableTitle text="PR Overview"></TableTitle>
+            {orderBy === "prsCount" ? renderArrow(orderDirection) : null}
           </div>
-          <div className={clsx(classNames.cols.prVelocity)}>
+          <div
+            className={clsx(classNames.cols.prVelocity, "flex items-center cursor-pointer")}
+            onClick={() => toggleFilter("prVelocityCount")}
+          >
             <TableTitle text="PR Velocity"></TableTitle>
+            {orderBy === "prVelocityCount" ? renderArrow(orderDirection) : null}
           </div>
-          <div className={clsx(classNames.cols.spam)}>
+          <div
+            className={clsx(classNames.cols.spam, "flex items-center cursor-pointer")}
+            onClick={() => toggleFilter("spamPrsCount")}
+          >
             <TableTitle text="SPAM"></TableTitle>
+            {orderBy === "spamPrsCount" ? renderArrow(orderDirection) : null}
           </div>
           <div className={clsx(classNames.cols.contributors, "hidden lg:flex")}>
             <TableTitle text="Contributors"></TableTitle>
@@ -102,7 +164,7 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
           topic={topic}
           error={repoListIsError}
           loading={repoListIsLoading}
-          listOfRepositories={repoListData}
+          listOfRepositories={filteredRepos}
           user={username}
           repo={selectedFilter}
         />
@@ -145,7 +207,8 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
                   onPageChange={function (page: number): void {
                     setPage(page);
                   }}
-                  divisor={false}
+                  divisor={true}
+                  goToPage
                 />
               </div>
             </div>
diff --git a/components/organisms/StripeCheckoutButton/stripe-checkout-button.tsx b/components/organisms/StripeCheckoutButton/stripe-checkout-button.tsx
new file mode 100644
index 00000000..4e0bc507
--- /dev/null
+++ b/components/organisms/StripeCheckoutButton/stripe-checkout-button.tsx
@@ -0,0 +1,49 @@
+import Text from "components/atoms/Typography/text";
+import Button from "components/atoms/Button/button";
+import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
+
+import { getStripe } from "lib/utils/stripe-client";
+
+const StripeCheckoutButton = () => {
+  const { sessionToken } = useSupabaseAuth();
+  const handleFormSubmit = async (event: any) => {
+    event.preventDefault();
+
+    try {
+      const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/checkout/session`, {
+        method: "POST",
+        headers: {
+          "Authorization": `Bearer ${sessionToken}`
+        }
+      });
+
+      if (response.ok) {
+        const { sessionId } = await response.json();
+
+        const stripe = await getStripe();
+        stripe?.redirectToCheckout({ sessionId });
+      } else {
+        // display error to user
+        console.error(response.statusText);
+      }
+    } catch (e) {
+      //
+    }
+  };
+
+  return (
+    <div>
+      <Text className="">Upgrade to a subscription to gain access to generate custom reports!</Text>
+
+      <p className="flex justify-center py-4 px-2">
+        <form onSubmit={handleFormSubmit}>
+          <Button type="primary" className="w-52 h-[38px]">
+            Upgrade Access
+          </Button>
+        </form>
+      </p>
+    </div>
+  );
+};
+
+export default StripeCheckoutButton;
diff --git a/layouts/filter.tsx b/layouts/filter.tsx
index 2ae56290..3f8ee887 100644
--- a/layouts/filter.tsx
+++ b/layouts/filter.tsx
@@ -38,4 +38,4 @@ const FilterLayout = ({children}: {children: React.ReactNode}) => {
   );
 };
 
-export default FilterLayout;
\ No newline at end of file
+export default FilterLayout;
diff --git a/layouts/hub-page.tsx b/layouts/hub-page.tsx
index 0ac2eb5e..cf900443 100644
--- a/layouts/hub-page.tsx
+++ b/layouts/hub-page.tsx
@@ -46,4 +46,4 @@ const HubPageLayout = ({children}: {children: React.ReactNode}) => {
   );
 };
 
-export default HubPageLayout;
\ No newline at end of file
+export default HubPageLayout;
diff --git a/layouts/profile.tsx b/layouts/profile.tsx
new file mode 100644
index 00000000..5450a0bd
--- /dev/null
+++ b/layouts/profile.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+
+import Footer from "../components/organisms/Footer/footer";
+import TopNav from "../components/organisms/TopNav/top-nav";
+
+const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
+  return (
+    <>
+      <TopNav />
+      <div className="page-container flex min-h-[calc(100vh-(54px+95px))] flex-col items-center">
+        <main className="flex w-full flex-1 flex-col items-center bg-light-slate-2">{children}</main>
+      </div>
+      <Footer />
+    </>
+  );
+};
+
+export default ProfileLayout;
diff --git a/lib/hooks/useFilterRepos.ts b/lib/hooks/useFilterRepos.ts
new file mode 100644
index 00000000..505b8865
--- /dev/null
+++ b/lib/hooks/useFilterRepos.ts
@@ -0,0 +1,44 @@
+import { useEffect, useState } from "react";
+
+import dynamicSort from "lib/utils/dynamic-sort";
+import getTotalPrs from "lib/utils/get-total-prs";
+
+type FilterOptions = keyof DbRepo | "prsCount" | "activity" | undefined
+
+const useFilterRepos = (filterName: FilterOptions, orderDirection: string | undefined, repos: DbRepo[]) => {
+  const [filteredRepos, setFilteredRepos] = useState<DbRepo[]>(repos ?? []);
+
+  useEffect(() => {
+    if (repos.length > 0 && filterName) {
+      let sortedRepos: DbRepo[] = [];
+
+      if (filterName === "prsCount") {
+        sortedRepos = repos.sort((a, b) => {
+          if (isDbRepo(a) && isDbRepo(b)) {
+            return orderDirection === "ASC" ? getTotalPrs(a.openPrsCount, a.mergedPrsCount, a.closedPrsCount, a.draftPrsCount) - getTotalPrs(b.openPrsCount, b.mergedPrsCount, b.closedPrsCount, b.draftPrsCount) : getTotalPrs(b.openPrsCount, b.mergedPrsCount, b.closedPrsCount, b.draftPrsCount) - getTotalPrs(a.openPrsCount, a.mergedPrsCount, a.closedPrsCount, a.draftPrsCount);
+          }
+
+          return 0;
+        });
+      } else {
+        if (isKeyofDbRepo(filterName)) {
+          sortedRepos = repos.sort(dynamicSort(orderDirection === "ASC" ? filterName : `-${filterName}`));
+        }
+      }
+
+      setFilteredRepos(sortedRepos);
+    }
+  }, [repos, filterName, orderDirection]);
+
+  return { filteredRepos };
+};
+
+export default useFilterRepos;
+
+function isDbRepo(repo: any): repo is DbRepo {
+  return (repo as DbRepo).owner !== undefined;
+}
+
+function isKeyofDbRepo(repo: any): repo is keyof DbRepo {
+  return (repo as keyof DbRepo) !== undefined;
+}
diff --git a/lib/hooks/useRepoList.ts b/lib/hooks/useRepoList.ts
new file mode 100644
index 00000000..9d73b5c8
--- /dev/null
+++ b/lib/hooks/useRepoList.ts
@@ -0,0 +1,12 @@
+const useRepoList = (repos: string) => {
+  return repos.split(",").map((repo) => {
+    const [repoOwner, repoName] = repo.split("/");
+
+    return {
+      repoName,
+      repoIcon: `https://www.github.com/${repoOwner ?? "github"}.png?size=460`
+    };
+  });
+};
+
+export default useRepoList;
diff --git a/lib/hooks/useRepositoriesList.ts b/lib/hooks/useRepositoriesList.ts
index c74a8e75..c8a1f603 100644
--- a/lib/hooks/useRepositoriesList.ts
+++ b/lib/hooks/useRepositoriesList.ts
@@ -16,7 +16,6 @@ const useRepositoriesList = (skipFilters = false, repoIds: number[] = []) => {
   const [page, setPage] = useState(1);
   const [limit, setLimit] = useState(10);
   const range = useStore(state => state.range);
-
   const baseEndpoint = !skipFilters ? `${topic}/repos` : "repos/list";
   const pageQuery = page ? `page=${page}` : "";
   const filterQuery = getFilterQuery(selectedFilter);
diff --git a/lib/hooks/useSingleContributor.ts b/lib/hooks/useSingleContributor.ts
new file mode 100644
index 00000000..2350bbac
--- /dev/null
+++ b/lib/hooks/useSingleContributor.ts
@@ -0,0 +1,27 @@
+import getFilterQuery from "lib/utils/get-filter-query";
+import { useRouter } from "next/router";
+import useSWR from "swr";
+
+interface PaginatedRepoPRResponse {
+  readonly data: DbContribution[];
+  readonly meta: Meta;
+}
+
+const useSingleContributor = (contributor: string) => {
+  const router = useRouter();
+  const { filterName } = router.query;
+  const topic = filterName as string;
+
+  const baseEndPoint = `${topic}/contributions`;
+  const endPointString = `${baseEndPoint}?page=1&limit=1&range=30&contributor=${contributor}`;
+  const { data, error } = useSWR<PaginatedRepoPRResponse>(endPointString);
+
+  return {
+    data: data?.data ?? [],
+    meta: data?.meta ?? { itemCount: 0, limit: 0, page: 0, hasNextPage: false, hasPreviousPage: false, pageCount: 0 },
+    isLoading: !error && !data,
+    isError: !!error
+  };
+};
+
+export { useSingleContributor };
diff --git a/lib/hooks/useTopicContributorPRs.ts b/lib/hooks/useTopicContributorPRs.ts
index e830587a..ae3bba73 100644
--- a/lib/hooks/useTopicContributorPRs.ts
+++ b/lib/hooks/useTopicContributorPRs.ts
@@ -7,13 +7,13 @@ interface PaginatedContributorPRsResponse {
   readonly meta: Meta;
 }
 
-const useTopicContributorPRs = (contributor: string, topic: string, repoIds: number[] = []) => {
+const useTopicContributorPRs = (contributor: string, topic: string, repoIds: number[] = [], limit = 8) => {
   const router = useRouter();
   const { selectedFilter } = router.query;
   const baseEndpoint = `${topic}/${contributor}/prs`;
   const filterQuery = getFilterQuery(selectedFilter);
-  const reposQuery = repoIds.length > 0 ? `&repoIds=${repoIds.join(",")}`: "";
-  const endpointString = `${baseEndpoint}?limit=8${filterQuery}${reposQuery}`;
+  const reposQuery = repoIds.length > 0 ? `&repoIds=${repoIds.join(",")}` : "";
+  const endpointString = `${baseEndpoint}?limit=${limit}${filterQuery}${reposQuery}`;
 
   const { data, error, mutate } = useSWR<PaginatedContributorPRsResponse, Error>(endpointString);
 
diff --git a/lib/utils/cachedImages.ts b/lib/utils/cachedImages.ts
new file mode 100644
index 00000000..dac0feb2
--- /dev/null
+++ b/lib/utils/cachedImages.ts
@@ -0,0 +1,9 @@
+// Inspired by https://cloudinary.com/documentation/fetch_remote_images#remote_image_fetch_url
+// This is temporary and requires a Cloudinary account's cloud name.
+// In the future we can developer our own local image transformations.
+// https://github.com/open-sauced/insights/issues/468
+const cachedImage = (imageUrl: string, cloudName: string | undefined) => {
+  return cloudName ? `https://res.cloudinary.com/${cloudName}/image/fetch/${imageUrl}` : imageUrl;
+};
+
+export default cachedImage;
diff --git a/lib/utils/dynamic-sort.ts b/lib/utils/dynamic-sort.ts
new file mode 100644
index 00000000..fdd8981f
--- /dev/null
+++ b/lib/utils/dynamic-sort.ts
@@ -0,0 +1,30 @@
+type sortArg<T> = keyof T | `-${string & keyof T}`
+
+/**
+ * This function is used to sort an array of objects by a key.
+ * @param arg the key to sort by, or the key prefixed with a dash to sort in descending order
+ * @returns a number to be used in the sort function
+ */
+function dynamicSort<T>(arg: sortArg<T>) {
+  let sortOrder = 1;
+  let key: keyof T;
+
+  if(typeof arg === "string" && arg.startsWith("-")) {
+    sortOrder = -1;
+    key = arg.substring(1) as keyof T;
+  } else {
+    key = arg as string as keyof T;
+  }
+
+  return function (a: T,b: T) {
+    const result = (typeof a[key] === "string") ? (lowerFirst(a[key]) as string).localeCompare(lowerFirst(b[key]) as string) : (a[key] < b[key]) ? -1 : (a[key] > b[key]) ? 1 : 0;
+    return result * sortOrder;
+  };
+}
+
+export default dynamicSort;
+
+function lowerFirst<T>(str: T[keyof T]) {
+  if (typeof str !== "string") return str;
+  return str.charAt(0).toLowerCase() + str.slice(1);
+}
diff --git a/lib/utils/get-total-prs.ts b/lib/utils/get-total-prs.ts
new file mode 100644
index 00000000..30d54826
--- /dev/null
+++ b/lib/utils/get-total-prs.ts
@@ -0,0 +1,29 @@
+/**
+ * This function returns the total number of PRs
+ * @param openPrsCount the number of open PRs
+ * @param mergedPrsCount the number of merged PRs
+ * @param closedPrsCount the number of closed PRs
+ * @param draftPrsCount the number of draft PRs
+ * @returns the total number of PRs
+ */
+const getTotalPrs = (
+  openPrsCount?: number,
+  mergedPrsCount?: number,
+  closedPrsCount?: number,
+  draftPrsCount?: number
+): number => {
+  const open = openPrsCount || 0;
+  const merged = mergedPrsCount || 0;
+  const closed = closedPrsCount || 0;
+  const drafts = draftPrsCount || 0;
+
+  const total = open + closed + merged - drafts;
+
+  if (total <= 0) {
+    return 0;
+  }
+
+  return total;
+};
+
+export default getTotalPrs;
diff --git a/lib/utils/roundedImages.ts b/lib/utils/roundedImages.ts
index d9900830..7d75070b 100644
--- a/lib/utils/roundedImages.ts
+++ b/lib/utils/roundedImages.ts
@@ -1,7 +1,6 @@
 // Inspired by https://cloudinary.com/documentation/fetch_remote_images#remote_image_fetch_url
 // This is temporary and requires a Cloudinary account's cloud name.
-// In the future we can developer our own local image transformations.
-// https://github.com/open-sauced/insights/issues/468
+// https://github.com/open-sauced/insights/issues/680
 const roundedImage = (imageUrl: string, cloudName: string | undefined) => {
   return cloudName
     ? `https://res.cloudinary.com/${cloudName}/image/fetch/c_fill,g_face,h_300,w_300,bo_20px_solid_white,r_max/f_auto,e_shadow/${imageUrl}`
diff --git a/lib/utils/stripe-client.ts b/lib/utils/stripe-client.ts
new file mode 100644
index 00000000..7e9cf868
--- /dev/null
+++ b/lib/utils/stripe-client.ts
@@ -0,0 +1,13 @@
+import { loadStripe, Stripe } from "@stripe/stripe-js";
+
+let stripePromise: Promise<Stripe | null>;
+
+export const getStripe = () => {
+  if (!stripePromise) {
+    stripePromise = loadStripe(
+      process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_LIVE ?? process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ?? ""
+    );
+  }
+
+  return stripePromise;
+};
diff --git a/next-types.d.ts b/next-types.d.ts
index b4302663..c701686f 100644
--- a/next-types.d.ts
+++ b/next-types.d.ts
@@ -16,7 +16,7 @@ interface DbRepo {
   readonly amount?: string;
   readonly churn?: string;
   readonly spamPrsCount?: number;
-  readonly prVelocityCount?: number;  
+  readonly prVelocityCount?: number;
 }
 
 interface DbRepoPR {
@@ -30,6 +30,9 @@ interface DbRepoPR {
   readonly filesCount: number;
   protected linesCount: number;
   readonly merged: boolean;
+  readonly repo_owner: string;
+  readonly repo_name: string;
+  readonly number: number;
 }
 
 interface DbRepoCommit {
@@ -74,10 +77,10 @@ interface DbInsight {
   readonly accepted_repos: number;
   readonly all_contributors: number;
   readonly spam_contributors: number;
-  readonly accepted_contributors: number;    
+  readonly accepted_contributors: number;
   readonly all_repo_total: number;
   readonly spam_repo_total: number;
-  readonly accepted_repo_total: number;  
+  readonly accepted_repo_total: number;
 }
 
 interface DbUserInsight {
diff --git a/next.config.js b/next.config.js
index 8f401bba..6524fa7c 100644
--- a/next.config.js
+++ b/next.config.js
@@ -2,6 +2,12 @@
 module.exports = {
   reactStrictMode: true,
   images: {
-    domains: ["avatars.githubusercontent.com", "images.unsplash.com", "www.github.com", "github.com"]
+    domains: [
+      "avatars.githubusercontent.com",
+      "images.unsplash.com",
+      "www.github.com",
+      "github.com",
+      "res.cloudinary.com"
+    ]
   }
 };
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index 018420a3..c55c96d8 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1,12 +1,12 @@
 {
   "name": "@open-sauced/insights",
-  "version": "1.21.0",
+  "version": "1.22.0-beta.12",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@open-sauced/insights",
-      "version": "1.21.0",
+      "version": "1.22.0-beta.12",
       "hasInstallScript": true,
       "license": "MIT",
       "dependencies": {
@@ -20,8 +20,10 @@
         "@radix-ui/react-context-menu": "^1.0.0",
         "@radix-ui/react-dropdown-menu": "^1.0.0",
         "@radix-ui/react-switch": "^1.0.1",
+        "@radix-ui/react-toggle-group": "^1.0.1",
         "@radix-ui/react-tooltip": "^1.0.0",
         "@react-spring/web": "^9.5.5",
+        "@stripe/stripe-js": "^1.46.0",
         "@supabase/auth-helpers-nextjs": "^0.4.2",
         "@supabase/auth-helpers-react": "^0.3.1",
         "@supabase/gotrue-js": "^1.22.22",
@@ -40,6 +42,7 @@
         "react-icons": "^4.4.0",
         "react-loading-skeleton": "^3.1.0",
         "react-spring": "^9.5.5",
+        "stripe": "^11.3.0",
         "swr": "^1.3.0",
         "zustand": "^4.1.5"
       },
@@ -5848,6 +5851,127 @@
         "react": "^16.8 || ^17.0 || ^18.0"
       }
     },
+    "node_modules/@radix-ui/react-toggle": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.1.tgz",
+      "integrity": "sha512-hZIp9ZKnw4NwVqeB4evWBLa91ryaSJhAO0Ed82wkzRPgg/I29ypcY6SuBb3AMZW+GsuBZpIVujpCq+33TdEcyg==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/primitive": "1.0.0",
+        "@radix-ui/react-primitive": "1.0.1",
+        "@radix-ui/react-use-controllable-state": "1.0.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle-group": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.1.tgz",
+      "integrity": "sha512-eye/gYvzy82xtoSSeu5Pwlzrh6N2rOcDIwAI7xMatu622Qjlg64LtwB0PSh3iWdmn6Wqy1Fjo5twNPQsp0guiw==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/primitive": "1.0.0",
+        "@radix-ui/react-context": "1.0.0",
+        "@radix-ui/react-direction": "1.0.0",
+        "@radix-ui/react-primitive": "1.0.1",
+        "@radix-ui/react-roving-focus": "1.0.1",
+        "@radix-ui/react-toggle": "1.0.1",
+        "@radix-ui/react-use-controllable-state": "1.0.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-collection": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.1.tgz",
+      "integrity": "sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-compose-refs": "1.0.0",
+        "@radix-ui/react-context": "1.0.0",
+        "@radix-ui/react-primitive": "1.0.1",
+        "@radix-ui/react-slot": "1.0.1"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-primitive": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz",
+      "integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-slot": "1.0.1"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-roving-focus": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.1.tgz",
+      "integrity": "sha512-TB76u5TIxKpqMpUAuYH2VqMhHYKa+4Vs1NHygo/llLvlffN6mLVsFhz0AnSFlSBAvTBYVHYAkHAyEt7x1gPJOA==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/primitive": "1.0.0",
+        "@radix-ui/react-collection": "1.0.1",
+        "@radix-ui/react-compose-refs": "1.0.0",
+        "@radix-ui/react-context": "1.0.0",
+        "@radix-ui/react-direction": "1.0.0",
+        "@radix-ui/react-id": "1.0.0",
+        "@radix-ui/react-primitive": "1.0.1",
+        "@radix-ui/react-use-callback-ref": "1.0.0",
+        "@radix-ui/react-use-controllable-state": "1.0.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-slot": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
+      "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-compose-refs": "1.0.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz",
+      "integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-slot": "1.0.1"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
+    "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
+      "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-compose-refs": "1.0.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0 || ^18.0"
+      }
+    },
     "node_modules/@radix-ui/react-tooltip": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.0.tgz",
@@ -14739,6 +14863,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/@stripe/stripe-js": {
+      "version": "1.46.0",
+      "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.46.0.tgz",
+      "integrity": "sha512-dkm0zCEoRLu5rTnsIgwDf/QG2DKcalOT2dk1IVgMySOHWTChLyOvQwMYhEduGgLvyYWTwNhAUV4WOLPQvjwLwA=="
+    },
     "node_modules/@supabase/auth-helpers-nextjs": {
       "version": "0.4.2",
       "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.4.2.tgz",
@@ -29651,7 +29780,6 @@
       "version": "1.12.2",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
       "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
-      "dev": true,
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -31172,7 +31300,6 @@
       "version": "6.11.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
       "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-      "dev": true,
       "dependencies": {
         "side-channel": "^1.0.4"
       },
@@ -33624,7 +33751,6 @@
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
       "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-      "dev": true,
       "dependencies": {
         "call-bind": "^1.0.0",
         "get-intrinsic": "^1.0.2",
@@ -34602,6 +34728,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/stripe": {
+      "version": "11.3.0",
+      "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.3.0.tgz",
+      "integrity": "sha512-/ZVM1dmwrSZzNvN0vIixqhN79fgW2l3qBjw57bnFr5EQcRJTQkuJw6iEgb/EKg/40i2K/lWoa4gwnQkekC326Q==",
+      "dependencies": {
+        "@types/node": ">=8.1.0",
+        "qs": "^6.11.0"
+      },
+      "engines": {
+        "node": ">=12.*"
+      }
+    },
     "node_modules/style-loader": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz",
diff --git a/package.json b/package.json
index d9411066..8af597d1 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.21.0",
+  "version": "1.22.0-beta.12",
   "author": "TED Vortex <ted.vortex@gmail.com>",
   "private": true,
   "license": "MIT",
@@ -48,8 +48,10 @@
     "@radix-ui/react-context-menu": "^1.0.0",
     "@radix-ui/react-dropdown-menu": "^1.0.0",
     "@radix-ui/react-switch": "^1.0.1",
+    "@radix-ui/react-toggle-group": "^1.0.1",
     "@radix-ui/react-tooltip": "^1.0.0",
     "@react-spring/web": "^9.5.5",
+    "@stripe/stripe-js": "^1.46.0",
     "@supabase/auth-helpers-nextjs": "^0.4.2",
     "@supabase/auth-helpers-react": "^0.3.1",
     "@supabase/gotrue-js": "^1.22.22",
@@ -68,6 +70,7 @@
     "react-icons": "^4.4.0",
     "react-loading-skeleton": "^3.1.0",
     "react-spring": "^9.5.5",
+    "stripe": "^11.3.0",
     "swr": "^1.3.0",
     "zustand": "^4.1.5"
   },
diff --git a/pages/_app.tsx b/pages/_app.tsx
index ee279bfb..659904d7 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -51,7 +51,14 @@ function MyApp({ Component, pageProps }: ComponentWithPageLayout) {
       // Before unloading the app, we write back all the data into `localStorage`.
       window.addEventListener("beforeunload", () => {
         const appCache = JSON.stringify(Array.from(map.entries()));
-        localStorage.setItem("app-cache", appCache);
+        try {
+          localStorage.setItem("app-cache", appCache);
+        } catch (error) {
+          if (error instanceof Error && error.name === "QuotaExceededError")
+            return console.warn("⚠ local storage limit exceeded ⚠");
+
+          throw error;
+        }
       });
 
       // We still use the map for write & read for performance.
diff --git a/pages/pages/[userOrg]/[filterName]/[toolName]/filter/[...selectedFilter].tsx b/pages/pages/[userOrg]/[filterName]/[toolName]/filter/[...selectedFilter].tsx
index ece0bd5a..b72f5a44 100644
--- a/pages/pages/[userOrg]/[filterName]/[toolName]/filter/[...selectedFilter].tsx
+++ b/pages/pages/[userOrg]/[filterName]/[toolName]/filter/[...selectedFilter].tsx
@@ -30,4 +30,4 @@ const HubPage: WithPageLayout = () => {
 
 HubPage.PageLayout = HubPageLayout;
 
-export default HubPage;
\ No newline at end of file
+export default HubPage;
diff --git a/pages/user/[username].tsx b/pages/user/[username].tsx
new file mode 100644
index 00000000..ff06b334
--- /dev/null
+++ b/pages/user/[username].tsx
@@ -0,0 +1,41 @@
+import { useRouter } from "next/router";
+import useRepoList from "lib/hooks/useRepoList";
+import { useSingleContributor } from "lib/hooks/useSingleContributor";
+
+import ContributorProfilePage from "components/organisms/ContributorProfilePage/contributor-profile-page";
+import { ContributorsProfileType } from "components/molecules/ContributorHoverCard/contributor-hover-card";
+
+import HubPageLayout from "layouts/hub-page";
+import HubLayout from "layouts/hub";
+import ProfileLayout from "layouts/profile";
+
+const Contributor = () => {
+  const router = useRouter();
+  const { username } = router.query;
+  const contributorLogin = username as string;
+
+  const { data: contributor, isLoading: contributorLoading, isError } = useSingleContributor(contributorLogin);
+
+  const repoList = useRepoList(contributor[0]?.recent_repo_list || "");
+  const contributorLanguageList = (contributor[0]?.langs || "").split(",");
+  const profile: ContributorsProfileType = {
+    githubAvatar: `https://www.github.com/${contributorLogin}.png?size=300`,
+    githubName: contributorLogin,
+    totalPR: contributor[0]?.recent_pr_total
+  };
+
+  return (
+    <div className="w-full">
+      <ContributorProfilePage
+        repoList={repoList}
+        langList={contributorLanguageList}
+        githubName={profile.githubName}
+        githubAvatar={profile.githubAvatar}
+        listOfPRs={[]}
+      />
+    </div>
+  );
+};
+
+Contributor.PageLayout = ProfileLayout;
+export default Contributor;
diff --git a/stories/atoms/toggle-group.stories.tsx b/stories/atoms/toggle-group.stories.tsx
new file mode 100644
index 00000000..3b5b93d9
--- /dev/null
+++ b/stories/atoms/toggle-group.stories.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+import ToggleSwitch from "components/atoms/ToggleSwitch/toggle-switch";
+import ToggleGroup from "components/atoms/ToggleGroup/toggle-group";
+
+const storyConfig = {
+  title: "Design System/Atoms/ToggleGroup"
+};
+
+export default storyConfig;
+
+const ToggleSwitchTemplate: ComponentStory<typeof ToggleGroup> = (args) => <ToggleGroup {...args} />;
+export const Default = ToggleSwitchTemplate.bind({});
+export const AllowNone = ToggleSwitchTemplate.bind({});
+export const CustomItems = ToggleSwitchTemplate.bind({});
+Default.args = {
+  children: [
+    <>Option 1</>,
+    <>Option 2</>,
+    <>Option 3</>
+  ],
+  defaultSelection: "0",
+  className: "w-max"
+};
+AllowNone.args = {
+  children: [
+    <>Option 1</>,
+    <>Option 2</>,
+    <>Option 3</>
+  ],
+  allowNone: true,
+  className: "w-max"
+};
+CustomItems.args = {
+  children: [
+    <div key={1}>Option 1 w\ <a href="#" target="_blank">link</a></div>,
+    <p key={2}>Option 2 w\ <strong>strong</strong></p>,
+    <div key={3}>Option 3 w\ <div>div</div></div>
+  ],
+  defaultSelection: "0",
+  className: "w-max"
+};
diff --git a/stories/molecules/contributor-hover-card.stories.tsx b/stories/molecules/contributor-hover-card.stories.tsx
new file mode 100644
index 00000000..dd4c7d6a
--- /dev/null
+++ b/stories/molecules/contributor-hover-card.stories.tsx
@@ -0,0 +1,23 @@
+import { TipProvider } from "components/atoms/Tooltip/tooltip";
+import ContributorHoverCard from "components/molecules/ContributorHoverCard/contributor-hover-card";
+import { ComponentStory } from "@storybook/react";
+
+const storyConfig = {
+  title: "Design System/Molecules/ContributorHoverCard"
+};
+export default storyConfig;
+
+const ScatterChartTooltipTemplate: ComponentStory<typeof ContributorHoverCard> = (args) => (
+  <TipProvider>
+    <ContributorHoverCard {...args} />
+  </TipProvider>
+);
+
+export const ScatterChartTooltipStory = ScatterChartTooltipTemplate.bind({});
+
+ScatterChartTooltipStory.args = {
+  repoList: [],
+  githubName: "SunGoldTech",
+  totalPR: 23,
+  dateOfFirstPr: "3mo"
+};
diff --git a/stories/molecules/scatter-chart-tooltip.stories.tsx b/stories/molecules/scatter-chart-tooltip.stories.tsx
deleted file mode 100644
index 00d73558..00000000
--- a/stories/molecules/scatter-chart-tooltip.stories.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { TipProvider } from "components/atoms/Tooltip/tooltip";
-import ContributorHoverCard from "components/molecules/ContributorHoverCard/contributor-hover-card";
-
-const storyConfig = {
-  title: "Design System/Molecules/ContributorHoverCard"
-};
-export default storyConfig;
-
-export const ScatterChartTooltipStory = () => (
-  <TipProvider>
-    <ContributorHoverCard />
-  </TipProvider>
-);
diff --git a/stories/molecules/sorted-by-selector.stories.tsx b/stories/molecules/sorted-by-selector.stories.tsx
new file mode 100644
index 00000000..af45148a
--- /dev/null
+++ b/stories/molecules/sorted-by-selector.stories.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import SortedBySelector from "components/molecules/SortedBySelector/sorted-by-selector";
+import { ComponentStory } from "@storybook/react";
+
+const storyConfig = {
+  title: "Design System/Molecules/SortedBySelector",
+  component: "SortedBySelector"
+};
+export default storyConfig;
+
+const sortingOptions = ["name", "spamPrsCount", "prVelocityCount", "prsCount"];
+
+// SortedBySelector Template
+const SortedBySelectorTemplate: ComponentStory<typeof SortedBySelector> = (args) => (
+  <SortedBySelector {...args} />
+);
+
+export const Default = SortedBySelectorTemplate.bind({});
+export const Selected = SortedBySelectorTemplate.bind({});
+
+Default.args = {
+  selected: ""
+};
+Selected.args = {
+  selected: sortingOptions[0]
+};
diff --git a/stories/organisms/contributor-profile-page.stories.tsx b/stories/organisms/contributor-profile-page.stories.tsx
index 1ad7fb44..cc97e514 100644
--- a/stories/organisms/contributor-profile-page.stories.tsx
+++ b/stories/organisms/contributor-profile-page.stories.tsx
@@ -13,7 +13,10 @@ const listOfPRs = [
     prIssuedTime: "2mo",
     prMergedTime: "2mo",
     noOfFilesChanged: 13,
-    noOfLinesChanged: 837
+    noOfLinesChanged: 837,
+    repoName: "open-sauced",
+    repoOwner: "open-sauced",
+    prNumber: 1
   },
   {
     prName: "Merging some work",
@@ -21,7 +24,10 @@ const listOfPRs = [
     prIssuedTime: "2mo",
     prMergedTime: "2mo",
     noOfFilesChanged: 13,
-    noOfLinesChanged: 837
+    noOfLinesChanged: 837,
+    repoName: "open-sauced",
+    repoOwner: "open-sauced",
+    prNumber: 2
   },
   {
     prName: "Merging some work",
@@ -29,7 +35,10 @@ const listOfPRs = [
     prIssuedTime: "2mo",
     prMergedTime: "2mo",
     noOfFilesChanged: 13,
-    noOfLinesChanged: 837
+    noOfLinesChanged: 837,
+    repoName: "open-sauced",
+    repoOwner: "open-sauced",
+    prNumber: 3
   },
   {
     prName: "Merging some work",
@@ -37,7 +46,10 @@ const listOfPRs = [
     prIssuedTime: "2mo",
     prMergedTime: "2mo",
     noOfFilesChanged: 13,
-    noOfLinesChanged: 837
+    noOfLinesChanged: 837,
+    repoName: "open-sauced",
+    repoOwner: "open-sauced",
+    prNumber: 4
   }
 ];
 


Test patch:
diff --git a/components/molecules/LatestPrTableHeader/latest-pr-table-header.tsx b/components/molecules/LatestPrTableHeader/latest-pr-table-header.tsx
index 517c957a..2c5125b9 100644
--- a/components/molecules/LatestPrTableHeader/latest-pr-table-header.tsx
+++ b/components/molecules/LatestPrTableHeader/latest-pr-table-header.tsx
@@ -7,29 +7,34 @@ import { FaRegCheckCircle, FaRegDotCircle } from "react-icons/fa";
 
 import Text from "components/atoms/Typography/text";
 
-const LatestPrTableHeader = () => {
+interface LatestPrTableHeaderProps {
+  classNames?: string;
+  isHoverCard?: boolean;
+}
+
+const LatestPrTableHeader = ({ classNames, isHoverCard }: LatestPrTableHeaderProps) => {
   return (
-    <div className="flex gap-2 mt-8 items-center bg-light-slate-3 rounded-md px-2 py-1 pr-4">
+    <div className="flex gap-2 items-center bg-light-slate-3 rounded-md px-2 py-1 ">
       <div className="w-3/5">
         <Text className=" ">Latest PRs</Text>
       </div>
       <IconContext.Provider value={{ color: "gray", style: { width: 14, height: 14 } }}>
-        <div className="flex justify-end w-[calc(10%-4px)]">
+        <div className={`${isHoverCard && "ml-auto"} justify-end w-[calc(10%-4px)]`}>
           <FaRegDotCircle title="When Pull Request was created" />
         </div>
       </IconContext.Provider>
       <IconContext.Provider value={{ color: "gray", style: { width: 14, height: 14 } }}>
-        <div className="flex justify-end w-[calc(10%-4px)]">
+        <div className={`${isHoverCard ? "hidden" : "flex"} justify-end w-[calc(10%-4px)]`}>
           <FaRegCheckCircle title="When Pull Request was merged" />
         </div>
       </IconContext.Provider>
       <IconContext.Provider value={{ color: "gray", style: { width: 14, height: 14, strokeWidth: 0.3 } }}>
-        <div className="flex justify-end w-[calc(10%-4px)]">
+        <div className={`${isHoverCard ? "hidden" : "flex"} justify-end w-[calc(10%-4px)]`}>
           <GoDiff title="Files changed in Pull Request" />
         </div>
       </IconContext.Provider>
       <IconContext.Provider value={{ color: "gray", style: { width: 14, height: 14, strokeWidth: 0.5 } }}>
-        <div className="flex justify-end w-[calc(10%-4px)]">
+        <div className={`${isHoverCard ? "hidden" : "flex"} justify-end w-[calc(10%-4px)]`}>
           <BsFileDiff title="Lines changed in Pull Request" />
         </div>
       </IconContext.Provider>
diff --git a/components/molecules/LatestPrTableRow/latest-pr-table-row.tsx b/components/molecules/LatestPrTableRow/latest-pr-table-row.tsx
index 4a404210..0faca368 100644
--- a/components/molecules/LatestPrTableRow/latest-pr-table-row.tsx
+++ b/components/molecules/LatestPrTableRow/latest-pr-table-row.tsx
@@ -6,6 +6,7 @@ import { VscGitMerge, VscGitPullRequest, VscGitPullRequestClosed, VscGitPullRequ
 import { calcDistanceFromToday } from "lib/utils/date-utils";
 
 import Text from "components/atoms/Typography/text";
+import humanizeNumber from "lib/utils/humanizeNumber";
 
 interface LatestPrTableRowProps {
   prName: string;
@@ -15,6 +16,11 @@ interface LatestPrTableRowProps {
   prIssuedTime: string;
   noOfFilesChanged: number;
   noOfLinesChanged: number;
+  isHoverCard?: boolean;
+  repoName: string;
+  repoOwner: string;
+  prNumber: number;
+  prUpdatedTime: string;
 }
 const LatestPrTableRow = ({
   prName,
@@ -23,11 +29,16 @@ const LatestPrTableRow = ({
   prMergedTime,
   noOfFilesChanged,
   noOfLinesChanged,
-  merged
+  merged,
+  isHoverCard,
+  repoName,
+  repoOwner,
+  prNumber,
+  prUpdatedTime
 }: LatestPrTableRowProps) => {
   return (
     <div className="flex gap-2 items-center px-2 py-1">
-      <div className="flex cursor-default item-center gap-2 w-3/5">
+      <div className={`flex cursor-default item-center gap-2 w-3/5 ${isHoverCard && "w-5/6"}`}>
         {prStatus === "open" ? (
           <IconContext.Provider value={{ color: "green", style: { width: 14, height: 14, marginTop: 2 } }}>
             <VscGitPullRequest title="Open Pull Request" />
@@ -47,19 +58,25 @@ const LatestPrTableRow = ({
             <VscGitPullRequestDraft title="Draft Pull Request" />
           </IconContext.Provider>
         )}
-        <Text>{calcDistanceFromToday(new Date(parseInt(prIssuedTime, 10)))}</Text>
+        <Text title="updated date">{calcDistanceFromToday(new Date(parseInt(prUpdatedTime, 10)))}</Text>
         <Text title={prName} className="!text-light-slate-12 !w-32 md:!w-72  !truncate ">
-          {prName}
+          <a href={`https://github.com/${repoOwner}/${repoName}/pull/${prNumber}`} target="_blank" rel="noreferrer">
+            {prName}
+          </a>
         </Text>
       </div>
-      <div className="flex justify-end w-[calc(10%-4px)] text-sm text-light-slate-11">
+      <div className={`${isHoverCard && "ml-auto"} justify-end w-[calc(10%-4px)] text-sm text-light-slate-11`}>
         {calcDistanceFromToday(new Date(parseInt(prIssuedTime, 10)))}
       </div>
-      <div className="flex justify-end w-[calc(10%-4px)] text-sm text-light-slate-11">
+      <div className={`${isHoverCard ? "hidden" : "flex"} justify-end w-[calc(10%-4px)] text-sm text-light-slate-11`}>
         {merged ? calcDistanceFromToday(new Date(parseInt(prMergedTime, 10))) : "-"}
       </div>
-      <div className="flex justify-end w-[calc(10%-4px)] text-sm text-light-slate-11">{noOfFilesChanged}</div>
-      <div className="flex justify-end w-[calc(10%-4px)] text-sm text-light-slate-11">{noOfLinesChanged}</div>
+      <div className={`${isHoverCard ? "hidden" : "flex"} justify-end w-[calc(10%-4px)] text-sm text-light-slate-11`}>
+        {noOfFilesChanged >= 1000 ? humanizeNumber(noOfFilesChanged, "abbreviation") : noOfFilesChanged}
+      </div>
+      <div className={`${isHoverCard ? "hidden" : "flex"} justify-end w-[calc(10%-4px)] text-sm text-light-slate-11`}>
+        {noOfLinesChanged >= 1000 ? humanizeNumber(noOfLinesChanged, "abbreviation") : noOfLinesChanged}
+      </div>
     </div>
   );
 };
diff --git a/tests/lib/utils/dynamic-sort.test.ts b/tests/lib/utils/dynamic-sort.test.ts
new file mode 100644
index 00000000..39122dfa
--- /dev/null
+++ b/tests/lib/utils/dynamic-sort.test.ts
@@ -0,0 +1,89 @@
+import dynamicSort from "lib/utils/dynamic-sort";
+
+describe("[lib] dynamicSort()", () => {
+  const repos = [
+    {name: "open-sauced", prVelocityCount: 1, spam: 1},
+    {name: "react", prVelocityCount: 4, spam: 3},
+    {name: "next.js", prVelocityCount: 7, spam: 4},
+    {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0},
+    {name: "Redwood", prVelocityCount: 8, spam: 5}
+  ];
+
+  it("should sort the repos by name in ascending order", () => {
+    const result = repos.sort(dynamicSort("name"));
+    const expected = [
+      {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0},
+      {name: "next.js", prVelocityCount: 7, spam: 4},
+      {name: "open-sauced", prVelocityCount: 1, spam: 1},
+      {name: "react", prVelocityCount: 4, spam: 3},
+      {name: "Redwood", prVelocityCount: 8, spam: 5}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it("should sort the repos by name in descending order", () => {
+    const result = repos.sort(dynamicSort("-name"));
+    const expected = [
+      {name: "Redwood", prVelocityCount: 8, spam: 5},
+      {name: "react", prVelocityCount: 4, spam: 3},
+      {name: "open-sauced", prVelocityCount: 1, spam: 1},
+      {name: "next.js", prVelocityCount: 7, spam: 4},
+      {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it("should sort the repos by prVelocityCount in ascending order", () => {
+    const result = repos.sort(dynamicSort("prVelocityCount"));
+    const expected = [
+      {name: "open-sauced", prVelocityCount: 1, spam: 1},
+      {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0},
+      {name: "react", prVelocityCount: 4, spam: 3},
+      {name: "next.js", prVelocityCount: 7, spam: 4},
+      {name: "Redwood", prVelocityCount: 8, spam: 5}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it("should sort the repos by prVelocityCount in descending order", () => {
+    const result = repos.sort(dynamicSort("-prVelocityCount"));
+    const expected = [
+      {name: "Redwood", prVelocityCount: 8, spam: 5},
+      {name: "next.js", prVelocityCount: 7, spam: 4},
+      {name: "react", prVelocityCount: 4, spam: 3},
+      {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0},
+      {name: "open-sauced", prVelocityCount: 1, spam: 1}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it("should sort the repos by spam in ascending order", () => {
+    const result = repos.sort(dynamicSort("spam"));
+    const expected = [
+      {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0},
+      {name: "open-sauced", prVelocityCount: 1, spam: 1},
+      {name: "react", prVelocityCount: 4, spam: 3},
+      {name: "next.js", prVelocityCount: 7, spam: 4},
+      {name: "Redwood", prVelocityCount: 8, spam: 5}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it("should sort the repos by spam in descending order", () => {
+    const result = repos.sort(dynamicSort("-spam"));
+    const expected = [
+      {name: "Redwood", prVelocityCount: 8, spam: 5},
+      {name: "next.js", prVelocityCount: 7, spam: 4},
+      {name: "react", prVelocityCount: 4, spam: 3},
+      {name: "open-sauced", prVelocityCount: 1, spam: 1},
+      {name: "api.opensauced.pizza", prVelocityCount: 2, spam: 0}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+});
diff --git a/tests/lib/utils/get-total-prs.test.ts b/tests/lib/utils/get-total-prs.test.ts
new file mode 100644
index 00000000..0620f4ee
--- /dev/null
+++ b/tests/lib/utils/get-total-prs.test.ts
@@ -0,0 +1,36 @@
+import getTotalPrs from "lib/utils/get-total-prs";
+
+describe("[lib] getTotalPRs()", () => {
+  const repos = [
+    {name: "open-sauced", openPrsCount: 6, mergedPrsCount: 3, closedPrsCount: 1, draftPrsCount: 2},
+    {name: "react", openPrsCount: 9, mergedPrsCount: 5, closedPrsCount: 3, draftPrsCount: 4},
+    {name: "next.js", openPrsCount: 13, mergedPrsCount: 7, closedPrsCount: 2, draftPrsCount: 1},
+    {name: "api.opensauced.pizza", openPrsCount: 2, mergedPrsCount: 0, closedPrsCount: 0, draftPrsCount: 0}
+  ];
+
+  it("should sort the repos based of the number of total prs in ascending order", () => {
+    const result = repos.sort((a, b) => getTotalPrs(a.openPrsCount, a.mergedPrsCount, a.closedPrsCount, a.draftPrsCount) - getTotalPrs(b.openPrsCount, b.mergedPrsCount, b.closedPrsCount, b.draftPrsCount));
+
+    const expected = [
+      {name: "api.opensauced.pizza", openPrsCount: 2, mergedPrsCount: 0, closedPrsCount: 0, draftPrsCount: 0},
+      {name: "open-sauced", openPrsCount: 6, mergedPrsCount: 3, closedPrsCount: 1, draftPrsCount: 2},
+      {name: "react", openPrsCount: 9, mergedPrsCount: 5, closedPrsCount: 3, draftPrsCount: 4},
+      {name: "next.js", openPrsCount: 13, mergedPrsCount: 7, closedPrsCount: 2, draftPrsCount: 1}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it("should sort the repos based of the number of total prs in descending order", () => {
+    const result = repos.sort((a, b) => getTotalPrs(b.openPrsCount, b.mergedPrsCount, b.closedPrsCount, b.draftPrsCount) - getTotalPrs(a.openPrsCount, a.mergedPrsCount, a.closedPrsCount, a.draftPrsCount));
+
+    const expected = [
+      {name: "next.js", openPrsCount: 13, mergedPrsCount: 7, closedPrsCount: 2, draftPrsCount: 1},
+      {name: "react", openPrsCount: 9, mergedPrsCount: 5, closedPrsCount: 3, draftPrsCount: 4},
+      {name: "open-sauced", openPrsCount: 6, mergedPrsCount: 3, closedPrsCount: 1, draftPrsCount: 2},
+      {name: "api.opensauced.pizza", openPrsCount: 2, mergedPrsCount: 0, closedPrsCount: 0, draftPrsCount: 0}
+    ];
+
+    expect(result).toEqual(expected);
+  });
+});