Merge pull request #30 from navi-sa/ayush-task

remove Team member and make manager
This commit is contained in:
Ajay Devarakonda
2023-09-15 12:16:08 +05:30
committed by GitHub
13 changed files with 8181 additions and 741 deletions

3
.gitignore vendored
View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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$/;

View File

@@ -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']}>

View 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
View 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;

View File

@@ -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;
}

View 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;
};

View File

@@ -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">
&nbsp;&nbsp;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) && (
<>
&nbsp;&nbsp;&nbsp;&nbsp;
<CloseIcon
onClick={() =>
handleRemoveUser(data.id, participant.id)
}
/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</>
)}
{participant.email === managerEmail && (
<Typography variant="p4">
&nbsp;&nbsp;&nbsp;&nbsp;Manager
</Typography>
)}
</div>
</div>
))}
</>

1347
yarn.lock

File diff suppressed because it is too large Load Diff