Implement functionality to transfer project to different organization (#50)

* Add GQL mutation for transfer project

* Integrate transfer project GQL client method

* Use update project GQL method for transfer project

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
2024-02-02 14:04:26 +05:30
committed by GitHub
co-authored by neeraj
parent ef89d69577
commit 8111d34d86
10 changed files with 109 additions and 196 deletions
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { Domain, DomainStatus } from 'gql-client';
import { Domain, DomainStatus, Project } from 'gql-client';
import {
Chip,
@@ -12,7 +12,7 @@ import {
Card,
} from '@material-tailwind/react';
import { ProjectDetails, RepositoryDetails } from '../../../../types/project';
import { RepositoryDetails } from '../../../../types/project';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
@@ -28,7 +28,7 @@ interface DomainCardProps {
domains: Domain[];
domain: Domain;
repo: RepositoryDetails;
project: ProjectDetails;
project: Project;
onUpdate: () => Promise<void>;
}
@@ -1,34 +1,23 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useParams, Link, useOutletContext } from 'react-router-dom';
import { Domain } from 'gql-client';
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Domain, Project } from 'gql-client';
import { Button, Typography } from '@material-tailwind/react';
import DomainCard from './DomainCard';
import { ProjectSearchOutletContext } from '../../../../types/project';
import { useGQLClient } from '../../../../context/GQLClientContext';
import repositories from '../../../../assets/repositories.json';
const Domains = () => {
const { id } = useParams();
const Domains = ({ project }: { project: Project }) => {
const client = useGQLClient();
const [domains, setDomains] = useState<Domain[]>([]);
const { projects } = useOutletContext<ProjectSearchOutletContext>();
const currentProject = useMemo(() => {
return projects.find((project) => {
return project.id === id;
});
}, [id, projects]);
const fetchDomains = async () => {
if (currentProject === undefined) {
if (project === undefined) {
return;
}
const fetchedDomains = await client.getDomains(currentProject.id);
const fetchedDomains = await client.getDomains(project.id);
setDomains(fetchedDomains.domains);
};
@@ -55,7 +44,7 @@ const Domains = () => {
key={domain.id}
// TODO: Use github API for getting linked repository
repo={repositories[0]!}
project={currentProject!}
project={project}
onUpdate={fetchDomains}
/>
);
@@ -1,23 +1,15 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form';
import toast from 'react-hot-toast';
import { Project } from 'gql-client';
import { Organization, Project } from 'gql-client';
import {
Button,
Typography,
Input,
Select,
Option,
} from '@material-tailwind/react';
import { Button, Typography, Input, Option } from '@material-tailwind/react';
import DeleteProjectDialog from './DeleteProjectDialog';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
const TEAMS = ['Airfoil'];
const DEFAULT_SELECT_TEAM = undefined;
import AsyncSelect from '../../../shared/AsyncSelect';
const CopyIcon = ({ value }: { value: string }) => {
return (
@@ -41,22 +33,26 @@ const GeneralTabPanel = ({
onUpdate: () => Promise<void>;
}) => {
const client = useGQLClient();
const [transferOrganizations, setTransferOrganizations] = useState<
Organization[]
>([]);
const [selectedTransferOrganization, setSelectedTransferOrganization] =
useState('');
const {
handleSubmit: handleTransfer,
control,
formState,
reset: transferFormReset,
} = useForm({
defaultValues: {
team: DEFAULT_SELECT_TEAM,
orgId: '',
},
});
const [openTransferDialog, setOpenTransferDialog] = useState(false);
const handleTransferProjectDialog = () =>
setOpenTransferDialog(!openTransferDialog);
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
const handleDeleteProjectDialog = () =>
setOpenDeleteDialog(!openDeleteDialog);
@@ -72,6 +68,45 @@ const GeneralTabPanel = ({
},
});
const fetchUserOrganizations = useCallback(async () => {
const { organizations } = await client.getOrganizations();
const orgsToTransfer = organizations.filter(
(org) => org.id !== project.organization.id,
);
setTransferOrganizations(orgsToTransfer);
}, [project]);
const handleTransferProject = useCallback(async () => {
const { updateProject: isTransferred } = await client.updateProject(
project.id,
{
organizationId: selectedTransferOrganization,
},
);
setOpenTransferDialog(!openTransferDialog);
if (isTransferred) {
toast.success('Project transferred');
await fetchUserOrganizations();
await onUpdate();
transferFormReset();
} else {
toast.error('Project not transrfered');
}
}, [project, selectedTransferOrganization]);
const selectedUserOrgName = useMemo(() => {
return (
transferOrganizations.find(
(org) => org.id === selectedTransferOrganization,
)?.name || ''
);
}, [transferOrganizations, selectedTransferOrganization]);
useEffect(() => {
fetchUserOrganizations();
}, [project]);
useEffect(() => {
reset({ appName: project.name, description: project.description });
}, [project]);
@@ -140,29 +175,30 @@ const GeneralTabPanel = ({
</Link>
</Typography>
<form
onSubmit={handleTransfer(() => {
handleTransferProjectDialog();
onSubmit={handleTransfer(({ orgId }) => {
setSelectedTransferOrganization(orgId);
setOpenTransferDialog(!openTransferDialog);
})}
>
<Typography variant="small" className="font-medium text-gray-800">
Choose team
</Typography>
<Controller
name="team"
name="orgId"
rules={{ required: 'This field is required' }}
control={control}
render={({ field }) => (
<Select
<AsyncSelect
{...field}
// TODO: Implement placeholder for select
label={!field.value ? 'Select an account / team' : ''}
>
{TEAMS.map((team, key) => (
<Option key={key} value={team}>
^ {team}
{transferOrganizations.map((org, key) => (
<Option key={key} value={org.id}>
^ {org.name}
</Option>
))}
</Select>
</AsyncSelect>
)}
/>
<Button
@@ -177,15 +213,15 @@ const GeneralTabPanel = ({
</form>
<ConfirmDialog
dialogTitle="Transfer project"
handleOpen={handleTransferProjectDialog}
handleOpen={() => setOpenTransferDialog(!openTransferDialog)}
open={openTransferDialog}
confirmButtonTitle="Yes, Confirm transfer"
handleConfirm={handleTransferProjectDialog}
handleConfirm={handleTransferProject}
color="blue"
>
<Typography variant="small">
Upon confirmation, your project nextjs-boilerplate will be
transferred from saugat to Airfoil.
Upon confirmation, your project {project.name} will be transferred
from {project.organization.name} to {selectedUserOrgName}.
</Typography>
</ConfirmDialog>
</div>
@@ -0,0 +1,15 @@
// https://github.com/creativetimofficial/material-tailwind/issues/419#issuecomment-1760474312
import React, { useEffect, useState } from 'react';
import { Select, SelectProps } from '@material-tailwind/react';
const AsyncSelect = React.forwardRef((props: SelectProps, ref: any) => {
const [key, setKey] = useState(0);
useEffect(() => setKey((preVal) => preVal + 1), [props]);
return <Select key={key} ref={ref} {...props} />;
});
AsyncSelect.displayName = 'AsyncSelect';
export default AsyncSelect;