diff --git a/package.json b/package.json
index 8af7131..d3db39b 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,6 @@
"build": "yarn run clear-build && tsc && vite build && yarn run copy-config",
"lint": "eslint --fix \"**/?*.{ts,tsx,js,jsx}\"",
"eslint-check": "eslint \"**/?*.{ts,tsx,js,jsx}\"",
- "prepare": "husky install",
"pretty": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,scss,md}\"",
"prettier-check": "prettier --check \"./**/*.{js,jsx,ts,tsx,css,scss,md}\"",
"eslint-check-quiet": "yarn eslint-check --quiet"
@@ -33,7 +32,7 @@
},
"dependencies": {
"@navi/dark-knight": "^1.0.13",
- "@navi/web-ui": "^1.59.2",
+ "@navi/web-ui": "^1.59.4",
"@reduxjs/toolkit": "^1.9.7",
"@stoddabr/react-tableau-embed-live": "^0.3.26",
"antd": "^5.9.4",
diff --git a/src/Pages/Incidents/Incidents.module.scss b/src/Pages/Incidents/Incidents.module.scss
index 309e996..5e938cb 100644
--- a/src/Pages/Incidents/Incidents.module.scss
+++ b/src/Pages/Incidents/Incidents.module.scss
@@ -184,6 +184,9 @@
display: inline;
}
+.list-wrapper {
+ display: flex;
+}
.hint-text {
margin-top: 24px;
font-size: 12px;
diff --git a/src/Pages/TeamRevamp/Team.module.scss b/src/Pages/TeamRevamp/Team.module.scss
new file mode 100644
index 0000000..a703eea
--- /dev/null
+++ b/src/Pages/TeamRevamp/Team.module.scss
@@ -0,0 +1,7 @@
+.team-wrapper {
+ margin: 24px;
+}
+
+.wrapper-class {
+ display: flex;
+}
diff --git a/src/Pages/TeamRevamp/TeamDetails/TeamDetails.module.scss b/src/Pages/TeamRevamp/TeamDetails/TeamDetails.module.scss
new file mode 100644
index 0000000..5f8e897
--- /dev/null
+++ b/src/Pages/TeamRevamp/TeamDetails/TeamDetails.module.scss
@@ -0,0 +1,7 @@
+.team-details-wrapper {
+ margin: 20px 0px 0px 79px;
+}
+.fallback-component {
+ height: 100px;
+ margin-left: 450px;
+}
diff --git a/src/Pages/TeamRevamp/TeamDetails/index.tsx b/src/Pages/TeamRevamp/TeamDetails/index.tsx
new file mode 100644
index 0000000..50ed116
--- /dev/null
+++ b/src/Pages/TeamRevamp/TeamDetails/index.tsx
@@ -0,0 +1,26 @@
+import { useSelector } from 'react-redux';
+import { selectSearchTeamData } from '@src/slices/team1Slice';
+import FallbackComponent from '@src/components/Fallback';
+import TeamMemberDetails from '../partials/TeamMemberDetails';
+import SlackDetails from '../partials/SlackDetails';
+import styles from './TeamDetails.module.scss';
+
+const TeamDetails: React.FC = () => {
+ const { isPending } = useSelector(selectSearchTeamData);
+
+ if (isPending) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+
+ );
+};
+
+export default TeamDetails;
diff --git a/src/Pages/TeamRevamp/TeamList/SearchInput.tsx b/src/Pages/TeamRevamp/TeamList/SearchInput.tsx
new file mode 100644
index 0000000..1bf991e
--- /dev/null
+++ b/src/Pages/TeamRevamp/TeamList/SearchInput.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { BorderedInput } from '@navi/web-ui/lib/primitives';
+import { SearchIcon } from '@navi/web-ui/lib/icons';
+import { SearchInputComponentProps } from '../types';
+import styles from './TeamList.module.scss';
+
+const SearchInput: React.FC = ({
+ value,
+ onChange,
+ isAdmin,
+}) => {
+ const containerClass = isAdmin
+ ? styles['search-bar']
+ : styles['search-input'];
+ return (
+ }
+ onChange={onChange}
+ containerClassName={containerClass}
+ value={value}
+ placeholder="Search by team "
+ />
+ );
+};
+
+export default SearchInput;
diff --git a/src/Pages/TeamRevamp/TeamList/TeamList.module.scss b/src/Pages/TeamRevamp/TeamList/TeamList.module.scss
new file mode 100644
index 0000000..d81b64f
--- /dev/null
+++ b/src/Pages/TeamRevamp/TeamList/TeamList.module.scss
@@ -0,0 +1,66 @@
+.team-div {
+ margin-top: 36px;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ border-radius: 8px;
+ border: 1px solid var(--navi-color-gray-border);
+ background: var(--navi-color-gray-bg-primary);
+ box-shadow: 0px 6px 10px 0px rgba(0, 0, 0, 0.08),
+ 0px 1px 18px 0px rgba(0, 0, 0, 0.06), 0px 3px 5px 0px rgba(0, 0, 0, 0.1);
+ position: sticky;
+}
+.search-wrapper {
+ display: flex;
+}
+.create-team-btn {
+ width: 36px;
+ height: 36px;
+ padding: 8px;
+ margin-top: 8px;
+ margin-left: 8px;
+}
+@mixin search {
+ height: 36px;
+ margin-left: 12px;
+}
+.search-bar {
+ @include search;
+ width: 234px;
+}
+.search-input {
+ @include search;
+ width: 272px;
+}
+.select-picker-wrapper {
+ min-width: 300px;
+ min-height: calc(100vh - 228px) !important;
+ border: none !important;
+ box-shadow: none !important;
+ :hover {
+ background-color: var(--navi-select-picker-bg-item-selected);
+ }
+}
+
+.select-picker-wrapper::-webkit-scrollbar {
+ display: none;
+}
+.icon-wrapper {
+ display: flex;
+ gap: 8px;
+}
+.custom-options {
+ display: flex;
+ justify-content: space-between;
+ font-size: small;
+ color: var(--navi-color-gray-c2);
+ height: 29px;
+ padding-top: 15px;
+}
+.search-results {
+ margin-left: 15px;
+ margin-top: 12px;
+ color: var(--navi-color-gray-c3);
+}
+.add-icon {
+ margin-left: 3px;
+}
diff --git a/src/Pages/TeamRevamp/TeamList/index.tsx b/src/Pages/TeamRevamp/TeamList/index.tsx
new file mode 100644
index 0000000..07d2a1d
--- /dev/null
+++ b/src/Pages/TeamRevamp/TeamList/index.tsx
@@ -0,0 +1,192 @@
+import { useEffect, useState, useRef, useReducer } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { SelectPicker } from '@navi/web-ui/lib/components';
+import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
+import { Button, Typography } from '@navi/web-ui/lib/primitives';
+import { AddIcon } from '@navi/web-ui/lib/icons';
+import PersonIcon from '@src/assets/PersonIcon';
+import { fetchTeamDetails, setModalOpen } from '@src/slices/team1Slice';
+import { AppDispatch, RootState } from '@src/store';
+import { useAuthData } from '@src/services/hooks/useAuth';
+import CreateTeam from '@src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam';
+import SearchInput from './SearchInput';
+import styles from './TeamList.module.scss';
+import SelectedPersonIcon from '@src/assets/SelectedPersonIcon';
+
+type Options = {
+ label: string;
+ value: number;
+ additionalData: {
+ count: number;
+ isSelected: boolean;
+ };
+};
+
+const CustomTeamOptions = (option: Options): JSX.Element => {
+ return (
+
+
+
+ {option.additionalData.count}
+
+ {option.additionalData.isSelected ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+const TeamList: React.FC = () => {
+ const dispatch = useDispatch();
+ const Role = useAuthData();
+ const teamList = useSelector((state: RootState) => state.team1.teams.data);
+ const defaultTeam = useSelector(
+ (state: RootState) => state.team1.teams.data[0],
+ );
+ const [team, setTeam] = useState();
+ const newTeam = useSelector((state: RootState) => state.team1.teams.newTeam);
+ const [input, setInput] = useState('');
+ const scroll = useRef(false);
+
+ const scrollintoView = (event: string): void => {
+ if (!event) return;
+ const element = document.getElementById(event);
+ if (element) {
+ setTimeout(() => {
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }, 0);
+ }
+ };
+
+ const options = Array.isArray(teamList)
+ ? teamList.map(item => ({
+ label: item.name,
+ value: item.id,
+ additionalData: {
+ count: item.slackUserIds?.length,
+ isSelected:
+ item.id === team?.value ||
+ (team?.value ? false : defaultTeam?.id === item.id),
+ },
+ }))
+ : [];
+
+ useEffect(() => {
+ if (!scroll.current && team?.value) {
+ scroll.current = true;
+ if (input.length) {
+ setInput('');
+ scrollintoView(team?.value?.toString() ?? '');
+ }
+ return;
+ }
+ }, [team?.value, options]);
+
+ useEffect(() => {
+ scroll.current = false;
+ if (!scroll.current && newTeam?.toString()?.length) {
+ scroll.current = true;
+ if (Array.isArray(teamList) && teamList.length) {
+ teamList?.forEach(item => {
+ if (item.id === newTeam) {
+ setTimeout(() => {
+ scrollintoView(newTeam?.toString() ?? '');
+ }, 100);
+ }
+ });
+ }
+ return;
+ }
+ }, [teamList]);
+
+ const handleInputChange = (
+ event: React.ChangeEvent,
+ ): void => {
+ setInput(event.target.value);
+ };
+ const createHandler = (): void => {
+ dispatch(setModalOpen(true));
+ };
+
+ const handleSelectionChange = (
+ event: SelectPickerOptionProps | SelectPickerOptionProps[],
+ ): void => {
+ if (!Array.isArray(event)) {
+ setTeam(event);
+ dispatch(fetchTeamDetails(event.value.toString()));
+ scroll.current = false;
+ }
+ };
+
+ const returnIsFilteredResultEmpty = (): boolean => {
+ const filteredResult = options.filter(item =>
+ item.label.toLowerCase().includes(input.toLowerCase()),
+ );
+ return !filteredResult.length;
+ };
+
+ return (
+
+
+
+
+ {Role.includes('Admin') && (
+
+ }
+ onClick={createHandler}
+ variant="primary"
+ className={styles['create-team-btn']}
+ size="small"
+ >
+ {''}
+
+ )}
+
+ {input && (
+
+ {' '}
+
+ {' '}
+ SEARCH RESULTS{' '}
+
+
+ )}
+ {returnIsFilteredResultEmpty() ? (
+
+ {' '}
+
+ {' '}
+ No results found
+
+
+ ) : null}
+
+
+
+
+ );
+};
+
+export default TeamList;
diff --git a/src/Pages/TeamRevamp/constants.ts b/src/Pages/TeamRevamp/constants.ts
new file mode 100644
index 0000000..2e7f89f
--- /dev/null
+++ b/src/Pages/TeamRevamp/constants.ts
@@ -0,0 +1,182 @@
+import { createURL } from '@src/services/globalUtils';
+import { ActionType, AppState, Participant } from './types';
+
+const URL_PREFIX = createURL('/houston');
+
+export const FETCH_TEAM_DATA = `${URL_PREFIX}/teams`;
+export const FETCH_ALL_BOTS_DATA = `${URL_PREFIX}/bots`;
+export const CREATE_TEAM = `${URL_PREFIX}/teams/add`;
+
+export const FETCH_SINGLE_TEAM_DATA = (payload: string): string => {
+ return `${URL_PREFIX}/teams/${payload}`;
+};
+
+export const UPDATE_TEAM_DATA = (): string => {
+ return `${URL_PREFIX}/teams`;
+};
+
+export const REMOVE_TEAM_MEMBER = (teamId: string, userId: string): string => {
+ return `${URL_PREFIX}/teams/${teamId}/members/${userId}`;
+};
+
+export const MAKE_MANAGER = (teamId: string, userId: string): string => {
+ return `${URL_PREFIX}/teams/${teamId}/manager/${userId}`;
+};
+
+export const regularExpression = /^[a-zA-Z][a-zA-Z0-9_ -]{1,48}[a-zA-Z0-9]$/;
+export const validEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+export const emailRegularExpression = /^[a-zA-Z]+\.[a-zA-Z]+@navi\.com$/;
+
+export const actionTypes = {
+ SET_OPEN_DIALOG: 'SET_OPEN_DIALOG',
+ SET_OPEN_MODAL: 'SET_OPEN_MODAL',
+ SET_EMAIL_IDS: 'SET_EMAIL_IDS',
+ SET_SELECTED_MEMBER: 'SET_SELECTED_MEMBER',
+ SET_HOVERED: 'SET_HOVERED',
+ SET_SLACK_CHANNEL_ID: 'SET_SLACK_CHANNEL_ID',
+ SET_ONCALL: 'SET_ONCALL',
+ SET_PSEC_ONCALL: 'SET_PSEC_ONCALL',
+ SET_INPUT: 'SET_INPUT',
+ SET_PSEC_INPUT: 'SET_PSEC_INPUT',
+ SET_OPEN_ONCALL: 'SET_OPEN_ONCALL',
+ SET_PSEC_OPEN_ONCALL: 'SET_PSEC_OPEN_ONCALL',
+ SET_TEAM_NAME: 'SET_TEAM_NAME',
+ SET_TEAM_NAME_ERROR: 'SET_TEAM_NAME_ERROR',
+ SET_EMAIL_ERROR: 'SET_EMAIL_ERROR',
+ SET_EMAIL: 'SET_EMAIL',
+ SET_CLEAR_MODAL: 'SET_CLEAR_MODAL',
+ SET_ADD_MEMBER_ERROR: 'SET_ADD_MEMBER_ERROR',
+};
+
+export const initialState: AppState = {
+ openDialog: false,
+ openModal: false,
+ emailIds: '',
+ selectedMember: undefined,
+ hovered: {
+ ishovered: false,
+ id: '',
+ },
+ slackChannelId: '',
+ oncall: {
+ label: '',
+ value: '',
+ },
+ psecOncall: {
+ label: '',
+ value: '',
+ },
+ input: '',
+ psecInput: '',
+ openOnCall: false,
+ openPsecOnCall: false,
+ teamName: '',
+ teamNameError: '',
+ emailError: '',
+ email: '',
+ clearModal: false,
+ addMemberError: '',
+};
+
+export const reducer = (state: AppState, action: ActionType): AppState => {
+ switch (action.type) {
+ case actionTypes.SET_OPEN_DIALOG:
+ return {
+ ...state,
+ openDialog: action.payload,
+ };
+ case actionTypes.SET_OPEN_MODAL:
+ return {
+ ...state,
+ openModal: action.payload,
+ };
+ case actionTypes.SET_EMAIL_IDS:
+ return {
+ ...state,
+ emailIds: action.payload,
+ };
+ case actionTypes.SET_SELECTED_MEMBER:
+ return {
+ ...state,
+ selectedMember: action.payload,
+ };
+ case actionTypes.SET_HOVERED:
+ return {
+ ...state,
+ hovered: {
+ ...state.hovered,
+ ...action.payload,
+ },
+ };
+ case actionTypes.SET_SLACK_CHANNEL_ID:
+ return {
+ ...state,
+ slackChannelId: action.payload,
+ };
+ case actionTypes.SET_ONCALL:
+ return {
+ ...state,
+ oncall: action.payload,
+ };
+ case actionTypes.SET_PSEC_ONCALL:
+ return {
+ ...state,
+ psecOncall: action.payload,
+ };
+ case actionTypes.SET_INPUT:
+ return {
+ ...state,
+ input: action.payload,
+ };
+ case actionTypes.SET_PSEC_INPUT:
+ return {
+ ...state,
+ psecInput: action.payload,
+ };
+ case actionTypes.SET_OPEN_ONCALL:
+ return {
+ ...state,
+ openOnCall: action.payload,
+ };
+ case actionTypes.SET_PSEC_OPEN_ONCALL:
+ return {
+ ...state,
+ openPsecOnCall: action.payload,
+ };
+ case actionTypes.SET_TEAM_NAME:
+ return {
+ ...state,
+ teamName: action.payload,
+ };
+ case actionTypes.SET_TEAM_NAME_ERROR:
+ return {
+ ...state,
+ teamNameError: action.payload,
+ };
+ case actionTypes.SET_EMAIL_ERROR:
+ return {
+ ...state,
+ emailError: action.payload,
+ };
+ case actionTypes.SET_EMAIL:
+ return {
+ ...state,
+ email: action.payload,
+ };
+ case actionTypes.SET_CLEAR_MODAL:
+ return {
+ ...state,
+ teamName: '',
+ email: '',
+ teamNameError: '',
+ emailError: '',
+ };
+ case actionTypes.SET_ADD_MEMBER_ERROR:
+ return {
+ ...state,
+ addMemberError: action.payload,
+ };
+ default:
+ return state;
+ }
+};
diff --git a/src/Pages/TeamRevamp/index.tsx b/src/Pages/TeamRevamp/index.tsx
new file mode 100644
index 0000000..6941f14
--- /dev/null
+++ b/src/Pages/TeamRevamp/index.tsx
@@ -0,0 +1,62 @@
+import { FC, useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import Typography from '@navi/web-ui/lib/primitives/Typography';
+import { AppDispatch } from '@src/store';
+import ErrorBoundary from '@src/components/ErrorBoundary/ErrorBoundary';
+import {
+ TeamState,
+ fetchTeamDetails,
+ fetchTeams,
+ selectSearchTeamData,
+ setSelectedTeam,
+} from '@src/slices/team1Slice';
+import TeamDetails from './TeamDetails';
+import { fetchAllBots } from './partials/Bots';
+import TeamList from './TeamList';
+import styles from './Team.module.scss';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
+
+const TeamRevamp: FC = () => {
+ const dispatch = useDispatch();
+ const teamData: TeamState = useSelector(selectSearchTeamData);
+ const { data, selectedTeam } = teamData;
+ const selectedTeamId = selectedTeam?.id || '';
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
+
+ useEffect(() => {
+ dispatch(fetchTeams());
+ fetchAllBots();
+ }, []);
+
+ useEffect(() => {
+ if (data) {
+ dispatch(setSelectedTeam(data[0]));
+ }
+ }, []);
+
+ useEffect(() => {
+ if (selectedTeam && selectedTeamId) {
+ dispatch(fetchTeamDetails(selectedTeamId.toString()));
+ }
+ }, [selectedTeam]);
+ useEffect(() => {
+ fireEvent(EVENT_NAME.Houston_Team_Land, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ }, []);
+ return (
+
+
+
+ );
+};
+
+export default TeamRevamp;
diff --git a/src/Pages/TeamRevamp/partials/Bots.tsx b/src/Pages/TeamRevamp/partials/Bots.tsx
new file mode 100644
index 0000000..4f39381
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/Bots.tsx
@@ -0,0 +1,24 @@
+import { ApiService } from '@src/services/api';
+import { FETCH_ALL_BOTS_DATA } from '../constants';
+import { PickerOptionProps } from '../types';
+import { toast } from '@navi/web-ui/lib/primitives/Toast';
+
+let bots: Array = [];
+
+export const getBots = (): Array => bots;
+
+export const fetchAllBots = (): void => {
+ ApiService.get(FETCH_ALL_BOTS_DATA)
+ .then(response => {
+ bots = [];
+ response?.data?.data.map(data => {
+ bots.push({
+ label: '@' + data.name,
+ value: data.id,
+ });
+ });
+ })
+ .catch(error => {
+ toast.error(error?.message);
+ });
+};
diff --git a/src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.module.scss b/src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.module.scss
new file mode 100644
index 0000000..fb08d8f
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.module.scss
@@ -0,0 +1,14 @@
+.team-wrapper {
+ padding: 0px 24px 0px 24px;
+}
+@mixin team-form-wrapper {
+ height: medium;
+ margin-bottom: 20px;
+ gap: 0 8px;
+}
+.email-wrapper {
+ @include team-form-wrapper;
+}
+.input-wrapper {
+ @include team-form-wrapper;
+}
diff --git a/src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.tsx b/src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.tsx
new file mode 100644
index 0000000..ae1fcf0
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.tsx
@@ -0,0 +1,172 @@
+import { useReducer } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { Typography } from '@navi/web-ui/lib/primitives';
+import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
+import { BorderedInput, ModalDialog } from '@navi/web-ui/lib/primitives';
+import { toast } from '@navi/web-ui/lib/primitives/Toast';
+import { AppDispatch } from '@src/store';
+import { ApiService } from '@src/services/api';
+import {
+ fetchTeamDetails,
+ fetchTeams,
+ selectSearchTeamData,
+ setModalOpen,
+ setNewTeam,
+} from '@src/slices/team1Slice';
+import {
+ CREATE_TEAM,
+ actionTypes,
+ initialState,
+ reducer,
+} from '@src/Pages/TeamRevamp/constants';
+import {
+ regularExpression,
+ emailRegularExpression,
+ validEmail,
+} from '@src/Pages/TeamRevamp/constants';
+import { CreateTeamProps } from '@src/Pages/TeamRevamp/types';
+import styles from './CreateTeam.module.scss';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
+
+const CreateTeam: React.FC = ({ setTeam }) => {
+ const dispatch = useDispatch();
+ const [state, dispatchData] = useReducer(reducer, initialState);
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
+ const { modalOpen } = useSelector(selectSearchTeamData);
+ const validateTeamName = (value: string): void => {
+ value = value.trim();
+ dispatchData({
+ type: actionTypes.SET_TEAM_NAME_ERROR,
+ payload: !regularExpression.test(value)
+ ? 'Min. 3 characters required. Use spaces, ‘_’, or ‘-’ only'
+ : '',
+ });
+ };
+
+ const validateEmail = (value: string): void => {
+ dispatchData({
+ type: actionTypes.SET_EMAIL_ERROR,
+ payload: validEmail.test(value)
+ ? !emailRegularExpression.test(value)
+ ? 'Please enter a Navi email ID'
+ : ''
+ : '',
+ });
+ };
+ const handleTeamNameChange = (
+ e: React.ChangeEvent,
+ ): void => {
+ const inputValue = e.target.value;
+ dispatchData({
+ type: actionTypes.SET_TEAM_NAME,
+ payload: inputValue,
+ });
+ validateTeamName(inputValue);
+ };
+
+ const handleEmailChange = (e: React.ChangeEvent): void => {
+ const inputValue = e.target.value;
+ dispatchData({
+ type: actionTypes.SET_EMAIL,
+ payload: inputValue,
+ });
+ validateEmail(inputValue);
+ };
+
+ const addTeamHandler = (): void => {
+ fireEvent(EVENT_NAME.Houston_Create_Team, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ ApiService.post(CREATE_TEAM, {
+ name: state.teamName,
+ manager_email: state.email,
+ })
+ .then(response => {
+ const toastMessage = `${response?.data?.data?.message}`;
+ const createdTeamId = response?.data?.data?.id;
+ const createdTeamName = response?.data?.data?.name;
+ toast.success(toastMessage);
+ clearErrors();
+ dispatch(fetchTeamDetails(createdTeamId.toString()));
+ setTeam({ label: createdTeamName, value: createdTeamId });
+ dispatch(setNewTeam(createdTeamId));
+ dispatch(fetchTeams());
+ })
+ .catch(error => {
+ const toastMessage = `${
+ error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message}`
+ : ''
+ }`;
+ toast.error(toastMessage);
+ });
+ };
+
+ const clearErrors = (): void => {
+ dispatch(setModalOpen(false));
+ dispatchData({
+ type: actionTypes.SET_CLEAR_MODAL,
+ payload: '',
+ });
+ };
+
+ return (
+
+ {modalOpen && (
+
+
+
+ }
+ placeholder="E.g. NaviPay_Operations"
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+ )}
+
+ );
+};
+export default CreateTeam;
diff --git a/src/Pages/TeamRevamp/partials/SlackDetails/Slack.module.scss b/src/Pages/TeamRevamp/partials/SlackDetails/Slack.module.scss
new file mode 100644
index 0000000..5f8e24c
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/SlackDetails/Slack.module.scss
@@ -0,0 +1,76 @@
+.team-input-wrapper {
+ min-width: 320px;
+ height: 38px;
+}
+.add-member {
+ margin: 72px 0px 24px 0px;
+}
+.member-div {
+ margin: 32px 0px 0px 0px;
+ color: var(--navi-color-gray-c3);
+}
+.on-call-wrapper {
+ display: flex;
+ margin-top: 10px;
+}
+.team-name {
+ margin: 24px 24px 24px 0px;
+ color: var(--navi-color-gray-c1);
+}
+.slack-details-wrapper {
+ display: inline-flex;
+}
+.select-picker-wrapper {
+ min-height: 80px !important;
+ position: absolute;
+ z-index: 1;
+}
+.on-call-handler {
+ position: relative;
+ margin-right: 24px;
+}
+.search-input {
+ min-width: 320px !important;
+}
+
+.info-icon-wrapper {
+ display: flex;
+ align-items: center;
+ margin-top: 5px;
+ gap: 2px;
+}
+.info-icon {
+ width: 16px;
+ height: 16px;
+ margin-right: 4px;
+}
+.update-details {
+ margin-left: calc(100% - 120px);
+ margin-bottom: 31px;
+ margin-top: 12px;
+}
+.update-details-disabled {
+ margin-left: calc(100% - 120px);
+ opacity: 0;
+ pointer-events: none;
+ margin-bottom: 31px;
+ margin-top: 12px;
+}
+.team-channel-wrapper {
+ margin-right: 24px;
+ margin-top: 5px;
+}
+@mixin slack-label {
+ color: var(--navi-color-gray-c2);
+}
+.channel-label,
+.oncall-label,
+.psec-oncall-label {
+ @include slack-label;
+}
+.hint-text {
+ color: var(--navi-color-gray-c3);
+}
+.hint-text-disabled {
+ opacity: 0;
+}
diff --git a/src/Pages/TeamRevamp/partials/SlackDetails/index.tsx b/src/Pages/TeamRevamp/partials/SlackDetails/index.tsx
new file mode 100644
index 0000000..4be58a8
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/SlackDetails/index.tsx
@@ -0,0 +1,285 @@
+import { useEffect, useRef, MutableRefObject, useReducer } from 'react';
+import {
+ BorderedInput,
+ Button,
+ Tooltip,
+ Typography,
+} from '@navi/web-ui/lib/primitives';
+import { SelectPicker } from '@navi/web-ui/lib/components';
+import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
+import useOutsideClick from '@src/services/hooks/useOustideClick';
+import { useAuthData } from '@src/services/hooks/useAuth';
+import useTeamApis from '../../useTeamApis';
+import { actionTypes, initialState, reducer } from '../../constants';
+import { TeamsDetail } from '../../types';
+import { getBots } from '../Bots';
+import useGetTeamDetailsConstants from '../../util';
+import styles from './Slack.module.scss';
+
+const TeamDetails: React.FC = () => {
+ const [state, dispatchData] = useReducer(reducer, initialState);
+ const Role = useAuthData();
+ const { updateDetails } = useTeamApis();
+ const {
+ userEmail,
+ managerEmail,
+ teamDetails,
+ teamData,
+ DEFAULT_TEAM_ONCALL,
+ DEFAULT_TEAM_ONCALL_ID,
+ DEFAULT_TEAM_PSEC_ID,
+ DEFAULT_PSEC_ONCALL,
+ webChannelId,
+ } = useGetTeamDetailsConstants();
+ const searchRef = useRef(null);
+
+ useEffect(() => {
+ dispatchData({
+ type: actionTypes.SET_INPUT,
+ payload: '',
+ });
+ dispatchData({
+ type: actionTypes.SET_PSEC_INPUT,
+ payload: '',
+ });
+ }, [teamData]);
+
+ const handleslackChannelId = (
+ e: React.ChangeEvent,
+ ): void => {
+ dispatchData({
+ type: actionTypes.SET_SLACK_CHANNEL_ID,
+ payload: e.target.value,
+ });
+ };
+ const handleOncallChange = (event): void => {
+ dispatchData({
+ type: actionTypes.SET_ONCALL,
+ payload: {
+ label: event.label,
+ value: event.value,
+ },
+ });
+ dispatchData({
+ type: actionTypes.SET_INPUT,
+ payload: event.label,
+ });
+ dispatchData({
+ type: actionTypes.SET_OPEN_ONCALL,
+ payload: false,
+ });
+ };
+ const handlePsecOncall = (event): void => {
+ dispatchData({
+ type: actionTypes.SET_PSEC_ONCALL,
+ payload: {
+ label: event.label,
+ value: event.value,
+ },
+ });
+ dispatchData({
+ type: actionTypes.SET_PSEC_INPUT,
+ payload: event.label,
+ });
+ dispatchData({
+ type: actionTypes.SET_PSEC_OPEN_ONCALL,
+ payload: false,
+ });
+ };
+ const handleOncallOutsideClick = (): void => {
+ dispatchData({
+ type: actionTypes.SET_OPEN_ONCALL,
+ payload: false,
+ });
+ };
+ const handlePsecOncallOutsideClick = (): void => {
+ dispatchData({
+ type: actionTypes.SET_PSEC_OPEN_ONCALL,
+ payload: false,
+ });
+ };
+ const refOncall = useOutsideClick({
+ callback: handleOncallOutsideClick,
+ }) as MutableRefObject;
+
+ const refPsecOncall = useOutsideClick({
+ callback: handlePsecOncallOutsideClick,
+ }) as MutableRefObject;
+
+ const handleOncallPicker = (): void => {
+ if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
+ dispatchData({
+ type: actionTypes.SET_OPEN_ONCALL,
+ payload: !state.openOnCall,
+ });
+ };
+ const handleOpenPsecOnCallPicker = (): void => {
+ if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
+ dispatchData({
+ type: actionTypes.SET_PSEC_OPEN_ONCALL,
+ payload: !state.openPsecOnCall,
+ });
+ };
+ const handleInputChange = (e: React.ChangeEvent): void => {
+ dispatchData({
+ type: actionTypes.SET_INPUT,
+ payload: e.target.value,
+ });
+ };
+ const handlePsecInputChange = (
+ e: React.ChangeEvent,
+ ): void => {
+ dispatchData({
+ type: actionTypes.SET_PSEC_INPUT,
+ payload: e.target.value,
+ });
+ };
+
+ const isUserParticipant = (teamDetails: TeamsDetail): boolean | undefined => {
+ const participantEmails = teamDetails?.participants?.map(
+ (participant: any) => participant.email,
+ );
+ return participantEmails?.includes(userEmail);
+ };
+ const isHintEnabled =
+ isUserParticipant(teamDetails) ||
+ Role.includes('Admin') ||
+ userEmail === managerEmail;
+ const hintClassName = isHintEnabled
+ ? styles['hint-text']
+ : styles['hint-text-disabled'];
+
+ return (
+
+
+ {teamDetails?.name}
+ {' '}
+
+ SLACK DETAILS
+ {' '}
+
+
+
+
+
+ Team on call
+ {' '}
+
+
+
+ {state.openOnCall && (
+
+ )}
+
+
+
+ PSEC on call
+
+
+
+
+ {state.openPsecOnCall && (
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default TeamDetails;
diff --git a/src/Pages/TeamRevamp/partials/TeamMemberDetails/TeamMemberDetails.module.scss b/src/Pages/TeamRevamp/partials/TeamMemberDetails/TeamMemberDetails.module.scss
new file mode 100644
index 0000000..f6112f3
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/TeamMemberDetails/TeamMemberDetails.module.scss
@@ -0,0 +1,105 @@
+.member-details-wrapper {
+ width: 100%;
+}
+.add-member {
+ margin-bottom: 24px;
+}
+
+.details-wrapper {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ justify-content: space-around;
+ gap: 12px;
+}
+.item-details {
+ margin-top: 12px;
+ margin-left: 10px;
+ width: calc(100% - 48px);
+}
+.details-container {
+ display: flex;
+ width: 252px;
+ height: 68px;
+}
+.icon-wrapper {
+ margin-top: 12px;
+ width: 32px;
+ margin-left: 6px;
+}
+.items-wrapper {
+ width: 252px;
+ border-radius: 8px;
+ height: 95px;
+}
+.team-member-view .make-manger-button,
+.delete-icon {
+ display: none;
+}
+
+.items-wrapper .make-manger-button,
+.items-wrapper .delete-icon {
+ display: none;
+}
+
+.items-wrapper:hover .make-manger-button {
+ display: block;
+ margin-top: 4px;
+}
+.items-wrapper:hover .delete-icon {
+ display: block;
+ margin-top: 12px;
+ width: 16px;
+ margin-right: 12px;
+}
+.items-wrapper:hover {
+ background-color: var(--navi-color-blue-bg);
+ cursor: pointer;
+}
+
+.manager-wrapper {
+ width: 253px;
+ border-radius: 8px;
+}
+.remove-button {
+ background-color: var(--navi-color-red-base);
+ height: 36px;
+ width: 77px;
+ color: var(--navi-color-gray-bg-primary);
+}
+.cancel-button {
+ background: var(--navi-color-gray-bg-primary);
+ height: 36px;
+ width: 77px;
+ color: var(--navi-color-gray-c2);
+ margin-right: 12px;
+ border: 1px solid var(--navi-color-gray-border);
+ border-radius: 6px;
+ margin-right: 16px;
+}
+.team-input-wrapper {
+ width: 510px;
+ height: 36px;
+ margin-top: 12px;
+}
+.footer-wrapper {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 32px;
+}
+
+.input-wrapper-child {
+ display: flex;
+}
+.horizontal-line {
+ display: flex;
+}
+.hr-tag {
+ width: 100%;
+ margin-top: 12px;
+ margin-left: 5px;
+ border: none;
+ border-top: 2px solid var(--navi-color-gray-border);
+}
+.members-tag {
+ color: var(--navi-color-gray-c3);
+}
diff --git a/src/Pages/TeamRevamp/partials/TeamMemberDetails/index.tsx b/src/Pages/TeamRevamp/partials/TeamMemberDetails/index.tsx
new file mode 100644
index 0000000..edc2e0e
--- /dev/null
+++ b/src/Pages/TeamRevamp/partials/TeamMemberDetails/index.tsx
@@ -0,0 +1,295 @@
+import { useReducer } from 'react';
+import {
+ BorderedInput,
+ Button,
+ Typography,
+ ModalDialog,
+} from '@navi/web-ui/lib/primitives';
+import PersonIconFill from '@src/assets/PersonIconFill';
+import ManagerIconFill from '@src/assets/ManagerIconFill';
+import MemberIcon from '@src/assets/MemberIcon';
+import DeleteIcon from '@src/assets/DeleteIcon';
+import { useAuthData } from '@src/services/hooks/useAuth';
+import { useGetTeamDetailsConstants } from '@src/Pages/TeamRevamp/util';
+import useTeamApis from '../../useTeamApis';
+import {
+ actionTypes,
+ emailRegularExpression,
+ initialState,
+ reducer,
+ validEmail,
+} from '@src/Pages/TeamRevamp/constants';
+import { TeamsDetail } from '@src/Pages/TeamRevamp/types';
+import styles from './TeamMemberDetails.module.scss';
+
+type MemberType = {
+ id: string;
+ name: string;
+ email: string;
+};
+const TeamMemberDetails: React.FC = () => {
+ const [state, dispatchData] = useReducer(reducer, initialState);
+ const Role = useAuthData();
+ const { teamId, userEmail, managerEmail, manager, teamMembers, teamDetails } =
+ useGetTeamDetailsConstants();
+ const { addMemberHandler, removeTeamMember, makeManager } = useTeamApis();
+ const isUserParticipant = (teamDetails: TeamsDetail): boolean | undefined => {
+ const participantEmails = teamDetails?.participants?.map(
+ (participant: any) => participant.email,
+ );
+ return participantEmails?.includes(userEmail);
+ };
+
+ const validateEmail = (value: string): void => {
+ const emailAddresses = value.split(',').map(email => email.trim());
+ const errorMessage = emailAddresses
+ .map(email =>
+ validEmail.test(email)
+ ? emailRegularExpression.test(email)
+ ? ''
+ : 'Please enter a Navi email ID'
+ : '',
+ )
+ .find(errorMessage => errorMessage !== '');
+ dispatchData({
+ type: actionTypes.SET_ADD_MEMBER_ERROR,
+ payload: errorMessage || '',
+ });
+ };
+ const handleAddMember = (e: React.ChangeEvent): void => {
+ const inputValue = e.target.value;
+ dispatchData({
+ type: actionTypes.SET_EMAIL_IDS,
+ payload: inputValue,
+ });
+ validateEmail(inputValue);
+ };
+ const onKeyPressClickHandler = (event: React.KeyboardEvent): void => {
+ if (state.addMemberError) {
+ return;
+ }
+ if (event.key === 'Enter') {
+ addMemberHandler(state.emailIds);
+ dispatchData({
+ type: actionTypes.SET_EMAIL_IDS,
+ payload: '',
+ });
+ }
+ dispatchData({
+ type: actionTypes.SET_EMAIL_IDS,
+ payload: '',
+ });
+ };
+
+ const handleOpenModal = (member: MemberType): void => {
+ dispatchData({
+ type: actionTypes.SET_SELECTED_MEMBER,
+ payload: member,
+ });
+ dispatchData({
+ type: actionTypes.SET_OPEN_MODAL,
+ payload: true,
+ });
+ };
+ const handleResetModal = (): void => {
+ dispatchData({
+ type: actionTypes.SET_OPEN_MODAL,
+ payload: false,
+ });
+ };
+ const handleOpenDialog = (member: MemberType): void => {
+ dispatchData({
+ type: actionTypes.SET_SELECTED_MEMBER,
+ payload: member,
+ });
+ dispatchData({
+ type: actionTypes.SET_OPEN_DIALOG,
+ payload: true,
+ });
+ };
+ const handleResetDialog = (): void => {
+ dispatchData({
+ type: actionTypes.SET_OPEN_DIALOG,
+ payload: false,
+ });
+ };
+
+ const handleHoverchange = (item: MemberType): void => {
+ if (userEmail === managerEmail || Role.includes('Admin')) {
+ dispatchData({
+ type: actionTypes.SET_HOVERED,
+ payload: {
+ ishovered: false,
+ id: item.id,
+ },
+ });
+ }
+ };
+ const handleHoverIcon = (item: MemberType): void => {
+ if (userEmail === managerEmail || Role.includes('Admin')) {
+ dispatchData({
+ type: actionTypes.SET_HOVERED,
+ payload: {
+ ishovered: true,
+ id: item.id,
+ },
+ });
+ }
+ };
+ const computedClassName = (item: MemberType): string => {
+ return (
+ (manager?.name === item.name && styles['manager-wrapper']) ||
+ (userEmail !== managerEmail &&
+ !Role.includes('Admin') &&
+ styles['team-member-view']) ||
+ styles['items-wrapper']
+ );
+ };
+ return (
+
+
+
+
+ MEMBERS
+
+
{' '}
+
+ {(isUserParticipant(teamDetails) ||
+ Role.includes('Admin') ||
+ userEmail === managerEmail) && (
+
+ )}
+
+
+
+ {teamMembers?.map(item => (
+
handleHoverIcon(item)}
+ onMouseLeave={() => handleHoverchange(item)}
+ >
+
+
+ {manager?.name === item.name ? (
+
+ ) : (
+
+ {state.hovered.ishovered &&
+ state.hovered.id === item.id ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+ {' '}
+
{item.name}
+
{item.email}
+
+ {manager?.name === item.name ? 'Manager' : ''}
+ {' '}
+
+ {manager?.name !== item.name && (
+
+ )}
+
+
+ {manager?.name !== item.name && (
+
{
+ handleOpenDialog(item);
+ }}
+ className={styles['delete-icon']}
+ >
+
+
+ )}
+
+
+ ))}
+
+
{
+ makeManager(state.selectedMember?.id.toString() || '');
+ handleResetModal();
+ },
+ },
+ ]}
+ header="Update manager? "
+ onClose={handleResetModal}
+ >
+
+
+ Are you sure you want to make {`${state.selectedMember?.name}`}{' '}
+ manager of this team?
+
+
+
+
+
+
+ Are you sure you want to remove {`${state.selectedMember?.name}`}{' '}
+ from this team?
+
+
+
+
+
+
+
+
+
+
+ );
+};
+export default TeamMemberDetails;
diff --git a/src/Pages/TeamRevamp/types.ts b/src/Pages/TeamRevamp/types.ts
new file mode 100644
index 0000000..47cebde
--- /dev/null
+++ b/src/Pages/TeamRevamp/types.ts
@@ -0,0 +1,125 @@
+import { ChangeEvent } from 'react';
+import { TeamState } from '@src/slices/team1Slice';
+import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
+export interface SearchInputComponentProps {
+ value: string;
+ onChange: (event: ChangeEvent) => void;
+ isAdmin: boolean;
+}
+export interface useTeamApiProps {
+ addMemberHandler: (emailIds: string) => void;
+ removeTeamMember: (memberId: string) => void;
+ makeManager: (memberId: string) => void;
+ updateDetails: (
+ slackChannelId: string,
+ oncall: string,
+ psecOncall: string,
+ ) => void;
+}
+export interface TeamsData {
+ id?: string;
+ name?: string;
+ slackUserIds?: Array;
+ lastUpdatedAt?: string;
+ expand?: boolean;
+}
+export interface CreateTeamProps {
+ setTeam: React.Dispatch<
+ React.SetStateAction
+ >;
+}
+export interface BotsData {
+ id: string;
+ name: string;
+}
+
+export interface TeamFormProps {
+ teamId: string;
+ isExpanded: boolean;
+ setLastUpdatedAt: (value: string) => void;
+}
+
+export const slackUserOptions = [
+ {
+ label: 'Email-id',
+ value: 'WorkEmailIds',
+ },
+ {
+ label: 'Slack user-id',
+ value: 'SlackUserIds',
+ },
+];
+
+export const userInputPlaceholders = {
+ 'Email-id': 'email ids',
+ 'Slack user-id': 'slack user ids',
+};
+
+export interface PickerOptionProps {
+ label: string;
+ value: string;
+}
+export interface Details {
+ teamId: number | undefined;
+ userEmail: string | undefined;
+ managerEmail: string | undefined;
+ manager: Participant | undefined;
+ teamMembers: any;
+ teamDetails: TeamsDetail;
+ teamData: TeamState;
+ DEFAULT_TEAM_ONCALL: string;
+ DEFAULT_TEAM_ONCALL_ID: string;
+ DEFAULT_TEAM_PSEC_ID: string;
+ DEFAULT_PSEC_ONCALL: string;
+ webChannelId: string;
+}
+export interface Participant {
+ name: string;
+ email: string;
+ id: string;
+}
+export interface TeamsDetail {
+ id?: number;
+ lastUpdatedAt?: string;
+ managerId?: string;
+ name?: string;
+ oncall?: { id: string; name: string };
+ participants?: Array;
+ pse_oncall?: { id: string; name: string };
+ slackUserIds?: string[];
+ webhookSlackChannelId?: string;
+ webhookSlackChannelName?: string;
+}
+export interface AppState {
+ openDialog: boolean;
+ openModal: boolean;
+ emailIds: string;
+ selectedMember?: Participant;
+ hovered: {
+ ishovered: boolean;
+ id: string;
+ };
+ slackChannelId: string;
+ oncall: {
+ label: string;
+ value: string;
+ };
+ psecOncall: {
+ label: string;
+ value: string;
+ };
+ input: string;
+ psecInput: string;
+ openOnCall: boolean;
+ openPsecOnCall: boolean;
+ teamName: string;
+ teamNameError: string;
+ emailError: string;
+ email: string;
+ clearModal: boolean;
+ addMemberError: string;
+}
+export interface ActionType {
+ type: string;
+ payload: any;
+}
diff --git a/src/Pages/TeamRevamp/useTeamApis.tsx b/src/Pages/TeamRevamp/useTeamApis.tsx
new file mode 100644
index 0000000..90e56bf
--- /dev/null
+++ b/src/Pages/TeamRevamp/useTeamApis.tsx
@@ -0,0 +1,147 @@
+import { useReducer } from 'react';
+import { useDispatch } from 'react-redux';
+import { toast } from '@navi/web-ui/lib/primitives/Toast';
+import { AppDispatch } from '@src/store';
+import { ApiService } from '@src/services/api';
+import { fetchTeamDetails, fetchTeams } from '@src/slices/team1Slice';
+import {
+ MAKE_MANAGER,
+ REMOVE_TEAM_MEMBER,
+ UPDATE_TEAM_DATA,
+ actionTypes,
+ initialState,
+ reducer,
+} from './constants';
+import { useGetTeamDetailsConstants } from './util';
+import { useTeamApiProps } from './types';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
+
+const useTeamApis = (): useTeamApiProps => {
+ const dispatch = useDispatch();
+ const { teamId } = useGetTeamDetailsConstants();
+ const [state, dispatchData] = useReducer(reducer, initialState);
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
+
+ const addMemberHandler = (emailIds: string): void => {
+ fireEvent(EVENT_NAME.Houston_Add_Member, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ const endPoint = UPDATE_TEAM_DATA();
+ const finalSlackData = emailIds.includes(',')
+ ? emailIds.split(',').map(item => item?.trim())
+ : [emailIds];
+ console.log(teamId, emailIds, finalSlackData);
+ ApiService.post(endPoint, {
+ id: teamId,
+ workEmailIds: finalSlackData,
+ })
+ .then(response => {
+ toast.success(response?.data?.data);
+ dispatch(fetchTeamDetails(teamId?.toString() || ''));
+ dispatch(fetchTeams());
+ })
+ .catch(error => {
+ const toastMessage = `${
+ error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message}`
+ : 'Something went wrong, please try again later'
+ }`;
+
+ toast.error(toastMessage);
+ });
+ };
+ const removeTeamMember = (memberId: string): void => {
+ fireEvent(EVENT_NAME.Houston_Remove_Member, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ const endpoint = REMOVE_TEAM_MEMBER(teamId?.toString() || '', memberId);
+ ApiService.delete(endpoint)
+ .then(response => {
+ if (response.status === 200) {
+ toast.success(response.data.data);
+ dispatch(fetchTeamDetails(teamId?.toString() || ''));
+ dispatch(fetchTeams());
+ } else {
+ toast.error(response.data.error.message);
+ }
+ })
+ .catch(error => {
+ toast.error(`Error removing member from team : ${error.message}`);
+ });
+ };
+
+ const makeManager = (memberId: string): void => {
+ fireEvent(EVENT_NAME.Houston_Make_Manager, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ const endpoint = MAKE_MANAGER(teamId?.toString() || '', memberId);
+
+ ApiService.patch(endpoint, {})
+ .then(response => {
+ if (response.status === 200) {
+ toast.success(response.data.data);
+ dispatch(fetchTeamDetails(teamId?.toString() || ''));
+ } else {
+ toast.error(response.data.error.message);
+ }
+ })
+ .catch(error => {
+ toast.error(`Error in making manager of team: ${error.message}`);
+ });
+ };
+ const updateDetails = (
+ slackChannelId: string,
+ oncall: string,
+ psecOncall: string,
+ ): void => {
+ if (oncall) {
+ fireEvent(EVENT_NAME.Houston_Add_Oncall, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ }
+
+ if (psecOncall) {
+ fireEvent(EVENT_NAME.Houston_Add_Pseconcall, {
+ screen_name: SCREEN_NAME.TEAM_PAGE,
+ });
+ }
+ const endPoint = UPDATE_TEAM_DATA();
+ ApiService.post(endPoint, {
+ id: teamId,
+ webhook_slack_channel: slackChannelId,
+ on_call_handle: oncall,
+ pse_on_call_id: psecOncall,
+ })
+ .then(response => {
+ toast.success(response?.data?.data);
+ dispatch(fetchTeamDetails(teamId?.toString() || ''));
+ dispatchData({
+ type: actionTypes.SET_OPEN_ONCALL,
+ payload: false,
+ });
+ dispatchData({
+ type: actionTypes.SET_PSEC_OPEN_ONCALL,
+ payload: false,
+ });
+ })
+ .catch(error => {
+ const toastMessage = `${
+ error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message}`
+ : 'Something went wrong,Pls try again later'
+ }`;
+ toast.error(toastMessage);
+ });
+ };
+
+ return {
+ addMemberHandler,
+ removeTeamMember,
+ makeManager,
+ updateDetails,
+ };
+};
+
+export default useTeamApis;
diff --git a/src/Pages/TeamRevamp/util.ts b/src/Pages/TeamRevamp/util.ts
new file mode 100644
index 0000000..e248c44
--- /dev/null
+++ b/src/Pages/TeamRevamp/util.ts
@@ -0,0 +1,57 @@
+import { useSelector } from 'react-redux';
+import { TeamState, selectSearchTeamData } from '@src/slices/team1Slice';
+import { Details } from './types';
+export const useTeamDetails = (): TeamState => {
+ return useSelector(selectSearchTeamData);
+};
+
+export const useGetTeamDetailsConstants = (): Details => {
+ const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
+ const teamData = useTeamDetails();
+ const teamDetails = teamData.teamDetails;
+ const teamId = teamDetails?.id;
+ const userEmail = userData?.emailId || '';
+ const managerEmail = teamDetails?.participants?.find(
+ item => item.id === teamDetails?.managerId,
+ )?.email;
+ const manager = teamDetails?.participants?.find(
+ item => item.id === teamDetails?.managerId,
+ );
+ const teamMembers =
+ teamDetails?.participants?.map(item => ({
+ name: item.name,
+ email: item.email,
+ id: item.id,
+ })) || [];
+ const DEFAULT_TEAM_ONCALL = teamDetails?.oncall?.name
+ ? `@${teamDetails?.oncall?.name}`
+ : '';
+ const DEFAULT_TEAM_ONCALL_ID = teamDetails?.oncall?.id || '';
+ const DEFAULT_TEAM_PSEC_ID = teamDetails?.pse_oncall?.id || '';
+ const DEFAULT_PSEC_ONCALL =
+ teamDetails?.pse_oncall && teamDetails?.pse_oncall?.name != ''
+ ? '@' + teamDetails?.pse_oncall?.name
+ : '';
+ const webChannelId =
+ teamDetails?.webhookSlackChannelName !== 'not found'
+ ? `@${teamDetails?.webhookSlackChannelName}`
+ : teamDetails?.webhookSlackChannelId
+ ? teamDetails?.webhookSlackChannelId
+ : '';
+ return {
+ teamId,
+ userEmail,
+ managerEmail,
+ manager,
+ teamMembers,
+ teamDetails,
+ teamData,
+ DEFAULT_TEAM_ONCALL,
+ DEFAULT_TEAM_ONCALL_ID,
+ DEFAULT_TEAM_PSEC_ID,
+ DEFAULT_PSEC_ONCALL,
+ webChannelId,
+ };
+};
+
+export default useGetTeamDetailsConstants;
diff --git a/src/assets/AlertIcon.tsx b/src/assets/AlertIcon.tsx
new file mode 100644
index 0000000..71e325c
--- /dev/null
+++ b/src/assets/AlertIcon.tsx
@@ -0,0 +1,38 @@
+import React, { FC, useState } from 'react';
+import { IconProps } from './types';
+
+const AlertIcon: FC = ({
+ width = '24',
+ height = '24',
+ onClick,
+ ...restProps
+}) => {
+ return (
+
+ );
+};
+
+export default AlertIcon;
diff --git a/src/assets/DeleteIcon.tsx b/src/assets/DeleteIcon.tsx
new file mode 100644
index 0000000..8684cc8
--- /dev/null
+++ b/src/assets/DeleteIcon.tsx
@@ -0,0 +1,26 @@
+import { FC } from 'react';
+import { IconProps } from './types';
+
+const DeleteIcon: FC = ({
+ width = '24',
+ height = '24',
+ onClick,
+ ...restProps
+}) => {
+ return (
+
+ );
+};
+
+export default DeleteIcon;
diff --git a/src/assets/FilterIcon.tsx b/src/assets/FilterIcon.tsx
index 9d066f5..0f11913 100644
--- a/src/assets/FilterIcon.tsx
+++ b/src/assets/FilterIcon.tsx
@@ -1,4 +1,4 @@
-import React, { FC } from 'react';
+import { FC } from 'react';
import { IconProps } from './types';
diff --git a/src/assets/ManagerIconFill.tsx b/src/assets/ManagerIconFill.tsx
new file mode 100644
index 0000000..b594c29
--- /dev/null
+++ b/src/assets/ManagerIconFill.tsx
@@ -0,0 +1,28 @@
+import React, { FC } from 'react';
+import { IconProps } from './types';
+
+const ManagerIconFill: FC = ({
+ width = '32',
+ height = '32',
+ ...restProps
+}) => {
+ return (
+
+ );
+};
+
+export default ManagerIconFill;
diff --git a/src/assets/MemberIcon.tsx b/src/assets/MemberIcon.tsx
new file mode 100644
index 0000000..5dbb4be
--- /dev/null
+++ b/src/assets/MemberIcon.tsx
@@ -0,0 +1,28 @@
+import React, { FC } from 'react';
+import { IconProps } from './types';
+
+const MemberIcon: FC = ({
+ width = '32',
+ height = '32',
+ ...restProps
+}) => {
+ return (
+
+ );
+};
+
+export default MemberIcon;
diff --git a/src/assets/PersonIcon.tsx b/src/assets/PersonIcon.tsx
index 352a268..343ca4e 100644
--- a/src/assets/PersonIcon.tsx
+++ b/src/assets/PersonIcon.tsx
@@ -7,8 +7,6 @@ const PersonIcon: FC = ({
onClick,
...restProps
}) => {
- const [isHovered, setIsHovered] = useState(false);
-
return (
);
diff --git a/src/assets/PersonIconFill.tsx b/src/assets/PersonIconFill.tsx
new file mode 100644
index 0000000..1e22078
--- /dev/null
+++ b/src/assets/PersonIconFill.tsx
@@ -0,0 +1,33 @@
+import React, { FC, MouseEventHandler, useState } from 'react';
+import { IconProps } from './types';
+
+const PersonIconFill: FC = ({
+ width = '32',
+ height = '32',
+ onClick,
+ ...restProps
+}) => {
+ const [isHovered, setIsHovered] = useState(false);
+ return (
+
+ );
+};
+
+export default PersonIconFill;
diff --git a/src/assets/SelectedPersonIcon.tsx b/src/assets/SelectedPersonIcon.tsx
new file mode 100644
index 0000000..1b4d066
--- /dev/null
+++ b/src/assets/SelectedPersonIcon.tsx
@@ -0,0 +1,28 @@
+import { FC, MouseEventHandler, useState } from 'react';
+import { IconProps } from './types';
+
+const PersonIcon: FC = ({
+ width = '24',
+ height = '24',
+ onClick,
+ ...restProps
+}) => {
+ return (
+
+ );
+};
+
+export default PersonIcon;
diff --git a/src/assets/TeamIcon.tsx b/src/assets/TeamIcon.tsx
new file mode 100644
index 0000000..7907d0d
--- /dev/null
+++ b/src/assets/TeamIcon.tsx
@@ -0,0 +1,33 @@
+import { IconProps } from '@navi/web-ui/lib/icons/types';
+import { FC } from 'react';
+
+const TeamIcon: FC = () => {
+ return (
+
+ );
+};
+
+export default TeamIcon;
diff --git a/src/components/LeftNav/index.tsx b/src/components/LeftNav/index.tsx
index 840cad3..0ee533d 100644
--- a/src/components/LeftNav/index.tsx
+++ b/src/components/LeftNav/index.tsx
@@ -10,6 +10,7 @@ import LeadIcon from '@navi/web-ui/lib/icons/LeadIcon';
import { NavItemType } from '@navi/web-ui/lib/components/Navbar/types';
import { Typography } from '@navi/web-ui/lib/primitives';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
+import TeamIcon from '../../assets/TeamIcon';
import JiraDashboardIcon from '@src/assets/JiraDashboardIcon';
import Dialog from '../Dialog';
import styles from './LeftNav.module.scss';
@@ -58,7 +59,7 @@ const LeftNav: React.FC = ({ children }) => {
itemType: 'simpleNavItem',
label: 'Team',
route: '/team',
- Icon: LeadIcon,
+ Icon: TeamIcon,
handleNavigation: () => navigate('/team'),
},
{
diff --git a/src/router.tsx b/src/router.tsx
index f1c1761..dd6c79e 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -5,6 +5,7 @@ const Incident = lazy(() => import('./Pages/Incidents/index'));
const Team = lazy(() => import('./Pages/Team/index'));
const Severity = lazy(() => import('./Pages/Severity/index'));
const Tableau = lazy(() => import('./Pages/Tableau/index'));
+const TeamRevamp = lazy(() => import('./Pages/TeamRevamp/index'));
const JiraDashboard = lazy(() => import('./Pages/JiraDashboard/index'));
import { CustomRouteObject } from './types';
@@ -19,11 +20,6 @@ const routes: CustomRouteObject[] = [
path: '/incident/:incidentId',
element: ,
},
- {
- id: 'TEAM',
- path: '/team',
- element: ,
- },
{
id: 'SEVERITY',
path: '/severity',
@@ -34,6 +30,11 @@ const routes: CustomRouteObject[] = [
path: '/metrics',
element: ,
},
+ {
+ id: 'TEAM',
+ path: '/team',
+ element: ,
+ },
{
id: 'JIRA_DASHBOARD',
path: '/jiraDashboard',
diff --git a/src/services/hooks/isFirstRender.tsx b/src/services/hooks/isFirstRender.tsx
new file mode 100644
index 0000000..2fdb81e
--- /dev/null
+++ b/src/services/hooks/isFirstRender.tsx
@@ -0,0 +1,10 @@
+import { useRef } from 'react';
+const useIsFirstRender = (): boolean => {
+ const isFirst = useRef(true);
+ if (isFirst.current) {
+ isFirst.current = false;
+ return true;
+ }
+ return isFirst.current;
+};
+export default useIsFirstRender;
diff --git a/src/services/hooks/useAuth.tsx b/src/services/hooks/useAuth.tsx
new file mode 100644
index 0000000..0ff1b17
--- /dev/null
+++ b/src/services/hooks/useAuth.tsx
@@ -0,0 +1,12 @@
+import { useState, useEffect } from 'react';
+const useAuthData = () => {
+ const [userRole, setUserRole] = useState('');
+
+ useEffect(() => {
+ const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
+ setUserRole(userData?.roles || []);
+ }, []);
+
+ return userRole;
+};
+export { useAuthData };
diff --git a/src/slices/team1Slice.tsx b/src/slices/team1Slice.tsx
new file mode 100644
index 0000000..a28dc06
--- /dev/null
+++ b/src/slices/team1Slice.tsx
@@ -0,0 +1,119 @@
+import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+
+import {
+ FETCH_SINGLE_TEAM_DATA,
+ FETCH_TEAM_DATA,
+} from '@src/Pages/TeamRevamp/constants';
+import { TeamsData, TeamsDetail } from '@src/Pages/TeamRevamp/types';
+import { ApiService } from '@src/services/api';
+import { RootState } from '@src/store';
+
+export interface TeamState {
+ data: TeamsData;
+ isPending: boolean;
+ modalOpen: boolean;
+ teamDetails: TeamsDetail;
+ error: string;
+ selectedTeam: TeamsDetail;
+ newTeam: number | string;
+}
+
+type TeamInitialState = {
+ teams: TeamState;
+};
+
+const initialState: TeamInitialState = {
+ teams: {
+ data: {},
+ error: '',
+ isPending: false,
+ modalOpen: false,
+ teamDetails: {},
+ selectedTeam: {},
+ newTeam: '',
+ },
+};
+
+export const fetchTeams = createAsyncThunk('team/fetchTeams', async () => {
+ const endPoint = FETCH_TEAM_DATA;
+ return await ApiService.get(endPoint);
+});
+
+export const fetchTeamDetails = createAsyncThunk(
+ 'team/fetchTeamDetails',
+ async (teamId: string) => {
+ const endPoint = FETCH_SINGLE_TEAM_DATA(teamId);
+ return await ApiService.get(endPoint);
+ },
+);
+
+const team1Slice = createSlice({
+ name: 'team1',
+ initialState,
+ reducers: {
+ setTeamData: (state, action: PayloadAction) => {
+ state.teams.data = action.payload;
+ },
+ setModalOpen: (state, action: PayloadAction) => {
+ state.teams.modalOpen = action.payload;
+ },
+ setTeamDetails: (state, action: PayloadAction) => {
+ state.teams.teamDetails = action.payload;
+ },
+ setNewTeam: (state, action: PayloadAction) => {
+ state.teams.newTeam = action.payload;
+ },
+ setSelectedTeam: (state, action: PayloadAction) => {
+ state.teams.selectedTeam = action.payload;
+ },
+ },
+
+ extraReducers: builder => {
+ builder.addCase(fetchTeams.fulfilled, (state, action) => {
+ state.teams.data = action.payload.data.data;
+ state.teams.isPending = false;
+ state.teams.error = '';
+ if (!state.teams.selectedTeam) {
+ state.teams.selectedTeam = action.payload.data.data[0];
+ }
+ });
+ builder.addCase(fetchTeams.rejected, (state, action) => {
+ state.teams.error = action.error.message ?? 'Something went wrong';
+ state.teams.data = {};
+ state.teams.isPending = false;
+ state.teams.selectedTeam = {};
+ });
+ builder.addCase(fetchTeams.pending, (state, action) => {
+ state.teams.isPending = true;
+ state.teams.data = {};
+ state.teams.error = '';
+ state.teams.selectedTeam = {};
+ });
+ builder.addCase(fetchTeamDetails.fulfilled, (state, action) => {
+ state.teams.teamDetails = action.payload.data.data;
+ state.teams.isPending = false;
+ state.teams.error = '';
+ });
+ builder.addCase(fetchTeamDetails.rejected, (state, action) => {
+ state.teams.error = action.error.message ?? 'Something went wrong';
+ state.teams.teamDetails = {};
+ state.teams.isPending = false;
+ });
+ builder.addCase(fetchTeamDetails.pending, (state, action) => {
+ state.teams.isPending = true;
+ state.teams.teamDetails = {};
+ state.teams.error = '';
+ });
+ },
+});
+
+export const selectSearchTeamData = (state: RootState) => state.team1.teams;
+
+export const {
+ setTeamData,
+ setModalOpen,
+ setTeamDetails,
+ setSelectedTeam,
+ setNewTeam,
+} = team1Slice.actions;
+export default team1Slice.reducer;
diff --git a/src/store/index.tsx b/src/store/index.tsx
index 96c692b..f226248 100644
--- a/src/store/index.tsx
+++ b/src/store/index.tsx
@@ -2,12 +2,17 @@ import { configureStore } from '@reduxjs/toolkit';
import teamReducer from '../slices/teamSlice';
import severityReducer from '../slices/sevSlice';
import dashboardReducer from '../slices/dashboardSlice';
+
+import teamReducer1 from '../slices/team1Slice';
+
import jiraDashboardReducer from '../slices/jiraDashboardSlice';
+
const store = configureStore({
reducer: {
team: teamReducer,
severity: severityReducer,
dashboard: dashboardReducer,
+ team1: teamReducer1,
jiraDashboard: jiraDashboardReducer,
},
});