Repository: Zipstack/unstract
Pull request #1261: [FEAT] Verticals UI
Author: login: jaags-dev
Created: 2025-04-22T12:02:26Z | Merged: 2025-04-25T10:56:07Z

## What

- Verticals UI related changes

## Why

- A portal to access and test verticals

## How

-

## Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

-

## Database Migrations

- 

## Env Config

- 

## Relevant Docs

-

## Related Issues or PRs

-

## Dependencies Versions

-

## Notes on Testing

-

## Screenshots

## Checklist

I have read and understood the [Contribution Guidelines](https://docs.unstract.com/unstract/contributing/unstract/).



Diff:
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5daba59259..5c6284d255 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,6 +9,7 @@
       "version": "0.1.0",
       "dependencies": {
         "@ant-design/icons": "^5.1.4",
+        "@monaco-editor/react": "^4.7.0",
         "@react-awesome-query-builder/antd": "^6.6.10",
         "@react-pdf-viewer/core": "^3.12.0",
         "@react-pdf-viewer/default-layout": "^3.12.0",
@@ -3498,6 +3499,27 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "optional": true
     },
+    "node_modules/@monaco-editor/loader": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
+      "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
+      "dependencies": {
+        "state-local": "^1.0.6"
+      }
+    },
+    "node_modules/@monaco-editor/react": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
+      "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
+      "dependencies": {
+        "@monaco-editor/loader": "^1.5.0"
+      },
+      "peerDependencies": {
+        "monaco-editor": ">= 0.25.0 < 1",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
     "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
       "version": "5.1.1-v1",
       "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -14305,6 +14327,12 @@
         "node": "*"
       }
     },
+    "node_modules/monaco-editor": {
+      "version": "0.52.2",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
+      "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
+      "peer": true
+    },
     "node_modules/mri": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -18703,6 +18731,11 @@
       "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
       "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
     },
+    "node_modules/state-local": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
+      "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
+    },
     "node_modules/statuses": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -23324,6 +23357,22 @@
         }
       }
     },
+    "@monaco-editor/loader": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
+      "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
+      "requires": {
+        "state-local": "^1.0.6"
+      }
+    },
+    "@monaco-editor/react": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
+      "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
+      "requires": {
+        "@monaco-editor/loader": "^1.5.0"
+      }
+    },
     "@nicolo-ribaudo/eslint-scope-5-internals": {
       "version": "5.1.1-v1",
       "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -31218,6 +31267,12 @@
         "moment": "^2.29.4"
       }
     },
+    "monaco-editor": {
+      "version": "0.52.2",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
+      "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
+      "peer": true
+    },
     "mri": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -34205,6 +34260,11 @@
       "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
       "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
     },
