Add GQL mutation for updating project data in general settings tab (#41)
* Use update project gql client method in UI * Handle save project button based on form change * Fix import order --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
parent
61120ac44a
commit
5310d7c7d0
@ -1,5 +1,6 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useCombobox } from 'downshift';
|
import { useCombobox } from 'downshift';
|
||||||
|
import { Project } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
@ -9,8 +10,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { Project } from 'gql-client';
|
|
||||||
|
|
||||||
import SearchBar from '../SearchBar';
|
import SearchBar from '../SearchBar';
|
||||||
import { useGQLClient } from '../../context/GQLClientContext';
|
import { useGQLClient } from '../../context/GQLClientContext';
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { Project } from 'gql-client';
|
||||||
|
|
||||||
import { Typography, Button, Chip } from '@material-tailwind/react';
|
import { Typography, Button, Chip } from '@material-tailwind/react';
|
||||||
|
|
||||||
@ -8,17 +9,19 @@ import { ProjectDetails } from '../../../types/project';
|
|||||||
import { relativeTimeMs } from '../../../utils/time';
|
import { relativeTimeMs } from '../../../utils/time';
|
||||||
|
|
||||||
interface OverviewProps {
|
interface OverviewProps {
|
||||||
project: ProjectDetails;
|
project: Project;
|
||||||
|
organizationProject: ProjectDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OverviewTabPanel = ({ project }: OverviewProps) => {
|
const OverviewTabPanel = ({ project, organizationProject }: OverviewProps) => {
|
||||||
|
// TODO: Fetch current deployment
|
||||||
const currentDeploymentTitle = useMemo(() => {
|
const currentDeploymentTitle = useMemo(() => {
|
||||||
const deployment = project.deployments.find((deployment) => {
|
const deployment = organizationProject?.deployments.find((deployment) => {
|
||||||
return deployment.isCurrent === true;
|
return deployment.isCurrent === true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return deployment?.title;
|
return deployment?.title;
|
||||||
}, []);
|
}, [organizationProject]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-5">
|
<div className="grid grid-cols-5">
|
||||||
@ -28,14 +31,14 @@ const OverviewTabPanel = ({ project }: OverviewProps) => {
|
|||||||
<div className="grow">
|
<div className="grow">
|
||||||
<Typography>{project.name}</Typography>
|
<Typography>{project.name}</Typography>
|
||||||
<Typography variant="small" color="gray">
|
<Typography variant="small" color="gray">
|
||||||
{project.url}
|
{organizationProject.url}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between p-2 text-sm items-center">
|
<div className="flex justify-between p-2 text-sm items-center">
|
||||||
<div>
|
<div>
|
||||||
^ Domain
|
^ Domain
|
||||||
{!project.domain && (
|
{!organizationProject.domain && (
|
||||||
<Chip
|
<Chip
|
||||||
className="normal-case ml-6 bg-[#FED7AA] text-[#EA580C] inline font-normal"
|
className="normal-case ml-6 bg-[#FED7AA] text-[#EA580C] inline font-normal"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -44,8 +47,8 @@ const OverviewTabPanel = ({ project }: OverviewProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{project.domain ? (
|
{organizationProject.domain ? (
|
||||||
<p>{project.domain}</p>
|
<p>{organizationProject.domain}</p>
|
||||||
) : (
|
) : (
|
||||||
<Button className="normal-case rounded-full" color="blue" size="sm">
|
<Button className="normal-case rounded-full" color="blue" size="sm">
|
||||||
Setup
|
Setup
|
||||||
@ -54,7 +57,7 @@ const OverviewTabPanel = ({ project }: OverviewProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between p-2 text-sm">
|
<div className="flex justify-between p-2 text-sm">
|
||||||
<p>^ Source</p>
|
<p>^ Source</p>
|
||||||
<p>{project.source}</p>
|
<p>{organizationProject.source}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between p-2 text-sm">
|
<div className="flex justify-between p-2 text-sm">
|
||||||
<p>^ Deployment</p>
|
<p>^ Deployment</p>
|
||||||
@ -63,7 +66,7 @@ const OverviewTabPanel = ({ project }: OverviewProps) => {
|
|||||||
<div className="flex justify-between p-2 text-sm">
|
<div className="flex justify-between p-2 text-sm">
|
||||||
<p>^ Created</p>
|
<p>^ Created</p>
|
||||||
<p>
|
<p>
|
||||||
{relativeTimeMs(project.createdAt)} by ^ {project.createdBy}
|
{relativeTimeMs(project.createdAt)} by ^ {project.owner.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
|
import { Project } from 'gql-client';
|
||||||
|
|
||||||
import OverviewTabPanel from './OverviewTabPanel';
|
import OverviewTabPanel from './OverviewTabPanel';
|
||||||
import DeploymentsTabPanel from './DeploymentsTabPanel';
|
import DeploymentsTabPanel from './DeploymentsTabPanel';
|
||||||
@ -7,7 +8,9 @@ import { ProjectDetails } from '../../../types/project';
|
|||||||
import SettingsTabPanel from './SettingsTabPanel';
|
import SettingsTabPanel from './SettingsTabPanel';
|
||||||
|
|
||||||
interface ProjectTabsProps {
|
interface ProjectTabsProps {
|
||||||
project: ProjectDetails;
|
project: Project;
|
||||||
|
organizationProject: ProjectDetails;
|
||||||
|
onUpdate: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Database = () => (
|
const Database = () => (
|
||||||
@ -28,7 +31,11 @@ const Integrations = () => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ProjectTabs = ({ project }: ProjectTabsProps) => {
|
const ProjectTabs = ({
|
||||||
|
project,
|
||||||
|
onUpdate,
|
||||||
|
organizationProject,
|
||||||
|
}: ProjectTabsProps) => {
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
selectedTabClassName={
|
selectedTabClassName={
|
||||||
@ -43,7 +50,10 @@ const ProjectTabs = ({ project }: ProjectTabsProps) => {
|
|||||||
<Tab className={'p-2 cursor-pointer'}>Settings</Tab>
|
<Tab className={'p-2 cursor-pointer'}>Settings</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<OverviewTabPanel project={project} />
|
<OverviewTabPanel
|
||||||
|
project={project}
|
||||||
|
organizationProject={organizationProject}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<DeploymentsTabPanel projectId={project.id} />
|
<DeploymentsTabPanel projectId={project.id} />
|
||||||
@ -55,7 +65,7 @@ const ProjectTabs = ({ project }: ProjectTabsProps) => {
|
|||||||
<Integrations />
|
<Integrations />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<SettingsTabPanel />
|
<SettingsTabPanel project={project} onUpdate={onUpdate} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React, { createElement } from 'react';
|
import React, { createElement } from 'react';
|
||||||
|
import { Project } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsHeader,
|
TabsHeader,
|
||||||
@ -46,7 +48,13 @@ const tabsData = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SettingsTabPanel = () => {
|
const SettingsTabPanel = ({
|
||||||
|
project,
|
||||||
|
onUpdate,
|
||||||
|
}: {
|
||||||
|
project: Project;
|
||||||
|
onUpdate: () => Promise<void>;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs
|
<Tabs
|
||||||
@ -72,7 +80,10 @@ const SettingsTabPanel = () => {
|
|||||||
<TabsBody className="col-span-2">
|
<TabsBody className="col-span-2">
|
||||||
{tabsData.map(({ value, component }) => (
|
{tabsData.map(({ value, component }) => (
|
||||||
<TabPanel key={value} value={value} className="p-2">
|
<TabPanel key={value} value={value} className="p-2">
|
||||||
{createElement(component)}
|
{createElement(component, {
|
||||||
|
project: project,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
})}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</TabsBody>
|
</TabsBody>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { IconButton, Input, Typography } from '@material-tailwind/react';
|
|
||||||
|
|
||||||
import { EnvironmentVariable } from 'gql-client';
|
import { EnvironmentVariable } from 'gql-client';
|
||||||
|
|
||||||
|
import { IconButton, Input, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
|
|
||||||
const ShowPasswordIcon = ({
|
const ShowPasswordIcon = ({
|
||||||
|
@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
@ -12,8 +13,6 @@ import {
|
|||||||
Chip,
|
Chip,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { Environment, EnvironmentVariable } from 'gql-client';
|
|
||||||
|
|
||||||
import AddEnvironmentVariableRow from './AddEnvironmentVariableRow';
|
import AddEnvironmentVariableRow from './AddEnvironmentVariableRow';
|
||||||
import DisplayEnvironmentVariables from './DisplayEnvironmentVariables';
|
import DisplayEnvironmentVariables from './DisplayEnvironmentVariables';
|
||||||
import HorizontalLine from '../../../HorizontalLine';
|
import HorizontalLine from '../../../HorizontalLine';
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useOutletContext, useParams } 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 {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -13,7 +14,7 @@ import {
|
|||||||
|
|
||||||
import DeleteProjectDialog from './DeleteProjectDialog';
|
import DeleteProjectDialog from './DeleteProjectDialog';
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
import { ProjectSearchOutletContext } from '../../../../types/project';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
|
||||||
const TEAMS = ['Airfoil'];
|
const TEAMS = ['Airfoil'];
|
||||||
const DEFAULT_SELECT_TEAM = undefined;
|
const DEFAULT_SELECT_TEAM = undefined;
|
||||||
@ -32,13 +33,14 @@ const CopyIcon = ({ value }: { value: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GeneralTabPanel = () => {
|
const GeneralTabPanel = ({
|
||||||
const { id } = useParams();
|
project,
|
||||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
onUpdate,
|
||||||
|
}: {
|
||||||
const currentProject = useMemo(() => {
|
project: Project;
|
||||||
return projects.find((project: any) => project.id === id);
|
onUpdate: () => Promise<void>;
|
||||||
}, [id]);
|
}) => {
|
||||||
|
const client = useGQLClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit: handleTransfer,
|
handleSubmit: handleTransfer,
|
||||||
@ -58,134 +60,155 @@ const GeneralTabPanel = () => {
|
|||||||
const handleDeleteProjectDialog = () =>
|
const handleDeleteProjectDialog = () =>
|
||||||
setOpenDeleteDialog(!openDeleteDialog);
|
setOpenDeleteDialog(!openDeleteDialog);
|
||||||
|
|
||||||
const { handleSubmit, register } = useForm({
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: updateProjectFormState,
|
||||||
|
} = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
appName: currentProject?.name,
|
appName: project.name,
|
||||||
description: currentProject?.description,
|
description: project.description,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({ appName: project.name, description: project.description });
|
||||||
|
}, [project]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{currentProject && (
|
<form
|
||||||
<>
|
onSubmit={handleSubmit(async ({ appName, description }) => {
|
||||||
<form onSubmit={handleSubmit(() => {})}>
|
const { updateProject } = await client.updateProject(project.id, {
|
||||||
<Typography variant="h6">Project info</Typography>
|
name: appName,
|
||||||
<Typography variant="small" className="font-medium text-gray-800">
|
description,
|
||||||
App name
|
});
|
||||||
</Typography>
|
if (updateProject) {
|
||||||
<Input
|
await onUpdate();
|
||||||
variant="outlined"
|
}
|
||||||
// TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427
|
})}
|
||||||
crossOrigin={undefined}
|
>
|
||||||
size="md"
|
<Typography variant="h6">Project info</Typography>
|
||||||
{...register('appName')}
|
<Typography variant="small" className="font-medium text-gray-800">
|
||||||
/>
|
App name
|
||||||
<Typography variant="small" className="font-medium text-gray-800">
|
</Typography>
|
||||||
Description (Optional)
|
<Input
|
||||||
</Typography>
|
variant="outlined"
|
||||||
<Input
|
// TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427
|
||||||
variant="outlined"
|
crossOrigin={undefined}
|
||||||
crossOrigin={undefined}
|
size="md"
|
||||||
size="md"
|
{...register('appName')}
|
||||||
{...register('description')}
|
/>
|
||||||
/>
|
<Typography variant="small" className="font-medium text-gray-800">
|
||||||
<Typography variant="small" className="font-medium text-gray-800">
|
Description (Optional)
|
||||||
Project ID
|
</Typography>
|
||||||
</Typography>
|
<Input
|
||||||
<Input
|
variant="outlined"
|
||||||
crossOrigin={undefined}
|
crossOrigin={undefined}
|
||||||
variant="outlined"
|
size="md"
|
||||||
value={currentProject.id}
|
{...register('description')}
|
||||||
size="md"
|
/>
|
||||||
disabled
|
<Typography variant="small" className="font-medium text-gray-800">
|
||||||
icon={<CopyIcon value={currentProject.id} />}
|
Project ID
|
||||||
/>
|
</Typography>
|
||||||
<Button type="submit" variant="gradient" size="sm" className="mt-1">
|
<Input
|
||||||
Save
|
crossOrigin={undefined}
|
||||||
</Button>
|
variant="outlined"
|
||||||
</form>
|
value={project.id}
|
||||||
<div className="mb-1">
|
size="md"
|
||||||
<Typography variant="h6">Transfer project</Typography>
|
disabled
|
||||||
<Typography variant="small">
|
icon={<CopyIcon value={project.id} />}
|
||||||
Transfer this app to your personal account or a team you are a
|
/>
|
||||||
member of.
|
<Button
|
||||||
<Link to="" className="text-blue-500">
|
type="submit"
|
||||||
Learn more
|
variant="gradient"
|
||||||
</Link>
|
size="sm"
|
||||||
</Typography>
|
className="mt-1"
|
||||||
<form
|
disabled={!updateProjectFormState.isDirty}
|
||||||
onSubmit={handleTransfer(() => {
|
>
|
||||||
handleTransferProjectDialog();
|
Save
|
||||||
})}
|
</Button>
|
||||||
>
|
</form>
|
||||||
<Typography variant="small" className="font-medium text-gray-800">
|
<div className="mb-1">
|
||||||
Choose team
|
<Typography variant="h6">Transfer project</Typography>
|
||||||
</Typography>
|
<Typography variant="small">
|
||||||
<Controller
|
Transfer this app to your personal account or a team you are a member
|
||||||
name="team"
|
of.
|
||||||
rules={{ required: 'This field is required' }}
|
<Link to="" className="text-blue-500">
|
||||||
control={control}
|
Learn more
|
||||||
render={({ field }) => (
|
</Link>
|
||||||
<Select
|
</Typography>
|
||||||
{...field}
|
<form
|
||||||
// TODO: Implement placeholder for select
|
onSubmit={handleTransfer(() => {
|
||||||
label={!field.value ? 'Select an account / team' : ''}
|
handleTransferProjectDialog();
|
||||||
>
|
})}
|
||||||
{TEAMS.map((team, key) => (
|
>
|
||||||
<Option key={key} value={team}>
|
<Typography variant="small" className="font-medium text-gray-800">
|
||||||
^ {team}
|
Choose team
|
||||||
</Option>
|
</Typography>
|
||||||
))}
|
<Controller
|
||||||
</Select>
|
name="team"
|
||||||
)}
|
rules={{ required: 'This field is required' }}
|
||||||
/>
|
control={control}
|
||||||
<Button
|
render={({ field }) => (
|
||||||
variant="gradient"
|
<Select
|
||||||
size="sm"
|
{...field}
|
||||||
className="mt-1"
|
// TODO: Implement placeholder for select
|
||||||
disabled={!formState.isValid}
|
label={!field.value ? 'Select an account / team' : ''}
|
||||||
type="submit"
|
|
||||||
>
|
>
|
||||||
Transfer
|
{TEAMS.map((team, key) => (
|
||||||
</Button>
|
<Option key={key} value={team}>
|
||||||
</form>
|
^ {team}
|
||||||
<ConfirmDialog
|
</Option>
|
||||||
dialogTitle="Transfer project"
|
))}
|
||||||
handleOpen={handleTransferProjectDialog}
|
</Select>
|
||||||
open={openTransferDialog}
|
)}
|
||||||
confirmButtonTitle="Yes, Confirm transfer"
|
/>
|
||||||
handleConfirm={handleTransferProjectDialog}
|
<Button
|
||||||
color="blue"
|
variant="gradient"
|
||||||
>
|
size="sm"
|
||||||
<Typography variant="small">
|
className="mt-1"
|
||||||
Upon confirmation, your project nextjs-boilerplate will be
|
disabled={!formState.isValid}
|
||||||
transferred from saugat to Airfoil.
|
type="submit"
|
||||||
</Typography>
|
>
|
||||||
</ConfirmDialog>
|
Transfer
|
||||||
</div>
|
</Button>
|
||||||
<div className="mb-1">
|
</form>
|
||||||
<Typography variant="h6">Delete project</Typography>
|
<ConfirmDialog
|
||||||
<Typography variant="small">
|
dialogTitle="Transfer project"
|
||||||
The project will be permanently deleted, including its deployments
|
handleOpen={handleTransferProjectDialog}
|
||||||
and domains. This action is irreversible and can not be undone.
|
open={openTransferDialog}
|
||||||
</Typography>
|
confirmButtonTitle="Yes, Confirm transfer"
|
||||||
<Button
|
handleConfirm={handleTransferProjectDialog}
|
||||||
variant="gradient"
|
color="blue"
|
||||||
size="sm"
|
>
|
||||||
color="red"
|
<Typography variant="small">
|
||||||
onClick={handleDeleteProjectDialog}
|
Upon confirmation, your project nextjs-boilerplate will be
|
||||||
>
|
transferred from saugat to Airfoil.
|
||||||
^ Delete project
|
</Typography>
|
||||||
</Button>
|
</ConfirmDialog>
|
||||||
<DeleteProjectDialog
|
</div>
|
||||||
handleOpen={handleDeleteProjectDialog}
|
<div className="mb-1">
|
||||||
open={openDeleteDialog}
|
<Typography variant="h6">Delete project</Typography>
|
||||||
project={{ name: 'Iglootools' }}
|
<Typography variant="small">
|
||||||
/>
|
The project will be permanently deleted, including its deployments and
|
||||||
</div>
|
domains. This action is irreversible and can not be undone.
|
||||||
</>
|
</Typography>
|
||||||
)}
|
<Button
|
||||||
|
variant="gradient"
|
||||||
|
size="sm"
|
||||||
|
color="red"
|
||||||
|
onClick={handleDeleteProjectDialog}
|
||||||
|
>
|
||||||
|
^ Delete project
|
||||||
|
</Button>
|
||||||
|
<DeleteProjectDialog
|
||||||
|
handleOpen={handleDeleteProjectDialog}
|
||||||
|
open={openDeleteDialog}
|
||||||
|
project={{ name: 'Iglootools' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Outlet, useNavigate } from 'react-router-dom';
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { Environment } from 'gql-client';
|
import { Environment } from 'gql-client';
|
||||||
|
|
||||||
import HorizontalLine from '../components/HorizontalLine';
|
|
||||||
import { IconButton, Typography } from '@material-tailwind/react';
|
import { IconButton, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
|
import HorizontalLine from '../components/HorizontalLine';
|
||||||
import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
||||||
import { useGQLClient } from '../context/GQLClientContext';
|
import { useGQLClient } from '../context/GQLClientContext';
|
||||||
import { ProjectDetails } from '../types/project';
|
import { ProjectDetails } from '../types/project';
|
||||||
|
@ -1,27 +1,49 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useNavigate, useOutletContext, useParams } from 'react-router-dom';
|
import { useNavigate, useOutletContext, useParams } from 'react-router-dom';
|
||||||
|
import { Project as ProjectType } from 'gql-client';
|
||||||
|
|
||||||
import { Button, Typography } from '@material-tailwind/react';
|
import { Button, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import HorizontalLine from '../../components/HorizontalLine';
|
import HorizontalLine from '../../components/HorizontalLine';
|
||||||
import ProjectTabs from '../../components/projects/project/ProjectTabs';
|
import ProjectTabs from '../../components/projects/project/ProjectTabs';
|
||||||
|
import { useGQLClient } from '../../context/GQLClientContext';
|
||||||
import { ProjectSearchOutletContext } from '../../types/project';
|
import { ProjectSearchOutletContext } from '../../types/project';
|
||||||
|
|
||||||
const Project = () => {
|
const Project = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const client = useGQLClient();
|
||||||
|
|
||||||
const { projects } = useOutletContext<ProjectSearchOutletContext>();
|
const [project, setProject] = useState<ProjectType | null>(null);
|
||||||
|
|
||||||
const project = useMemo(() => {
|
const fetchProject = useCallback(async (id: string | undefined) => {
|
||||||
return projects.find((project) => {
|
if (id) {
|
||||||
|
const { project } = await client.getProject(id);
|
||||||
|
setProject(project);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProject(id);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const onUpdate = async () => {
|
||||||
|
await fetchProject(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Remove organization projects
|
||||||
|
const { projects: organizationProjects } =
|
||||||
|
useOutletContext<ProjectSearchOutletContext>();
|
||||||
|
|
||||||
|
const organizationProject = useMemo(() => {
|
||||||
|
return organizationProjects.find((project) => {
|
||||||
return project.id === id;
|
return project.id === id;
|
||||||
});
|
});
|
||||||
}, [id, projects]);
|
}, [id, organizationProjects]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
{project ? (
|
{project && organizationProject ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex p-4 gap-4 items-center">
|
<div className="flex p-4 gap-4 items-center">
|
||||||
<Button
|
<Button
|
||||||
@ -32,7 +54,7 @@ const Project = () => {
|
|||||||
{'<'}
|
{'<'}
|
||||||
</Button>
|
</Button>
|
||||||
<Typography variant="h3" className="grow">
|
<Typography variant="h3" className="grow">
|
||||||
{project?.title}
|
{project?.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button className="rounded-full" variant="outlined">
|
<Button className="rounded-full" variant="outlined">
|
||||||
Open Repo
|
Open Repo
|
||||||
@ -43,7 +65,11 @@ const Project = () => {
|
|||||||
</div>
|
</div>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<ProjectTabs project={project} />
|
<ProjectTabs
|
||||||
|
project={project}
|
||||||
|
organizationProject={organizationProject}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||||
|
|
||||||
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables } from './queries';
|
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject } from './queries';
|
||||||
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse } from './types';
|
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput } from './types';
|
||||||
import { removeMember, addEnvironmentVariables, updateDeploymentToProd } from './mutations';
|
import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation } from './mutations';
|
||||||
|
|
||||||
export interface GraphQLConfig {
|
export interface GraphQLConfig {
|
||||||
gqlEndpoint: string;
|
gqlEndpoint: string;
|
||||||
@ -39,6 +39,17 @@ export class GQLClient {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getProject (projectId: string) : Promise<GetProjectResponse> {
|
||||||
|
const { data } = await this.client.query({
|
||||||
|
query: getProject,
|
||||||
|
variables: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async getOrganizations () : Promise<GetOrganizationsResponse> {
|
async getOrganizations () : Promise<GetOrganizationsResponse> {
|
||||||
const { data } = await this.client.query({
|
const { data } = await this.client.query({
|
||||||
query: getOrganizations
|
query: getOrganizations
|
||||||
@ -124,4 +135,16 @@ export class GQLClient {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateProject (projectId: string, updateProject: UpdateProjectInput): Promise<UpdateProjectResponse> {
|
||||||
|
const { data } = await this.client.mutate({
|
||||||
|
mutation: updateProjectMutation,
|
||||||
|
variables: {
|
||||||
|
projectId,
|
||||||
|
updateProject
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,3 +17,9 @@ mutation ($deploymentId: String!) {
|
|||||||
updateDeploymentToProd(deploymentId: $deploymentId)
|
updateDeploymentToProd(deploymentId: $deploymentId)
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const updateProjectMutation = gql`
|
||||||
|
mutation ($projectId: String!, $updateProject: UpdateProjectInput) {
|
||||||
|
updateProject(projectId: $projectId, updateProject: $updateProject)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -12,6 +12,28 @@ query {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const getProject = gql`
|
||||||
|
query ($projectId: String!) {
|
||||||
|
project(projectId: $projectId) {
|
||||||
|
createdAt
|
||||||
|
description
|
||||||
|
id
|
||||||
|
name
|
||||||
|
template
|
||||||
|
updatedAt
|
||||||
|
prodBranch
|
||||||
|
framework
|
||||||
|
repository
|
||||||
|
webhooks
|
||||||
|
owner {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const getOrganizations = gql`
|
export const getOrganizations = gql`
|
||||||
query {
|
query {
|
||||||
organizations {
|
organizations {
|
||||||
|
@ -152,6 +152,10 @@ export type GetUserResponse = {
|
|||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GetProjectResponse = {
|
||||||
|
project: Project | null
|
||||||
|
}
|
||||||
|
|
||||||
export type SearchProjectsResponse = {
|
export type SearchProjectsResponse = {
|
||||||
searchProjects: Project[]
|
searchProjects: Project[]
|
||||||
}
|
}
|
||||||
@ -169,3 +173,12 @@ export type AddEnvironmentVariableInput = {
|
|||||||
export type UpdateDeploymentToProdResponse = {
|
export type UpdateDeploymentToProdResponse = {
|
||||||
updateDeploymentToProd: boolean;
|
updateDeploymentToProd: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdateProjectResponse = {
|
||||||
|
updateProject: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateProjectInput = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user