Merge pull request #30 from navi-sa/ayush-task
remove Team member and make manager
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,3 +12,6 @@ yarn-error.log*
|
||||
# configuration files
|
||||
config.js
|
||||
config.qa.js
|
||||
|
||||
yarn.lock
|
||||
package.lock.json
|
||||
8
config.dev.js
Normal file
8
config.dev.js
Normal file
@@ -0,0 +1,8 @@
|
||||
window.config = {
|
||||
AUTH_BASE_URL: 'https://qa-dark-knight.np.navi-sa.in',
|
||||
AUTH_CLIENT_ID: 'O3G7sCWk4r',
|
||||
ENVIRONMENT: 'development',
|
||||
BASE_API_URL: 'http://localhost:8080',
|
||||
CSP_HEADER:
|
||||
"default-src 'self' https://fonts.googleapis.com; frame-ancestors 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' *.navi-sa.in; img-src 'self' https://avatars.slack-edge.com/ data:",
|
||||
};
|
||||
7250
package-lock.json
generated
Normal file
7250
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,7 @@
|
||||
"sass": "^1.58.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.7",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
@@ -74,7 +73,6 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,14 @@ export const UPDATE_TEAM_DATA = (): string => {
|
||||
return `${window?.config?.BASE_API_URL}/teams`;
|
||||
};
|
||||
|
||||
export const REMOVE_TEAM_MEMBER = (teamId: string, userId: string): string => {
|
||||
return `${window?.config?.BASE_API_URL}/houston/teams/${teamId}/members/${userId}`;
|
||||
};
|
||||
|
||||
export const MAKE_MANAGER = (teamId: string, userId: string): string => {
|
||||
return `${window?.config?.BASE_API_URL}/houston/teams/${teamId}/manager/${userId}`;
|
||||
};
|
||||
|
||||
export const regularExpression = /^[a-zA-Z][a-zA-Z0-9_ -]{1,48}[a-zA-Z0-9]$/;
|
||||
|
||||
export const emailRegularExpression = /^[a-zA-Z]+\.[a-zA-Z]+@navi\.com$/;
|
||||
|
||||
@@ -105,16 +105,20 @@ const TeamForm = (props: TeamFormProps) => {
|
||||
|
||||
const returnSlackChannel = () => {
|
||||
return data?.webhookSlackChannelName && data?.webhookSlackChannelId ? (
|
||||
<Typography variant="p4" className={DrawerStyles['slack-channel']}>
|
||||
<SlackIcon />
|
||||
<a
|
||||
href={`https://go-navi.slack.com/archives/${data?.webhookSlackChannelId}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{data?.webhookSlackChannelName}
|
||||
</a>
|
||||
</Typography>
|
||||
<div className="slack-channel-wrapper">
|
||||
<Typography variant="p4" className={DrawerStyles['slack-channel']}>
|
||||
<SlackIcon />
|
||||
<div className="slack-channel-content">
|
||||
<a
|
||||
href={`https://go-navi.slack.com/archives/${data?.webhookSlackChannelId}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{data?.webhookSlackChannelName}
|
||||
</a>
|
||||
</div>
|
||||
</Typography>
|
||||
</div>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
@@ -146,9 +150,13 @@ const TeamForm = (props: TeamFormProps) => {
|
||||
<Typography variant="p3" color="#585757">
|
||||
Members:
|
||||
</Typography>
|
||||
<MembersDetails data={data} />
|
||||
|
||||
<MembersDetails data={data} fetchTeamById={fetchTeamById} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles['content-wrapper']}></div>
|
||||
|
||||
<hr className={styles['vertical-line']} />
|
||||
<div className={styles['content-wrapper']}>
|
||||
<div className={styles['custom-bordered-input']}>
|
||||
|
||||
25
src/assets/ManagerIcon.tsx
Normal file
25
src/assets/ManagerIcon.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { FC } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const PersonIcon: FC<IconProps> = ({
|
||||
width = '16',
|
||||
height = '16',
|
||||
...restProps
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM2 16C1.45 16 0.979333 15.8043 0.588 15.413C0.196 15.021 0 14.55 0 14V13.2C0 12.6333 0.146 12.1123 0.438 11.637C0.729334 11.1623 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64567 4.75 9.387C5.81667 9.129 6.9 9 8 9C9.1 9 10.1833 9.129 11.25 9.387C12.3167 9.64567 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2707 11.1623 15.562 11.637C15.854 12.1123 16 12.6333 16 13.2V14C16 14.55 15.8043 15.021 15.413 15.413C15.021 15.8043 14.55 16 14 16H2Z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonIcon;
|
||||
32
src/assets/PersonIcon.tsx
Normal file
32
src/assets/PersonIcon.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { FC, MouseEventHandler, useState } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const PersonIcon: FC<IconProps> = ({
|
||||
width = '24',
|
||||
height = '24',
|
||||
onClick,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onClick as MouseEventHandler<SVGSVGElement>}
|
||||
onMouseEnter={() => setIsHovered(true)} // Set isHovered to true on hover
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<path
|
||||
d="M8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM2 16C1.45 16 0.979333 15.8043 0.588 15.413C0.196 15.021 0 14.55 0 14V13.2C0 12.6333 0.146 12.1123 0.438 11.637C0.729334 11.1623 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64567 4.75 9.387C5.81667 9.129 6.9 9 8 9C9.1 9 10.1833 9.129 11.25 9.387C12.3167 9.64567 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2707 11.1623 15.562 11.637C15.854 12.1123 16 12.6333 16 13.2V14C16 14.55 15.8043 15.021 15.413 15.413C15.021 15.8043 14.55 16 14 16H2Z"
|
||||
fill={isHovered ? '#0276FE' : '#969696'}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonIcon;
|
||||
@@ -2,12 +2,27 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
margin-top: 5%;
|
||||
}
|
||||
|
||||
.participant-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.participant-detail-nest {
|
||||
display: flex;
|
||||
}
|
||||
.participant-detail-nest:hover .mark-as-manager {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mark-as-manager {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.remove-logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
11
src/components/MembersDetails/UseAuthHook.tsx
Normal file
11
src/components/MembersDetails/UseAuthHook.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
export const useAuthData = () => {
|
||||
const [userRole, setUserRole] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
|
||||
setUserRole(userData?.roles || []);
|
||||
}, []);
|
||||
|
||||
return userRole;
|
||||
};
|
||||
@@ -1,13 +1,106 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { Avatar, Typography } from '@navi/web-ui/lib/primitives';
|
||||
import { Avatar, Typography, Tag } from '@navi/web-ui/lib/primitives';
|
||||
|
||||
import styles from './Members.module.scss';
|
||||
import { toast } from '@navi/web-ui/lib/primitives/Toast';
|
||||
import { ApiService } from '@src/services/api';
|
||||
import { ModalDialog } from '@navi/web-ui/lib/primitives';
|
||||
import { useAuthData } from './UseAuthHook';
|
||||
import PersonIcon from '@src/assets/PersonIcon';
|
||||
import ManagerIcon from '@src/assets/ManagerIcon';
|
||||
import { CloseIcon } from '@navi/web-ui/lib/icons';
|
||||
|
||||
import {
|
||||
REMOVE_TEAM_MEMBER,
|
||||
MAKE_MANAGER,
|
||||
FETCH_SINGLE_TEAM_DATA,
|
||||
} from '@src/Pages/Team/constants';
|
||||
|
||||
const MembersDetails = (props: any) => {
|
||||
const { data } = props;
|
||||
const { fetchTeamById } = props;
|
||||
const [data, setData] = useState(props.data);
|
||||
const totalMembers = data?.participants?.length;
|
||||
const [showTotalMembers, setShowTotalMembers] = useState(10);
|
||||
const [showRemoveButton, setShowRemoveButton] = useState(false);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
const fetchSingleTeamData = teamId => {
|
||||
const fetchTeamDataEndpoint = FETCH_SINGLE_TEAM_DATA(teamId);
|
||||
ApiService.get(fetchTeamDataEndpoint)
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
const updatedData = response?.data?.data;
|
||||
setData(updatedData);
|
||||
} else {
|
||||
toast.error(response.data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
const toastMessage = `${
|
||||
error?.response?.data?.error?.message
|
||||
? `${error?.response?.data?.error?.message},`
|
||||
: ''
|
||||
}`;
|
||||
toast.error(toastMessage);
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveUser = (teamId, slackUserId) => {
|
||||
const endpoint = REMOVE_TEAM_MEMBER(teamId, slackUserId);
|
||||
ApiService.delete(endpoint)
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
toast.success(response.data.data);
|
||||
fetchSingleTeamData(teamId);
|
||||
} else {
|
||||
toast.error(response.data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
toast.error(
|
||||
`Error removing ${slackUserId} from team ${teamId}: ${error.message}`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleMakeManager = (teamId, slackUserId) => {
|
||||
setOpen(false);
|
||||
const endpoint = MAKE_MANAGER(teamId, slackUserId);
|
||||
|
||||
ApiService.patch(endpoint, {})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
toast.success(response.data.data);
|
||||
fetchSingleTeamData(teamId);
|
||||
} else {
|
||||
toast.error(response.data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
toast.error(
|
||||
`Error making ${slackUserId} a manager in team ${teamId}: ${error.message}`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
|
||||
const userRoles = useAuthData();
|
||||
console.log('userRoles', userRoles);
|
||||
|
||||
const managerEmail = data?.participants?.find(
|
||||
participant => participant.id === data.managerId,
|
||||
)?.email;
|
||||
const storedEmail = localStorage.getItem('email-id');
|
||||
const isAdmin = userRoles.includes('Admin');
|
||||
|
||||
useEffect(() => {
|
||||
if (managerEmail === storedEmail || isAdmin) {
|
||||
setShowRemoveButton(true);
|
||||
} else {
|
||||
setShowRemoveButton(false);
|
||||
}
|
||||
}, [data, userRoles]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -32,13 +125,94 @@ const MembersDetails = (props: any) => {
|
||||
</div>
|
||||
) : (
|
||||
<div onClick={() => setShowTotalMembers(10)}>
|
||||
<Typography variant="h5">View less</Typography>
|
||||
<Typography variant="h5">
|
||||
View less
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles['participant-detail']}>
|
||||
<div className={styles['participant-detail-nest']}>
|
||||
{showRemoveButton &&
|
||||
!(participant.email === managerEmail) && (
|
||||
<div className={styles[`mark-as-manager`]}>
|
||||
<Tag
|
||||
color="blue"
|
||||
label="Mark as manager"
|
||||
variant="transparent"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showRemoveButton &&
|
||||
!(participant.email === managerEmail) && (
|
||||
<>
|
||||
<PersonIcon onClick={() => setOpen(true)} />
|
||||
<ModalDialog
|
||||
open={open}
|
||||
footerButtons={[
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: function noRefCheck() {
|
||||
setOpen(false);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Change Manager',
|
||||
onClick: function noRefCheck() {
|
||||
handleMakeManager(data.id, participant.id);
|
||||
},
|
||||
},
|
||||
]}
|
||||
header="Are you sure you want to make this member a manager ?"
|
||||
onClose={function noRefCheck() {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<Typography variant="p4">
|
||||
Teams can only have 1 manager. The current manager
|
||||
will be changed to a member
|
||||
</Typography>
|
||||
</ModalDialog>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles['participant-detail-nest']}>
|
||||
{showRemoveButton &&
|
||||
!(participant.email === managerEmail) && (
|
||||
<div className={styles[`mark-as-manager`]}>
|
||||
<Tag
|
||||
color="blue"
|
||||
label="Mark as manager"
|
||||
variant="transparent"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{participant.email === managerEmail && <ManagerIcon />}
|
||||
</div>
|
||||
|
||||
{showRemoveButton &&
|
||||
!(participant.email === managerEmail) && (
|
||||
<>
|
||||
|
||||
<CloseIcon
|
||||
onClick={() =>
|
||||
handleRemoveUser(data.id, participant.id)
|
||||
}
|
||||
/>
|
||||
|
||||
</>
|
||||
)}
|
||||
{participant.email === managerEmail && (
|
||||
<Typography variant="p4">
|
||||
Manager
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user