+    "state-local": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
+      "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
+    },
     "statuses": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 4b196cfd46..fcbc59f232 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,6 +4,7 @@
   "private": true,
   "dependencies": {
     "@ant-design/icons": "^5.1.4",
+    "@monaco-editor/react": "^4.7.0",
     "@react-awesome-query-builder/antd": "^6.6.10",
     "@react-pdf-viewer/core": "^3.12.0",
     "@react-pdf-viewer/default-layout": "^3.12.0",
diff --git a/frontend/src/components/helpers/auth/PersistentLogin.js b/frontend/src/components/helpers/auth/PersistentLogin.js
index 80c7711528..a48d3e748d 100644
--- a/frontend/src/components/helpers/auth/PersistentLogin.js
+++ b/frontend/src/components/helpers/auth/PersistentLogin.js
@@ -13,11 +13,9 @@ let setSelectedProduct;
 let SELECTED_PRODUCT;
 let PRODUCT_NAMES = {};
 try {
-  selectedProductStore = require("../../../plugins/llm-whisperer/store/select-product-store.js");
-  SELECTED_PRODUCT =
-    require("../../../plugins/llm-whisperer/helper").SELECTED_PRODUCT;
-  PRODUCT_NAMES =
-    require("../../../plugins/llm-whisperer/helper").PRODUCT_NAMES;
+  selectedProductStore = require("../../../plugins/store/select-product-store.js");
+  SELECTED_PRODUCT = require("../../../plugins/helper/common").SELECTED_PRODUCT;
+  PRODUCT_NAMES = require("../../../plugins/helper/common").PRODUCT_NAMES;
 } catch {
   // Ignore if hook not available
 }
diff --git a/frontend/src/components/helpers/auth/RequireAuth.js b/frontend/src/components/helpers/auth/RequireAuth.js
index d98ba3c561..c846b6bf17 100644
--- a/frontend/src/components/helpers/auth/RequireAuth.js
+++ b/frontend/src/components/helpers/auth/RequireAuth.js
@@ -18,8 +18,9 @@ try {
 }
 let selectedProductStore;
 let isLlmWhisperer;
+let isVerticals;
 try {
-  selectedProductStore = require("../../../plugins/llm-whisperer/store/select-product-store.js");
+  selectedProductStore = require("../../../plugins/store/select-product-store.js");
 } catch {
   // do nothing
 }
@@ -40,8 +41,19 @@ const RequireAuth = () => {
   } catch (error) {
     // Do nothing
   }
+  try {
+    isVerticals =
+      selectedProductStore.useSelectedProductStore(
+        (state) => state?.selectedProduct
+      ) === "verticals";
+  } catch (error) {
+    // Do nothing
+  }
 
-  const currOrgName = getOrgNameFromPathname(pathname, isLlmWhisperer);
+  const currOrgName = getOrgNameFromPathname(
+    pathname,
+    isLlmWhisperer || isVerticals
+  );
   useEffect(() => {
     if (!sessionDetails?.isLoggedIn) {
       return;
@@ -53,6 +65,8 @@ const RequireAuth = () => {
   let navigateTo = `/${orgName}/onboard`;
   if (isLlmWhisperer) {
     navigateTo = `/llm-whisperer/${orgName}/playground`;
+  } else if (isVerticals) {
+    navigateTo = `/verticals/`;
   } else if (onboardCompleted(adapters)) {
     navigateTo = `/${orgName}/${homePagePath}`;
   }
diff --git a/frontend/src/components/helpers/auth/RequireGuest.js b/frontend/src/components/helpers/auth/RequireGuest.js
index a49c02035f..3cee19c2db 100644
--- a/frontend/src/components/helpers/auth/RequireGuest.js
+++ b/frontend/src/components/helpers/auth/RequireGuest.js
@@ -8,8 +8,9 @@ import {
 import { useSessionStore } from "../../../store/session-store";
 let selectedProductStore;
 let isLlmWhisperer;
+let isVerticals;
 try {
-  selectedProductStore = require("../../../plugins/llm-whisperer/store/select-product-store.js");
+  selectedProductStore = require("../../../plugins/store/select-product-store.js");
 } catch {
   // do nothing
 }
@@ -27,10 +28,20 @@ const RequireGuest = () => {
   } catch (error) {
     // Do nothing
   }
+  try {
+    isVerticals =
+      selectedProductStore.useSelectedProductStore(
+        (state) => state?.selectedProduct
+      ) === "verticals";
+  } catch (error) {
+    // Do nothing
+  }
 
   let navigateTo = `/${orgName}/onboard`;
   if (isLlmWhisperer) {
     navigateTo = `/llm-whisperer/${orgName}/playground`;
+  } else if (isVerticals) {
+    navigateTo = `/verticals/`;
   } else if (onboardCompleted(adapters)) {
     navigateTo = `/${orgName}/${homePagePath}`;
   }
diff --git a/frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx b/frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
index 5f07a65c76..d4a04278ab 100644
--- a/frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
+++ b/frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
@@ -52,7 +52,7 @@ try {
 let selectedProductStore;
 let selectedProduct;
 try {
-  selectedProductStore = require("../../../plugins/llm-whisperer/store/select-product-store.js");
+  selectedProductStore = require("../../../plugins/store/select-product-store.js");
 } catch {
   // Ignore if hook not available
 }
diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
index 4b35d62215..963664d6d1 100644
--- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
+++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
@@ -15,6 +15,7 @@ import {
   DownloadOutlined,
   FileProtectOutlined,
   LikeOutlined,
+  LoginOutlined,
 } from "@ant-design/icons";
 import { useEffect, useState, useMemo, useCallback } from "react";
 import { useNavigate, useLocation } from "react-router-dom";
@@ -49,7 +50,7 @@ let selectedProductStore;
 let selectedProduct;
 
 try {
-  selectedProductStore = require("../../../plugins/llm-whisperer/store/select-product-store.js");
+  selectedProductStore = require("../../../plugins/store/select-product-store.js");
 } catch {
   // Ignore if hook not available
 }
@@ -98,6 +99,12 @@ const CustomLogo = ({ onClick, className }) => {
   }
   return <UnstractLogo className={className} onClick={onClick} />;
 };
+let APIHubLogo;
+try {
+  APIHubLogo = require("../../../plugins/assets/index.js").APIHubLogo;
+} catch {
+  // Ignore if hook not available
+}
 
 let unstractSubscriptionPlan;
 let unstractSubscriptionPlanStore;
@@ -116,7 +123,7 @@ try {
 function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
   const navigate = useNavigate();
   const { sessionDetails } = useSessionStore();
-  const { orgName, allOrganization, orgId } = sessionDetails;
+  const { orgName, allOrganization, orgId, isLoggedIn } = sessionDetails;
   const baseUrl = getBaseUrl();
   const onBoardUrl = `${baseUrl}/${orgName}/onboard`;
   const logout = useLogout();
@@ -154,6 +161,7 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
   }, [unstractSubscriptionPlan]);
 
   const isUnstract = !(selectedProduct && selectedProduct !== "unstract");
+  const isAPIHub = selectedProduct && selectedProduct === "verticals";
 
   // Check user role and whether the onboarding is incomplete
   useEffect(() => {
@@ -332,12 +340,23 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
       });
     }
 
+    const handleLogin = () => {
+      const baseUrl = getBaseUrl();
+      const newURL = baseUrl + "/api/v1/login";
+      window.location.href = newURL;
+    };
+
     // Logout
+
+    const handleClick = isLoggedIn ? logout : handleLogin;
+    const icon = isLoggedIn ? <LogoutOutlined /> : <LoginOutlined />;
+    const label = isLoggedIn ? "Logout" : "Login";
+
     menuItems.push({
       key: "2",
       label: (
-        <Button onClick={logout} className="logout-button">
-          <LogoutOutlined /> Logout
+        <Button onClick={handleClick} icon={icon} className="logout-button">
+          {label}
         </Button>
       ),
     });
@@ -374,6 +393,8 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
               navigate(`/${sessionDetails?.orgName}/${homePagePath}`)
             }
           />
+        ) : isAPIHub ? (
+          APIHubLogo && <APIHubLogo className="topbar-logo" />
         ) : (
           WhispererLogo && <WhispererLogo className="topbar-logo" />
         )}
diff --git a/frontend/src/hooks/useSessionValid.js b/frontend/src/hooks/useSessionValid.js
index 592a8599e2..18161e47ee 100644
--- a/frontend/src/hooks/useSessionValid.js
+++ b/frontend/src/hooks/useSessionValid.js
@@ -22,7 +22,7 @@ let selectedProductStore;
 let PRODUCT_NAMES = {};
 
 try {
-  selectedProductStore = require("../plugins/llm-whisperer/store/select-product-store.js");
+  selectedProductStore = require("../plugins/store/select-product-store.js");
   PRODUCT_NAMES = require("../plugins/llm-whisperer/helper").PRODUCT_NAMES;
 } catch {
   // Ignore if hook not available
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 6d74f213e9..3fdbf2ea95 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -203,3 +203,35 @@ body {
 .text-align-center {
   text-align: center;
 }
+
+.mar-24 {
+  margin: 24px;
+}
+
+.mar-16 {
+  margin: 16px;
+}
+
+.pad-24 {
+  padding: 24px;
+}
+
+.mar-bottom-16 {
+  margin-bottom: 16px;
+}
+
+.mar-top-24 {
+  margin-top: 24px;
+}
+
+.height-100vh {
+  height: 100vh;
+}
+
+.mar-bottom-12{
+  margin-bottom: 12px;
+}
+
+.mar-0{
+  margin: 0;
+}
diff --git a/frontend/src/routes/Router.jsx b/frontend/src/routes/Router.jsx
index 08c9dd2cbe..fc7e7b508a 100644
--- a/frontend/src/routes/Router.jsx
+++ b/frontend/src/routes/Router.jsx
@@ -49,6 +49,14 @@ try {
   // Do nothing, Not-found Page will be triggered.
 }
 
+let verticalsRouter;
+try {
+  verticalsRouter =
+    require("../plugins/routes/useVerticalsRoutes.js").useVerticalsRoutes;
+} catch (err) {
+  // Do nothing, Not-found Page will be triggered.
+}
+
 try {
   SelectProduct =
     require("../plugins/select-product/SelectProduct.jsx").SelectProduct;
@@ -132,9 +140,9 @@ function Router() {
           )}
         </Route>
       </Route>
-
       <Route path="*" element={<NotFound />} />
       <Route path="oauth-status" element={<OAuthStatus />} />
+      {verticalsRouter && verticalsRouter()}
     </Routes>
   );
 }


Reviews:
user: login: jaseemjaskp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T11:06:55Z
user: login: vishnuszipstack
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T04:49:47Z
user: login: jaags-dev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T05:57:51Z
user: login: tahierhussain
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T06:10:22Z
user: login: jaags-dev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T06:19:28Z
user: login: jaags-dev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T11:21:18Z
user: login: jaags-dev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T11:21:37Z
user: login: tahierhussain
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T07:16:14Z

Review comments:
diff_hunk: @@ -97,6 +98,12 @@ const CustomLogo = ({ onClick, className }) => {
   }
   return <UnstractLogo className={className} onClick={onClick} />;
 };
