forked from cerc-io/snowballtools-base
Handle remove member operation in frontend (#32)
* Add remove member gql client method * Handle remove member UI operation * Refactor fetching of project members * Rename type MemberPermission to ProjectMember * Add types to gql client response * Remove circular dependency in gql client types --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
committed by
Ashwin Phatak
co-authored by
neeraj
parent
02f7ebb9bd
commit
cbc394f9f8
@@ -12,12 +12,12 @@ import {
|
||||
Checkbox,
|
||||
} from '@material-tailwind/react';
|
||||
|
||||
import { Member, Permission } from '../../../../types/project';
|
||||
import { ProjectMember, Permission } from '../../../../types/project';
|
||||
|
||||
interface AddMemberDialogProp {
|
||||
open: boolean;
|
||||
handleOpen: () => void;
|
||||
handleAddMember: (member: Member) => void;
|
||||
handleAddMember: (projectMember: ProjectMember) => void;
|
||||
}
|
||||
|
||||
interface formData {
|
||||
@@ -52,13 +52,17 @@ const AddMemberDialog = ({
|
||||
reset();
|
||||
handleOpen();
|
||||
|
||||
const member: Member = {
|
||||
email: data.emailAddress,
|
||||
id: Math.random(),
|
||||
name: '',
|
||||
const projectMember: ProjectMember = {
|
||||
id: Math.random().toString(),
|
||||
permissions: [],
|
||||
member: {
|
||||
name: '',
|
||||
email: data.emailAddress,
|
||||
id: Math.random().toString(),
|
||||
},
|
||||
};
|
||||
|
||||
handleAddMember(member);
|
||||
handleAddMember(projectMember);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,13 +6,13 @@ import { Button, Typography } from '@material-tailwind/react';
|
||||
import DomainCard from './DomainCard';
|
||||
import {
|
||||
DomainDetails,
|
||||
ProjectsOutletContext,
|
||||
ProjectSearchOutletContext,
|
||||
} from '../../../../types/project';
|
||||
|
||||
const Domains = () => {
|
||||
const { id } = useParams();
|
||||
|
||||
const { projects } = useOutletContext<ProjectsOutletContext>();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
const currentProject = useMemo(() => {
|
||||
return projects.find((project) => {
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ import DisplayEnvironmentVariables from './DisplayEnvironmentVariables';
|
||||
import {
|
||||
EnvironmentVariable,
|
||||
Environments,
|
||||
ProjectsOutletContext,
|
||||
ProjectSearchOutletContext,
|
||||
} from '../../../../types/project';
|
||||
import HorizontalLine from '../../../HorizontalLine';
|
||||
|
||||
@@ -36,7 +36,7 @@ export type EnvironmentVariablesFormValues = {
|
||||
export const EnvironmentVariablesTabPanel = () => {
|
||||
const { id } = useParams();
|
||||
|
||||
const { projects } = useOutletContext<ProjectsOutletContext>();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
const currentProject = useMemo(() => {
|
||||
return projects.find((project) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
|
||||
import DeleteProjectDialog from './DeleteProjectDialog';
|
||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||
import { ProjectsOutletContext } from '../../../../types/project';
|
||||
import { ProjectSearchOutletContext } from '../../../../types/project';
|
||||
|
||||
const TEAMS = ['Airfoil'];
|
||||
const DEFAULT_SELECT_TEAM = undefined;
|
||||
@@ -34,7 +34,7 @@ const CopyIcon = ({ value }: { value: string }) => {
|
||||
|
||||
const GeneralTabPanel = () => {
|
||||
const { id } = useParams();
|
||||
const { projects } = useOutletContext<ProjectsOutletContext>();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
const currentProject = useMemo(() => {
|
||||
return projects.find((project: any) => project.id === id);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import {
|
||||
Select,
|
||||
@@ -34,7 +33,8 @@ interface MemberCardProps {
|
||||
isOwner: boolean;
|
||||
isPending: boolean;
|
||||
permissions: string[];
|
||||
handleDeletePendingMember: (id: number) => void;
|
||||
handleDeletePendingMember: (id: string) => void;
|
||||
removeMemberHandler: () => Promise<void>;
|
||||
}
|
||||
|
||||
const MemberCard = ({
|
||||
@@ -44,6 +44,7 @@ const MemberCard = ({
|
||||
isPending,
|
||||
permissions,
|
||||
handleDeletePendingMember,
|
||||
removeMemberHandler,
|
||||
}: MemberCardProps) => {
|
||||
const [selectedPermission, setSelectedPermission] = useState(
|
||||
permissions.join('+'),
|
||||
@@ -127,7 +128,7 @@ const MemberCard = ({
|
||||
confirmButtonTitle="Yes, Remove member"
|
||||
handleConfirm={() => {
|
||||
setRemoveMemberDialogOpen((preVal) => !preVal);
|
||||
toast.success('Member removed from project');
|
||||
removeMemberHandler();
|
||||
}}
|
||||
color="red"
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useOutletContext, useParams } from 'react-router-dom';
|
||||
import toast, { Toaster } from 'react-hot-toast';
|
||||
|
||||
@@ -6,31 +6,60 @@ import { Chip, Button, Typography } from '@material-tailwind/react';
|
||||
|
||||
import MemberCard from './MemberCard';
|
||||
|
||||
import { Member } from '../../../../types/project';
|
||||
import {
|
||||
ProjectMember,
|
||||
ProjectSearchOutletContext,
|
||||
} from '../../../../types/project';
|
||||
import AddMemberDialog from './AddMemberDialog';
|
||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||
|
||||
const FIRST_MEMBER_CARD = 0;
|
||||
|
||||
const MembersTabPanel = () => {
|
||||
const { id } = useParams();
|
||||
const client = useGQLClient();
|
||||
|
||||
const [addmemberDialogOpen, setAddMemberDialogOpen] = useState(false);
|
||||
|
||||
// @ts-expect-error create context type for projects
|
||||
const { projects } = useOutletContext();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
const currentProject = useMemo(() => {
|
||||
return projects.find((project: any) => project.id === id);
|
||||
return projects.find((project) => project.id === id);
|
||||
}, [id]);
|
||||
|
||||
const [updatedMembers, setUpdatedMembers] = useState([
|
||||
...currentProject?.members,
|
||||
]);
|
||||
const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]);
|
||||
|
||||
const addMemberHandler = useCallback((member: Member) => {
|
||||
setUpdatedMembers((val) => [...val, member]);
|
||||
const addMemberHandler = useCallback((projectMember: ProjectMember) => {
|
||||
setProjectMembers((val) => [...val, projectMember]);
|
||||
toast.success('Invitation sent');
|
||||
}, []);
|
||||
|
||||
const fetchProjectMembers = useCallback(async () => {
|
||||
if (currentProject) {
|
||||
const { projectMembers } = await client.getProjectMembers(
|
||||
currentProject.id,
|
||||
);
|
||||
|
||||
setProjectMembers(projectMembers);
|
||||
}
|
||||
}, [currentProject]);
|
||||
|
||||
const removeMemberHandler = async (projectMemberId: string) => {
|
||||
const { removeMember: isMemberRemoved } =
|
||||
await client.removeMember(projectMemberId);
|
||||
|
||||
if (isMemberRemoved) {
|
||||
toast.success('Member removed from project');
|
||||
await fetchProjectMembers();
|
||||
} else {
|
||||
toast.error('Not able to remove member');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchProjectMembers();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-2 mb-20">
|
||||
<div className="flex justify-between mb-2">
|
||||
@@ -40,7 +69,7 @@ const MembersTabPanel = () => {
|
||||
<Chip
|
||||
className="normal-case ml-3 font-normal"
|
||||
size="sm"
|
||||
value={updatedMembers.length}
|
||||
value={projectMembers.length}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,20 +82,23 @@ const MembersTabPanel = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{updatedMembers.map((member, index) => {
|
||||
{projectMembers.map((projectMember, index) => {
|
||||
return (
|
||||
<MemberCard
|
||||
member={member.member}
|
||||
key={member.id}
|
||||
member={projectMember.member}
|
||||
key={projectMember.id}
|
||||
isFirstCard={index === FIRST_MEMBER_CARD}
|
||||
isOwner={member.member.id === currentProject?.owner.id}
|
||||
isPending={member.name === ''}
|
||||
permissions={member.permissions}
|
||||
handleDeletePendingMember={(id: number) => {
|
||||
setUpdatedMembers(
|
||||
updatedMembers.filter((member) => member.id !== id),
|
||||
isOwner={projectMember.member.id === currentProject?.owner.id}
|
||||
isPending={projectMember.member.name === ''}
|
||||
permissions={projectMember.permissions}
|
||||
handleDeletePendingMember={(id: string) => {
|
||||
setProjectMembers(
|
||||
projectMembers.filter(
|
||||
(projectMember) => projectMember.member.id !== id,
|
||||
),
|
||||
);
|
||||
}}
|
||||
removeMemberHandler={() => removeMemberHandler(projectMember.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -13,11 +13,11 @@ const ProjectSearch = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
const res = await client.getOrganizations();
|
||||
const { organizations } = await client.getOrganizations();
|
||||
|
||||
// Note: select first organization as organization switching not yet implemented
|
||||
const projects = res.organizations[0]?.projects || [];
|
||||
const orgName = res.organizations[0]?.name || '';
|
||||
const projects = organizations[0].projects || [];
|
||||
const orgName = organizations[0].name || '';
|
||||
|
||||
const updatedProjectsPromises = projects.map(async (project: any) => {
|
||||
const { deployments } = await client.getDeployments(String(project.id));
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Link } from 'react-router-dom';
|
||||
import { Button, Typography, Chip } from '@material-tailwind/react';
|
||||
|
||||
import ProjectCard from '../components/projects/ProjectCard';
|
||||
import { ProjectsOutletContext } from '../types/project';
|
||||
import { ProjectSearchOutletContext } from '../types/project';
|
||||
|
||||
const Projects = () => {
|
||||
const { projects } = useOutletContext<ProjectsOutletContext>();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -5,13 +5,13 @@ import { Button, Typography } from '@material-tailwind/react';
|
||||
|
||||
import HorizontalLine from '../../components/HorizontalLine';
|
||||
import ProjectTabs from '../../components/projects/project/ProjectTabs';
|
||||
import { ProjectsOutletContext } from '../../types/project';
|
||||
import { ProjectSearchOutletContext } from '../../types/project';
|
||||
|
||||
const Project = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { projects } = useOutletContext<ProjectsOutletContext>();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
const project = useMemo(() => {
|
||||
return projects.find((project) => {
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Link, useOutletContext } from 'react-router-dom';
|
||||
import { Button, Typography, Chip } from '@material-tailwind/react';
|
||||
|
||||
import ProjectCard from '../../components/projects/ProjectCard';
|
||||
import { ProjectsOutletContext } from '../../types/project';
|
||||
import { ProjectSearchOutletContext } from '../../types/project';
|
||||
|
||||
const Projects = () => {
|
||||
const { projects } = useOutletContext<ProjectsOutletContext>();
|
||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface ProjectDetails {
|
||||
icon: string;
|
||||
name: string;
|
||||
title: string;
|
||||
owner: Member;
|
||||
organization: string;
|
||||
description: string;
|
||||
url: string;
|
||||
@@ -18,13 +19,14 @@ export interface ProjectDetails {
|
||||
};
|
||||
repositoryId: number;
|
||||
repositories: RepositoryDetails[];
|
||||
members: MemberPermission[];
|
||||
members: ProjectMember[];
|
||||
ownerId: number;
|
||||
environmentVariables: EnvironmentVariable[];
|
||||
}
|
||||
|
||||
export interface MemberPermission {
|
||||
id: number;
|
||||
export interface ProjectMember {
|
||||
id: string;
|
||||
member: Member;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
@@ -103,9 +105,9 @@ export enum Permission {
|
||||
export interface Member {
|
||||
name: string;
|
||||
email: string;
|
||||
id: number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ProjectsOutletContext {
|
||||
export interface ProjectSearchOutletContext {
|
||||
projects: ProjectDetails[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user