forked from cerc-io/snowballtools-base
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:
@@ -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;
|
||||
Reference in New Issue
Block a user