+let APIHubLogo;
+try {
path: frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
user: login: jaseemjaskp
body: @jagadeeswaran-zipstack It should be verticals not APIHUb
created_at: 2025-04-23T11:06:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +98,12 @@ const CustomLogo = ({ onClick, className }) => {
   }
   return <UnstractLogo className={className} onClick={onClick} />;
 };
+let APIHubLogo;
+try {
path: frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
user: login: jaags-dev
body: @jaseemjaskp Like we discussed we're showcasing as APIHub so we're having as APIHub here.
created_at: 2025-04-24T05:57:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +65,8 @@ const RequireAuth = () => {
   let navigateTo = `/${orgName}/onboard`;
   if (isLlmWhisperer) {
     navigateTo = `/llm-whisperer/${orgName}/playground`;
+  } else if (isVerticals) {
+    navigateTo = `/verticals/`;
   } else if (onboardCompleted(adapters)) {
     navigateTo = `/${orgName}/${homePagePath}`;
   }
path: frontend/src/components/helpers/auth/RequireAuth.js
user: login: tahierhussain
body: @jagadeeswaran-zipstack NIT: Instead of maintaining separate boolean flags for each product (e.g., isLlmWhisperer, isVerticals), we could store the selected product name as a single value and use that in the logic (e.g., selectedProduct === "LLM_WHISPERER" or selectedProduct === "VERTICALS"). This approach would improve overall code quality and make the implementation more scalable, especially if more products are added in the future.
created_at: 2025-04-24T06:00:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -331,12 +339,24 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
       });
     }
 
