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:
parent
ef89d69577
commit
8111d34d86
@ -6,7 +6,6 @@ import assert from 'assert';
|
|||||||
import { DatabaseConfig } from './config';
|
import { DatabaseConfig } from './config';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
import { Organization } from './entity/Organization';
|
import { Organization } from './entity/Organization';
|
||||||
import { UserOrganization } from './entity/UserOrganization';
|
|
||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
import { Deployment, Environment } from './entity/Deployment';
|
import { Deployment, Environment } from './entity/Deployment';
|
||||||
import { Permission, ProjectMember } from './entity/ProjectMember';
|
import { Permission, ProjectMember } from './entity/ProjectMember';
|
||||||
@ -52,22 +51,19 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getOrganizationsByUserId (userId: number): Promise<Organization[]> {
|
async getOrganizationsByUserId (userId: number): Promise<Organization[]> {
|
||||||
const userOrganizationRepository = this.dataSource.getRepository(UserOrganization);
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
|
|
||||||
const userOrgs = await userOrganizationRepository.find({
|
const userOrgs = await organizationRepository.find({
|
||||||
relations: {
|
|
||||||
member: true,
|
|
||||||
organization: true
|
|
||||||
},
|
|
||||||
where: {
|
where: {
|
||||||
member: {
|
userOrganizations: {
|
||||||
id: userId
|
member: {
|
||||||
|
id: userId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const organizations = userOrgs.map(userOrg => userOrg.organization);
|
return userOrgs;
|
||||||
return organizations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsByOrganizationId (organizationId: number): Promise<Project[]> {
|
async getProjectsByOrganizationId (organizationId: number): Promise<Project[]> {
|
||||||
@ -96,6 +92,7 @@ export class Database {
|
|||||||
.leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true')
|
.leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true')
|
||||||
.leftJoinAndSelect('deployments.domain', 'domain')
|
.leftJoinAndSelect('deployments.domain', 'domain')
|
||||||
.leftJoinAndSelect('project.owner', 'owner')
|
.leftJoinAndSelect('project.owner', 'owner')
|
||||||
|
.leftJoinAndSelect('project.organization', 'organization')
|
||||||
.where('project.id = :projectId', {
|
.where('project.id = :projectId', {
|
||||||
projectId
|
projectId
|
||||||
})
|
})
|
||||||
|
@ -5,7 +5,7 @@ import { DeepPartial } from 'typeorm';
|
|||||||
import { OAuthApp } from '@octokit/oauth-app';
|
import { OAuthApp } from '@octokit/oauth-app';
|
||||||
|
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { deploymentToGqlType, projectMemberToGqlType, projectToGqlType, environmentVariableToGqlType, isUserOwner } from './utils';
|
import { deploymentToGqlType, projectMemberToGqlType, projectToGqlType, isUserOwner } from './utils';
|
||||||
import { Environment } from './entity/Deployment';
|
import { Environment } from './entity/Deployment';
|
||||||
import { Permission } from './entity/ProjectMember';
|
import { Permission } from './entity/ProjectMember';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
@ -22,37 +22,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any>
|
|||||||
},
|
},
|
||||||
|
|
||||||
organizations: async (_:any, __: any, context: any) => {
|
organizations: async (_:any, __: any, context: any) => {
|
||||||
const organizations = await db.getOrganizationsByUserId(context.userId);
|
return db.getOrganizationsByUserId(context.userId);
|
||||||
|
|
||||||
const orgsWithProjectsPromises = organizations.map(async (org) => {
|
|
||||||
const dbProjects = await db.getProjectsByOrganizationId(org.id);
|
|
||||||
|
|
||||||
const projectsPromises = dbProjects.map(async (dbProject) => {
|
|
||||||
const dbProjectMembers = await db.getProjectMembersByProjectId(dbProject.id);
|
|
||||||
const dbEnvironmentVariables = await db.getEnvironmentVariablesByProjectId(dbProject.id);
|
|
||||||
|
|
||||||
const projectMembers = dbProjectMembers.map(dbProjectMember => {
|
|
||||||
return projectMemberToGqlType(dbProjectMember);
|
|
||||||
});
|
|
||||||
|
|
||||||
const environmentVariables = dbEnvironmentVariables.map(dbEnvironmentVariable => {
|
|
||||||
return environmentVariableToGqlType(dbEnvironmentVariable);
|
|
||||||
});
|
|
||||||
|
|
||||||
return projectToGqlType(dbProject, projectMembers, environmentVariables);
|
|
||||||
});
|
|
||||||
|
|
||||||
const projects = await Promise.all(projectsPromises);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...org,
|
|
||||||
projects
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Add organizationMembers field when / if required
|
|
||||||
const orgsWithProjects = await Promise.all(orgsWithProjectsPromises);
|
|
||||||
return orgsWithProjects;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
project: async (_: any, { projectId }: { projectId: string }) => {
|
project: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
|
@ -137,6 +137,7 @@ input UpdateProjectInput {
|
|||||||
name: String
|
name: String
|
||||||
description: String
|
description: String
|
||||||
prodBranch: String
|
prodBranch: String
|
||||||
|
organizationId: String
|
||||||
webhooks: [String!]
|
webhooks: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { Domain, DomainStatus } from 'gql-client';
|
import { Domain, DomainStatus, Project } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Chip,
|
Chip,
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { ProjectDetails, RepositoryDetails } from '../../../../types/project';
|
import { RepositoryDetails } from '../../../../types/project';
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
import EditDomainDialog from './EditDomainDialog';
|
import EditDomainDialog from './EditDomainDialog';
|
||||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
@ -28,7 +28,7 @@ interface DomainCardProps {
|
|||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
domain: Domain;
|
domain: Domain;
|
||||||
repo: RepositoryDetails;
|
repo: RepositoryDetails;
|
||||||
project: ProjectDetails;
|
project: Project;
|
||||||
onUpdate: () => Promise<void>;
|
onUpdate: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,34 +1,23 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams, Link, useOutletContext } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Domain } from 'gql-client';
|
import { Domain, Project } from 'gql-client';
|
||||||
|
|
||||||
import { Button, Typography } from '@material-tailwind/react';
|
import { Button, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import DomainCard from './DomainCard';
|
import DomainCard from './DomainCard';
|
||||||
import { ProjectSearchOutletContext } from '../../../../types/project';
|
|
||||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
import repositories from '../../../../assets/repositories.json';
|
import repositories from '../../../../assets/repositories.json';
|
||||||
|
|
||||||
const Domains = () => {
|
const Domains = ({ project }: { project: Project }) => {
|
||||||
const { id } = useParams();
|
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
|
||||||
const [domains, setDomains] = useState<Domain[]>([]);
|
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 () => {
|
const fetchDomains = async () => {
|
||||||
if (currentProject === undefined) {
|
if (project === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchedDomains = await client.getDomains(currentProject.id);
|
const fetchedDomains = await client.getDomains(project.id);
|
||||||
setDomains(fetchedDomains.domains);
|
setDomains(fetchedDomains.domains);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,7 +44,7 @@ const Domains = () => {
|
|||||||
key={domain.id}
|
key={domain.id}
|
||||||
// TODO: Use github API for getting linked repository
|
// TODO: Use github API for getting linked repository
|
||||||
repo={repositories[0]!}
|
repo={repositories[0]!}
|
||||||
project={currentProject!}
|
project={project}
|
||||||
onUpdate={fetchDomains}
|
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 { Link } from 'react-router-dom';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { Project } from 'gql-client';
|
import { Organization, Project } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import { Button, Typography, Input, Option } from '@material-tailwind/react';
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Option,
|
|
||||||
} from '@material-tailwind/react';
|
|
||||||
|
|
||||||
import DeleteProjectDialog from './DeleteProjectDialog';
|
import DeleteProjectDialog from './DeleteProjectDialog';
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
import AsyncSelect from '../../../shared/AsyncSelect';
|
||||||
const TEAMS = ['Airfoil'];
|
|
||||||
const DEFAULT_SELECT_TEAM = undefined;
|
|
||||||
|
|
||||||
const CopyIcon = ({ value }: { value: string }) => {
|
const CopyIcon = ({ value }: { value: string }) => {
|
||||||
return (
|
return (
|
||||||
@ -41,22 +33,26 @@ const GeneralTabPanel = ({
|
|||||||
onUpdate: () => Promise<void>;
|
onUpdate: () => Promise<void>;
|
||||||
}) => {
|
}) => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
const [transferOrganizations, setTransferOrganizations] = useState<
|
||||||
|
Organization[]
|
||||||
|
>([]);
|
||||||
|
const [selectedTransferOrganization, setSelectedTransferOrganization] =
|
||||||
|
useState('');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit: handleTransfer,
|
handleSubmit: handleTransfer,
|
||||||
control,
|
control,
|
||||||
formState,
|
formState,
|
||||||
|
reset: transferFormReset,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
team: DEFAULT_SELECT_TEAM,
|
orgId: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [openTransferDialog, setOpenTransferDialog] = useState(false);
|
const [openTransferDialog, setOpenTransferDialog] = useState(false);
|
||||||
const handleTransferProjectDialog = () =>
|
|
||||||
setOpenTransferDialog(!openTransferDialog);
|
|
||||||
|
|
||||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
|
||||||
const handleDeleteProjectDialog = () =>
|
const handleDeleteProjectDialog = () =>
|
||||||
setOpenDeleteDialog(!openDeleteDialog);
|
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(() => {
|
useEffect(() => {
|
||||||
reset({ appName: project.name, description: project.description });
|
reset({ appName: project.name, description: project.description });
|
||||||
}, [project]);
|
}, [project]);
|
||||||
@ -140,29 +175,30 @@ const GeneralTabPanel = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
<form
|
<form
|
||||||
onSubmit={handleTransfer(() => {
|
onSubmit={handleTransfer(({ orgId }) => {
|
||||||
handleTransferProjectDialog();
|
setSelectedTransferOrganization(orgId);
|
||||||
|
setOpenTransferDialog(!openTransferDialog);
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Typography variant="small" className="font-medium text-gray-800">
|
<Typography variant="small" className="font-medium text-gray-800">
|
||||||
Choose team
|
Choose team
|
||||||
</Typography>
|
</Typography>
|
||||||
<Controller
|
<Controller
|
||||||
name="team"
|
name="orgId"
|
||||||
rules={{ required: 'This field is required' }}
|
rules={{ required: 'This field is required' }}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<AsyncSelect
|
||||||
{...field}
|
{...field}
|
||||||
// TODO: Implement placeholder for select
|
// TODO: Implement placeholder for select
|
||||||
label={!field.value ? 'Select an account / team' : ''}
|
label={!field.value ? 'Select an account / team' : ''}
|
||||||
>
|
>
|
||||||
{TEAMS.map((team, key) => (
|
{transferOrganizations.map((org, key) => (
|
||||||
<Option key={key} value={team}>
|
<Option key={key} value={org.id}>
|
||||||
^ {team}
|
^ {org.name}
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</AsyncSelect>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -177,15 +213,15 @@ const GeneralTabPanel = ({
|
|||||||
</form>
|
</form>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
dialogTitle="Transfer project"
|
dialogTitle="Transfer project"
|
||||||
handleOpen={handleTransferProjectDialog}
|
handleOpen={() => setOpenTransferDialog(!openTransferDialog)}
|
||||||
open={openTransferDialog}
|
open={openTransferDialog}
|
||||||
confirmButtonTitle="Yes, Confirm transfer"
|
confirmButtonTitle="Yes, Confirm transfer"
|
||||||
handleConfirm={handleTransferProjectDialog}
|
handleConfirm={handleTransferProject}
|
||||||
color="blue"
|
color="blue"
|
||||||
>
|
>
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
Upon confirmation, your project nextjs-boilerplate will be
|
Upon confirmation, your project {project.name} will be transferred
|
||||||
transferred from saugat to Airfoil.
|
from {project.organization.name} to {selectedUserOrgName}.
|
||||||
</Typography>
|
</Typography>
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
</div>
|
</div>
|
||||||
|
15
packages/frontend/src/components/shared/AsyncSelect.tsx
Normal file
15
packages/frontend/src/components/shared/AsyncSelect.tsx
Normal file
@ -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;
|
@ -1,87 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import { Outlet, useNavigate } from 'react-router-dom';
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
import { Environment } from 'gql-client';
|
|
||||||
|
|
||||||
import { IconButton, Typography } from '@material-tailwind/react';
|
import { IconButton, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import HorizontalLine from '../components/HorizontalLine';
|
import HorizontalLine from '../components/HorizontalLine';
|
||||||
import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
||||||
import { useGQLClient } from '../context/GQLClientContext';
|
|
||||||
import { ProjectDetails } from '../types/project';
|
|
||||||
|
|
||||||
const ProjectSearch = () => {
|
const ProjectSearch = () => {
|
||||||
const client = useGQLClient();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [projects, setProjects] = useState<ProjectDetails[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetch = async () => {
|
|
||||||
const { organizations } = await client.getOrganizations();
|
|
||||||
|
|
||||||
// Note: select first organization as organization switching not yet implemented
|
|
||||||
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));
|
|
||||||
const updatedDeployments = deployments.map((deployment: any) => {
|
|
||||||
return {
|
|
||||||
...deployment,
|
|
||||||
isProduction: deployment.environment === Environment.Production,
|
|
||||||
author: '',
|
|
||||||
commit: {
|
|
||||||
hash: '',
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
domain: deployment.domain
|
|
||||||
? {
|
|
||||||
...deployment.domain,
|
|
||||||
record: {
|
|
||||||
type: '',
|
|
||||||
name: '',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
// TODO: populate empty fields
|
|
||||||
icon: '',
|
|
||||||
organization: orgName,
|
|
||||||
deployments: updatedDeployments,
|
|
||||||
url: '',
|
|
||||||
domain: null,
|
|
||||||
createdBy: project.owner.name,
|
|
||||||
source: '',
|
|
||||||
repositoryId: 0,
|
|
||||||
repositories: [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
title: project.repository,
|
|
||||||
updatedAt: '',
|
|
||||||
user: '',
|
|
||||||
private: false,
|
|
||||||
branch: [''],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// TODO: populate from github API
|
|
||||||
latestCommit: {
|
|
||||||
message: '',
|
|
||||||
createdAt: '',
|
|
||||||
branch: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedProjects = await Promise.all(updatedProjectsPromises);
|
|
||||||
setProjects(updatedProjects);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch();
|
|
||||||
}, [client]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -107,7 +33,7 @@ const ProjectSearch = () => {
|
|||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
</div>
|
</div>
|
||||||
<div className="z-0">
|
<div className="z-0">
|
||||||
<Outlet context={{ projects }} />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -27,6 +27,10 @@ query ($projectId: String!) {
|
|||||||
repository
|
repository
|
||||||
webhooks
|
webhooks
|
||||||
icon
|
icon
|
||||||
|
organization {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
owner {
|
owner {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -96,34 +100,8 @@ query {
|
|||||||
organizations {
|
organizations {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
projects {
|
createdAt
|
||||||
id
|
updatedAt
|
||||||
owner {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
deployments {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
name
|
|
||||||
repository
|
|
||||||
prodBranch
|
|
||||||
description
|
|
||||||
template
|
|
||||||
framework
|
|
||||||
webhooks
|
|
||||||
members {
|
|
||||||
id
|
|
||||||
permissions
|
|
||||||
member{
|
|
||||||
id
|
|
||||||
name
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -249,6 +249,7 @@ export type UpdateProjectInput = {
|
|||||||
description?: string
|
description?: string
|
||||||
prodBranch?: string
|
prodBranch?: string
|
||||||
webhooks?: string[]
|
webhooks?: string[]
|
||||||
|
organizationId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateDomainInput = {
|
export type UpdateDomainInput = {
|
||||||
|
Loading…
Reference in New Issue
Block a user