);
}
diff --git a/web/src/components/Sidebar/index.tsx b/web/src/components/Sidebar/index.tsx
index 3845a773c..4b9456220 100644
--- a/web/src/components/Sidebar/index.tsx
+++ b/web/src/components/Sidebar/index.tsx
@@ -1,6 +1,4 @@
-/* eslint-disable react-hooks/exhaustive-deps */
import { useNavigate } from "react-router-dom";
-import { List } from "@mui/material";
import { useLogoutMutation } from "../../store/features/auth/api";
import useToggle from "../../hooks/common/useToggle";
import SlackConnectOverlay from "../SlackConnectOverlay";
@@ -9,11 +7,13 @@ import SidebarElement from "./SidebarElement";
import { LogoutRounded, SettingsRounded } from "@mui/icons-material";
import SidebarButtonElement from "./SidebarButtonElement";
import HeadElement from "./HeadElement";
+import useSidebar from "../../hooks/common/sidebar/useSidebar";
function Sidebar() {
const navigate = useNavigate();
const [triggerLogout] = useLogoutMutation();
const { isOpen: isActionOpen, toggle } = useToggle();
+ const { isOpen, toggle: toggleSidebar } = useSidebar();
const signOut = async () => {
await triggerLogout();
@@ -21,18 +21,21 @@ function Sidebar() {
};
return (
-
+
-
+
{elements.map((element, index) => (
))}
-
+
-
+
}
+ icon={}
onClick={signOut}
/>
-
+
diff --git a/web/src/hooks/common/sidebar/useSidebar.ts b/web/src/hooks/common/sidebar/useSidebar.ts
new file mode 100644
index 000000000..556ad6a96
--- /dev/null
+++ b/web/src/hooks/common/sidebar/useSidebar.ts
@@ -0,0 +1,24 @@
+import { useDispatch, useSelector } from "react-redux";
+import { sidebarSelector } from "../../../store/features/sidebar/selectors";
+import { toggleSidebar } from "../../../store/features/sidebar/sidebarSlice";
+
+type UseSidebarReturnType = {
+ isOpen: boolean;
+ toggle: () => void;
+};
+
+function useSidebar(): UseSidebarReturnType {
+ const { isOpen } = useSelector(sidebarSelector);
+ const dispatch = useDispatch();
+
+ const toggle = () => {
+ dispatch(toggleSidebar());
+ };
+
+ return {
+ isOpen,
+ toggle,
+ };
+}
+
+export default useSidebar;
diff --git a/web/src/hooks/useDefaultPage.ts b/web/src/hooks/useDefaultPage.ts
index b992722e2..be92423b2 100644
--- a/web/src/hooks/useDefaultPage.ts
+++ b/web/src/hooks/useDefaultPage.ts
@@ -25,7 +25,7 @@ function useDefaultPage() {
}, [email]);
useEffect(() => {
- if (!data && isError && !isUnAuth) {
+ if (!data && isError && !isUnAuth(location.pathname)) {
navigate("/signup", {
replace: true,
state: { from: location.pathname },
diff --git a/web/src/index.css b/web/src/index.css
index aa49093aa..c60ca9cd1 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -169,13 +169,13 @@ input[type="number"] {
-moz-appearance: textfield;
}
-.sidebar a {
+/* .sidebar a {
display: flex;
align-items: center;
padding: 0.5rem;
transition: all 0.2s;
-}
+} */
-.sidebar a:hover {
+/* .sidebar a:hover {
background-color: #f3f3f3;
-}
+} */
diff --git a/web/src/store/app/query.ts b/web/src/store/app/query.ts
index 8d41b341d..5bcc068a4 100644
--- a/web/src/store/app/query.ts
+++ b/web/src/store/app/query.ts
@@ -55,7 +55,7 @@ export const baseQueryWithReauthAndModify = async (args, api, extraOptions) => {
const modifiedArgs = modifyRequestBody(args, api);
let result: any = await baseQuery(modifiedArgs, api, extraOptions);
- if (result.error?.status === 401 && !isUnAuth) {
+ if (result.error?.status === 401 && !isUnAuth(location.pathname)) {
try {
const refreshResult = await refreshToken();
const newAccessToken = refreshResult.data?.access;
diff --git a/web/src/store/features/sidebar/initialState.ts b/web/src/store/features/sidebar/initialState.ts
new file mode 100644
index 000000000..74e6fa934
--- /dev/null
+++ b/web/src/store/features/sidebar/initialState.ts
@@ -0,0 +1,7 @@
+export type SidebarInitialState = {
+ isOpen: boolean;
+};
+
+export const initialState = {
+ isOpen: true,
+};
diff --git a/web/src/store/features/sidebar/selectors/index.ts b/web/src/store/features/sidebar/selectors/index.ts
new file mode 100644
index 000000000..2b2d7b183
--- /dev/null
+++ b/web/src/store/features/sidebar/selectors/index.ts
@@ -0,0 +1 @@
+export * from "./sidebarSelector";
diff --git a/web/src/store/features/sidebar/selectors/sidebarSelector.ts b/web/src/store/features/sidebar/selectors/sidebarSelector.ts
new file mode 100644
index 000000000..8b53edb6a
--- /dev/null
+++ b/web/src/store/features/sidebar/selectors/sidebarSelector.ts
@@ -0,0 +1,3 @@
+import { RootState } from "../../..";
+
+export const sidebarSelector = (state: RootState) => state.sidebar;
diff --git a/web/src/store/features/sidebar/sidebarSlice.ts b/web/src/store/features/sidebar/sidebarSlice.ts
new file mode 100644
index 000000000..289c673b5
--- /dev/null
+++ b/web/src/store/features/sidebar/sidebarSlice.ts
@@ -0,0 +1,15 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { initialState } from "./initialState";
+import * as Actions from "./slices";
+
+const sidebarSlice = createSlice({
+ name: "sidebar",
+ initialState: initialState,
+ reducers: {
+ toggle: Actions.toggle,
+ },
+});
+
+export const { toggle: toggleSidebar } = sidebarSlice.actions;
+
+export default sidebarSlice.reducer;
diff --git a/web/src/store/features/sidebar/slices/index.ts b/web/src/store/features/sidebar/slices/index.ts
new file mode 100644
index 000000000..934fd7345
--- /dev/null
+++ b/web/src/store/features/sidebar/slices/index.ts
@@ -0,0 +1 @@
+export * from "./toggle";
diff --git a/web/src/store/features/sidebar/slices/toggle.ts b/web/src/store/features/sidebar/slices/toggle.ts
new file mode 100644
index 000000000..6b322dead
--- /dev/null
+++ b/web/src/store/features/sidebar/slices/toggle.ts
@@ -0,0 +1,5 @@
+import { SidebarInitialState } from "../initialState";
+
+export const toggle = (state: SidebarInitialState) => {
+ state.isOpen = !state.isOpen;
+};
diff --git a/web/src/store/index.ts b/web/src/store/index.ts
index 217a0148e..7525a1bf6 100644
--- a/web/src/store/index.ts
+++ b/web/src/store/index.ts
@@ -13,6 +13,7 @@ import searchSlice from "./features/search/searchSlice.ts";
import paginationSlice from "./features/pagination/paginationSlice.ts";
import commonSlice from "./features/common/commonSlice.ts";
import dynamicAlertsSlice from "./features/dynamicAlerts/dynamicAlertsSlice.ts";
+import sidebarSlice from "./features/sidebar/sidebarSlice.ts";
export const store = configureStore({
reducer: {
@@ -29,6 +30,7 @@ export const store = configureStore({
pagination: paginationSlice,
common: commonSlice,
dynamicAlerts: dynamicAlertsSlice,
+ sidebar: sidebarSlice,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
diff --git a/web/src/utils/auth/authenticatedRoutes.ts b/web/src/utils/auth/authenticatedRoutes.ts
new file mode 100644
index 000000000..e6dd1300b
--- /dev/null
+++ b/web/src/utils/auth/authenticatedRoutes.ts
@@ -0,0 +1,10 @@
+import { routes } from "@/routes";
+import { unauthenticatedRoutes } from "./unauthenticatedRoutes";
+import { PageKeys } from "@/pageKeys";
+
+export const authenticatedRoutes: (typeof routes)[keyof typeof routes][] =
+ Object.keys(routes).filter(
+ (key) =>
+ !unauthenticatedRoutes.includes(routes[key]) &&
+ key !== PageKeys.NOT_FOUND,
+ );
diff --git a/web/src/utils/auth/unauthenticatedRoutes.ts b/web/src/utils/auth/unauthenticatedRoutes.ts
index 582a69569..30274d179 100644
--- a/web/src/utils/auth/unauthenticatedRoutes.ts
+++ b/web/src/utils/auth/unauthenticatedRoutes.ts
@@ -1,11 +1,19 @@
-export const unauthenticatedRoutes = ["/signup", "/login", "/oauth/callback/*"];
+import { routes } from "@/routes";
+import { pathNameValues, replaceRouteParam } from "../common/replaceRouteParam";
-const pathToRegex = (path) => {
+export const unauthenticatedRoutes: (typeof routes)[keyof typeof routes][] = [
+ routes.SIGNUP,
+ routes.LOGIN,
+ replaceRouteParam(routes.OAUTH_CALLBACK, pathNameValues.OAUTH_ID, "*"),
+ routes.RESET_PASSWORD,
+ routes.PLAYGROUND,
+];
+
+export const pathToRegex = (path: string): RegExp => {
return new RegExp(
"^" + path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*"),
);
};
-export const isUnAuth = unauthenticatedRoutes.some((route) =>
- pathToRegex(route).test(window.location.pathname),
-);
+export const isUnAuth = (path: string) =>
+ unauthenticatedRoutes.some((route) => pathToRegex(route).test(path));
diff --git a/web/src/utils/common/capitalize.ts b/web/src/utils/common/capitalize.ts
index 4ef54b573..5fe05054e 100644
--- a/web/src/utils/common/capitalize.ts
+++ b/web/src/utils/common/capitalize.ts
@@ -1,3 +1,9 @@
-export default function capitalizeFirstLetter(string) {
+/**
+ * Capitalizes the first letter of a given string.
+ *
+ * @param {string} string - The input string to be capitalized.
+ * @return {string} The input string with the first letter capitalized.
+ */
+export default function capitalizeFirstLetter(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1);
}
diff --git a/web/src/utils/common/cardsData.ts b/web/src/utils/common/cardsData.ts
index 3349b6be0..599739eba 100644
--- a/web/src/utils/common/cardsData.ts
+++ b/web/src/utils/common/cardsData.ts
@@ -1,4 +1,11 @@
-export const cardsData = [
+type CardsDataType = {
+ url: string; // the logo url (from /public/integrations)
+ enum: string; // enum from the backend
+ desc?: string; // Description to show on the /data-sources/add page
+ docs?: string; // Link to the documentation (to be shown while configuring)
+};
+
+export const cardsData: CardsDataType[] = [
{
url: "/integrations/sentry.png",
enum: "SENTRY",
diff --git a/web/src/utils/common/checkId.ts b/web/src/utils/common/checkId.ts
index ffb6d5677..830aacf03 100644
--- a/web/src/utils/common/checkId.ts
+++ b/web/src/utils/common/checkId.ts
@@ -1,4 +1,11 @@
-function checkId(id: string) {
+/**
+ * Checks if the id is a valid ID from backend or
+ * a UUID generated for the frontend
+ *
+ * @param {string} id - ID to check
+ * @return {string} Either the required numerical ID or 0
+ */
+function checkId(id: string): string {
if (!/^\d+$/.test(id)) {
return "0";
} else {
diff --git a/web/src/utils/common/dateUtils.ts b/web/src/utils/common/dateUtils.ts
index 815cb1200..177489b39 100644
--- a/web/src/utils/common/dateUtils.ts
+++ b/web/src/utils/common/dateUtils.ts
@@ -1,47 +1 @@
-export const renderTimestamp = (timestamp: number) => {
- // Create a Date object from the UNIX timestamp (timestamp is assumed to be in seconds)
- const date = new Date(timestamp * 1000);
-
- // Use Intl.DateTimeFormat for formatting the date
- // Customize options as needed to match the desired format 'YYYY-MM-DD HH:mm:ss'
- const options: any = {
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- hour12: false,
- };
-
- // Create an Intl.DateTimeFormat instance with the desired options and locale ('en-US' used here as an example)
- const formatter = new Intl.DateTimeFormat("en-US", options);
-
- // Format the date
- const formattedDate = formatter.format(date);
-
- // Since Intl.DateTimeFormat returns the date in the format 'MM/DD/YYYY, HH:mm:ss' for 'en-US' locale,
- // we need to rearrange the formatted string to match 'YYYY-MM-DD HH:mm:ss'
- const parts = formattedDate.split(", ");
- const datePart = parts[0].split("/");
- const timePart = parts[1];
-
- // Rearrange and format the string
- return `${datePart[2]}-${datePart[0]}-${datePart[1]} ${timePart}`;
-};
-
-export const isDate = (date) => {
- if (date.length < 10) return false;
-
- // Regular expression to match date formats like YYYY-MM-DD, YYYY/MM/DD, etc.
- const datePattern =
- /^\d{4}[-/]\d{2}[-/]\d{2}([ T]\d{2}:\d{2}:\d{2}(\.\d{1,6})?)?$/;
-
- // Check if the string matches the date pattern
- if (!datePattern.test(date)) {
- return false;
- }
-
- const d = new Date(date);
- return d instanceof Date && !isNaN(d.valueOf());
-};
+export * from "./dates";
diff --git a/web/src/utils/common/dates/index.ts b/web/src/utils/common/dates/index.ts
new file mode 100644
index 000000000..fe302fab5
--- /dev/null
+++ b/web/src/utils/common/dates/index.ts
@@ -0,0 +1,2 @@
+export * from "./isDate";
+export * from "./renderTimestamp";
diff --git a/web/src/utils/common/dates/isDate.ts b/web/src/utils/common/dates/isDate.ts
new file mode 100644
index 000000000..193f5eec7
--- /dev/null
+++ b/web/src/utils/common/dates/isDate.ts
@@ -0,0 +1,21 @@
+/**
+ * Checks if the input string is a valid date string in formats like YYYY-MM-DD, YYYY/MM/DD, etc.
+ *
+ * @param {string} date - The string to be checked for date validity.
+ * @return {boolean} Returns true if the input string is a valid date, otherwise false.
+ */
+export const isDate = (date: string): boolean => {
+ if (date.length < 10) return false;
+
+ // Regular expression to match date formats like YYYY-MM-DD, YYYY/MM/DD, etc.
+ const datePattern =
+ /^\d{4}[-/]\d{2}[-/]\d{2}([ T]\d{2}:\d{2}:\d{2}(\.\d{1,6})?)?$/;
+
+ // Check if the string matches the date pattern
+ if (!datePattern.test(date)) {
+ return false;
+ }
+
+ const d = new Date(date);
+ return d instanceof Date && !isNaN(d.valueOf());
+};
diff --git a/web/src/utils/common/dates/renderTimestamp.ts b/web/src/utils/common/dates/renderTimestamp.ts
new file mode 100644
index 000000000..354e487fc
--- /dev/null
+++ b/web/src/utils/common/dates/renderTimestamp.ts
@@ -0,0 +1,37 @@
+/**
+ * Renders a UNIX timestamp as a formatted date and time string in the format 'YYYY-MM-DD HH:mm:ss'.
+ *
+ * @param {number} timestamp - The UNIX timestamp to be rendered.
+ * @return {string} The formatted date and time string.
+ */
+export const renderTimestamp = (timestamp: number): string => {
+ // Create a Date object from the UNIX timestamp (timestamp is assumed to be in seconds)
+ const date = new Date(timestamp * 1000);
+
+ // Use Intl.DateTimeFormat for formatting the date
+ // Customize options as needed to match the desired format 'YYYY-MM-DD HH:mm:ss'
+ const options: any = {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+ };
+
+ // Create an Intl.DateTimeFormat instance with the desired options and locale ('en-US' used here as an example)
+ const formatter = new Intl.DateTimeFormat("en-US", options);
+
+ // Format the date
+ const formattedDate = formatter.format(date);
+
+ // Since Intl.DateTimeFormat returns the date in the format 'MM/DD/YYYY, HH:mm:ss' for 'en-US' locale,
+ // we need to rearrange the formatted string to match 'YYYY-MM-DD HH:mm:ss'
+ const parts = formattedDate.split(", ");
+ const datePart = parts[0].split("/");
+ const timePart = parts[1];
+
+ // Rearrange and format the string
+ return `${datePart[2]}-${datePart[0]}-${datePart[1]} ${timePart}`;
+};
diff --git a/web/src/utils/common/replaceRouteParam.ts b/web/src/utils/common/replaceRouteParam.ts
new file mode 100644
index 000000000..f7112c145
--- /dev/null
+++ b/web/src/utils/common/replaceRouteParam.ts
@@ -0,0 +1,18 @@
+import { routes } from "@/routes";
+
+export enum pathNameValues {
+ OAUTH_ID = "oauthId",
+ PLAYBOOK_ID = "playbook_id",
+ ID = "id",
+ WORKFLOW_RUN_ID = "workflow_run_id",
+ CONNECTOR_ENUM = "connectorEnum",
+}
+
+export const replaceRouteParam = (
+ route: (typeof routes)[keyof typeof routes],
+ paramName: pathNameValues,
+ value: string,
+): string => {
+ const regex = new RegExp(`:${paramName}`, "g");
+ return route.replace(regex, value);
+};
diff --git a/web/tsconfig.json b/web/tsconfig.json
index 39373ca9e..fad048bab 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -17,7 +17,12 @@
"jsx": "react-jsx",
"allowImportingTsExtensions": true,
"noImplicitAny": false,
- "types": ["vite/client", "vite-plugin-svgr/client"]
+ "types": ["vite/client", "vite-plugin-svgr/client", "jest"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "@components/*": ["src/components/*"]
+ }
},
"include": ["src"]
}
\ No newline at end of file
diff --git a/web/vite.config.js b/web/vite.config.js
index e3b0381d9..fd90c9c4b 100644
--- a/web/vite.config.js
+++ b/web/vite.config.js
@@ -1,5 +1,6 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
+import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
@@ -25,5 +26,6 @@ export default defineConfig({
plugins: ["@emotion/babel-plugin"],
},
}),
+ tsconfigPaths(),
],
});