+    const handleLogin = () => {
+      const baseUrl = getBaseUrl();
+      const newURL = baseUrl + "/api/v1/login";
+      window.location.href = newURL;
+    };
+
     // Logout
     menuItems.push({
       key: "2",
       label: (
-        <Button onClick={logout} className="logout-button">
-          <LogoutOutlined /> Logout
+        <Button
+          onClick={isLoggedIn ? logout : handleLogin}
+          className="logout-button"
+        >
+          <>
+            {isLoggedIn ? <LogoutOutlined /> : <LoginOutlined />}{" "}
+            {isLoggedIn ? "Logout" : "Login"}
+          </>
         </Button>
       ),
     });
path: frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
user: login: tahierhussain
body: @jagadeeswaran-zipstack NIT: Here's a suggestion for better code readability. 

```
const handleClick = isLoggedIn ? logout : handleLogin;
const icon = isLoggedIn ? <LogoutOutlined /> : <LoginOutlined />;
const label = isLoggedIn ? "Logout" : "Login";

menuItems.push({
  key: "2",
  label: (
    <Button onClick={handleClick} icon={icon} className="logout-button">
      {label}
    </Button>
  ),
});
```
created_at: 2025-04-24T06:05:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,6 +144,11 @@ function Router() {
 
       <Route path="*" element={<NotFound />} />
       <Route path="oauth-status" element={<OAuthStatus />} />
+      {verticalsRouter && (
+        <Route path="verticals" element={<FullPageLayout />}>
+          {verticalsRouter()}
+        </Route>
path: frontend/src/routes/Router.jsx
user: login: tahierhussain
body: @jagadeeswaran-zipstack Can we move the <Route /> that wraps the verticalsRouter inside the Cloud component as well? After that change, the code should look like this:

`{verticalsRouter && verticalsRouter()}`
created_at: 2025-04-24T06:10:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +65,8 @@ const RequireAuth = () => {
   let navigateTo = `/${orgName}/onboard`;
   if (isLlmWhisperer) {
     navigateTo = `/llm-whisperer/${orgName}/playground`;
+  } else if (isVerticals) {
+    navigateTo = `/verticals/`;
   } else if (onboardCompleted(adapters)) {
     navigateTo = `/${orgName}/${homePagePath}`;
   }
path: frontend/src/components/helpers/auth/RequireAuth.js
user: login: jaags-dev
body: Yes @tahierhussain that is how we're doing and exporting `isLlmWhisperer `and `isVerticals` from that store. Instead of checking isVertical here its better to do when we're storing that in global state because It reduces the code duplication.
created_at: 2025-04-24T06:19:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,6 +144,11 @@ function Router() {
 
       <Route path="*" element={<NotFound />} />
       <Route path="oauth-status" element={<OAuthStatus />} />
+      {verticalsRouter && (
+        <Route path="verticals" element={<FullPageLayout />}>
+          {verticalsRouter()}
+        </Route>
path: frontend/src/routes/Router.jsx
user: login: jaags-dev
body: Done 👍 
created_at: 2025-04-24T11:21:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -331,12 +339,24 @@ function TopNavBar({ isSimpleLayout, topNavBarOptions }) {
       });
     }
 
+    const handleLogin = () => {
+      const baseUrl = getBaseUrl();
+      const newURL = baseUrl + "/api/v1/login";
+      window.location.href = newURL;
+    };
+
     // Logout
     menuItems.push({
       key: "2",
       label: (
-        <Button onClick={logout} className="logout-button">
-          <LogoutOutlined /> Logout
+        <Button
+          onClick={isLoggedIn ? logout : handleLogin}
+          className="logout-button"
+        >
+          <>
+            {isLoggedIn ? <LogoutOutlined /> : <LoginOutlined />}{" "}
+            {isLoggedIn ? "Logout" : "Login"}
+          </>
         </Button>
       ),
     });
path: frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
user: login: jaags-dev
body: Got it. Made the required changes.
created_at: 2025-04-24T11:21:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-25T07:13:25Z
body: |                     filepath                      |                  function                   | $$\textcolor{#23d18b}{\tt{passed}}$$ | SUBTOTAL |
| ------------------------------------------------- | ------------------------------------------- | --------------------------------: | -------: |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_logs}}$$   |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_cleanup}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_cleanup\\_skip}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_client\\_init}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_get\\_image\\_exists}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_get\\_image}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_get\\_container\\_run\\_config}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_get\\_container\\_run\\_config\\_without\\_mount}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_run\\_container}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_get\\_image\\_for\\_sidecar}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\\_docker.py}}$$ | $$\textcolor{#23d18b}{\tt{test\\_sidecar\\_container}}$$ |   $$\textcolor{#23d18b}{\tt{1}}$$ | $$\textcolor{#23d18b}{\tt{1}}$$ |
| $$\textcolor{#23d18b}{\tt{TOTAL}}$$               |                                             |  $$\textcolor{#23d18b}{\tt{11}}$$ | $$\textcolor{#23d18b}{\tt{11}}$$ |

<!-- Sticky Pull Request Commentrunner-test-report -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-25T07:14:19Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=Zipstack_unstract&pullRequest=1261) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [B Maintainability Rating on New Code](https://sonarcloud.io/dashboard?id=Zipstack_unstract&pullRequest=1261) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Zipstack_unstract&pullRequest=1261)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE
user: login: hari-kuriakose
created_at: 2025-04-25T10:50:27Z
body: Sonar issues are minor hence relatively ok to proceed for now.
author_association: CONTRIBUTOR