Implement routes for project settings tab (#64)

* Add routes to settings tab panel

* Refactor code to move settings tab components to pages

* Rename registry fields in project and deployment entity

* Use kebab case for routes

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-02-12 15:18:00 +05:30 committed by GitHub
parent 559e0f8934
commit 76dfd3bb76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 215 additions and 198 deletions

View File

@ -63,10 +63,10 @@ export class Deployment {
url!: string; url!: string;
@Column('varchar') @Column('varchar')
recordId!: string; registryRecordId!: string;
@Column('simple-json') @Column('simple-json')
recordData!: ApplicationRecord; registryRecordData!: ApplicationRecord;
@Column({ @Column({
enum: Environment enum: Environment

View File

@ -56,10 +56,10 @@ export class Project {
prodBranch!: string; prodBranch!: string;
@Column('varchar', { nullable: true }) @Column('varchar', { nullable: true })
recordId!: string | null; registryRecordId!: string | null;
@Column('simple-json', { nullable: true }) @Column('simple-json', { nullable: true })
recordData!: ApplicationDeploymentRequest | null; registryRecordData!: ApplicationDeploymentRequest | null;
@Column('text', { default: '' }) @Column('text', { default: '' })
description!: string; description!: string;

View File

@ -13,6 +13,7 @@ const APP_RECORD_TYPE = 'ApplicationRecord';
const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest'; const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest';
const AUTHORITY_NAME = 'snowball'; const AUTHORITY_NAME = 'snowball';
// TODO: Move registry code to laconic-sdk/watcher-ts
export class Registry { export class Registry {
private registry: LaconicRegistry; private registry: LaconicRegistry;
private registryConfig: RegistryConfig; private registryConfig: RegistryConfig;
@ -22,7 +23,7 @@ export class Registry {
this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
} }
async createApplicationRecord (data: { recordName: string, appType: string }): Promise<{recordId: string, recordData: ApplicationRecord}> { async createApplicationRecord (data: { recordName: string, appType: string }): Promise<{registryRecordId: string, registryRecordData: ApplicationRecord}> {
// TODO: Get record name from repo package.json name // TODO: Get record name from repo package.json name
const recordName = data.recordName; const recordName = data.recordName;
@ -77,10 +78,10 @@ export class Registry {
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee);
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee);
return { recordId: result.data.id, recordData: applicationRecord }; return { registryRecordId: result.data.id, registryRecordData: applicationRecord };
} }
async createApplicationDeploymentRequest (data: { appName: string }): Promise<{recordId: string, recordData: ApplicationDeploymentRequest}> { async createApplicationDeploymentRequest (data: { appName: string }): Promise<{registryRecordId: string, registryRecordData: ApplicationDeploymentRequest}> {
const crn = this.getCrn(data.appName); const crn = this.getCrn(data.appName);
const records = await this.registry.resolveNames([crn]); const records = await this.registry.resolveNames([crn]);
const applicationRecord = records[0]; const applicationRecord = records[0];
@ -124,7 +125,7 @@ export class Registry {
log(`Application deployment request record published: ${result.data.id}`); log(`Application deployment request record published: ${result.data.id}`);
log('Application deployment request data:', applicationDeploymentRequest); log('Application deployment request data:', applicationDeploymentRequest);
return { recordId: result.data.id, recordData: applicationDeploymentRequest }; return { registryRecordId: result.data.id, registryRecordData: applicationDeploymentRequest };
} }
getCrn (appName: string): string { getCrn (appName: string): string {

View File

@ -214,7 +214,7 @@ export class Service {
} }
async createDeployment (userId: string, data: DeepPartial<Deployment>): Promise<Deployment> { async createDeployment (userId: string, data: DeepPartial<Deployment>): Promise<Deployment> {
const { recordId, recordData } = await this.registry.createApplicationRecord({ const { registryRecordId, registryRecordData } = await this.registry.createApplicationRecord({
recordName: data.project?.name ?? '', recordName: data.project?.name ?? '',
appType: data.project?.template ?? '' appType: data.project?.template ?? ''
}); });
@ -226,15 +226,15 @@ export class Service {
environment: data.environment, environment: data.environment,
isCurrent: data.isCurrent, isCurrent: data.isCurrent,
status: DeploymentStatus.Building, status: DeploymentStatus.Building,
recordId, registryRecordId,
recordData, registryRecordData,
domain: data.domain, domain: data.domain,
createdBy: Object.assign(new User(), { createdBy: Object.assign(new User(), {
id: userId id: userId
}) })
}); });
log(`Application record ${recordId} published for deployment ${newDeployement.id}`); log(`Application record ${registryRecordId} published for deployment ${newDeployement.id}`);
return newDeployement; return newDeployement;
} }
@ -262,10 +262,10 @@ export class Service {
domain: null domain: null
}); });
const { recordId, recordData } = await this.registry.createApplicationDeploymentRequest({ appName: project.name }); const { registryRecordId, registryRecordData } = await this.registry.createApplicationDeploymentRequest({ appName: project.name });
await this.db.updateProjectById(project.id, { await this.db.updateProjectById(project.id, {
recordId, registryRecordId,
recordData registryRecordData
}); });
return project; return project;

View File

@ -7,8 +7,8 @@
"status": "Building", "status": "Building",
"environment": "Production", "environment": "Production",
"isCurrent": true, "isCurrent": true,
"recordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "main", "branch": "main",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-ffhae3zq.snowball.xyz" "url": "testProject-ffhae3zq.snowball.xyz"
@ -21,8 +21,8 @@
"status": "Ready", "status": "Ready",
"environment": "Preview", "environment": "Preview",
"isCurrent": false, "isCurrent": false,
"recordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-vehagei8.snowball.xyz" "url": "testProject-vehagei8.snowball.xyz"
@ -35,8 +35,8 @@
"status": "Error", "status": "Error",
"environment": "Development", "environment": "Development",
"isCurrent": false, "isCurrent": false,
"recordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-qmgekyte.snowball.xyz" "url": "testProject-qmgekyte.snowball.xyz"
@ -49,8 +49,8 @@
"status": "Ready", "status": "Ready",
"environment": "Production", "environment": "Production",
"isCurrent": false, "isCurrent": false,
"recordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", "registryRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "prod", "branch": "prod",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-f8wsyim6.snowball.xyz" "url": "testProject-f8wsyim6.snowball.xyz"
@ -63,8 +63,8 @@
"status": "Building", "status": "Building",
"environment": "Production", "environment": "Production",
"isCurrent": true, "isCurrent": true,
"recordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "main", "branch": "main",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-2-eO8cckxk.snowball.xyz" "url": "testProject-2-eO8cckxk.snowball.xyz"
@ -77,8 +77,8 @@
"status": "Ready", "status": "Ready",
"environment": "Preview", "environment": "Preview",
"isCurrent": false, "isCurrent": false,
"recordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-2-yaq0t5yw.snowball.xyz" "url": "testProject-2-yaq0t5yw.snowball.xyz"
@ -91,8 +91,8 @@
"status": "Error", "status": "Error",
"environment": "Development", "environment": "Development",
"isCurrent": false, "isCurrent": false,
"recordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", "registryRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "testProject-2-hwwr6sbx.snowball.xyz" "url": "testProject-2-hwwr6sbx.snowball.xyz"
@ -105,8 +105,8 @@
"status": "Building", "status": "Building",
"environment": "Production", "environment": "Production",
"isCurrent": true, "isCurrent": true,
"recordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "main", "branch": "main",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "iglootools-ndxje48a.snowball.xyz" "url": "iglootools-ndxje48a.snowball.xyz"
@ -119,8 +119,8 @@
"status": "Ready", "status": "Ready",
"environment": "Preview", "environment": "Preview",
"isCurrent": false, "isCurrent": false,
"recordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "iglootools-gtgpgvei.snowball.xyz" "url": "iglootools-gtgpgvei.snowball.xyz"
@ -133,8 +133,8 @@
"status": "Error", "status": "Error",
"environment": "Development", "environment": "Development",
"isCurrent": false, "isCurrent": false,
"recordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", "registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
"recordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "testXyz",
"url": "iglootools-b4bpthjr.snowball.xyz" "url": "iglootools-b4bpthjr.snowball.xyz"

View File

@ -10,8 +10,8 @@
"framework": "test", "framework": "test",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
"recordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"subDomain": "testProject.snowball.xyz" "subDomain": "testProject.snowball.xyz"
}, },
{ {
@ -25,8 +25,8 @@
"framework": "test-2", "framework": "test-2",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
"recordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"subDomain": "testProject-2.snowball.xyz" "subDomain": "testProject-2.snowball.xyz"
}, },
{ {
@ -40,8 +40,8 @@
"framework": "test-3", "framework": "test-3",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
"recordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"subDomain": "iglootools.snowball.xyz" "subDomain": "iglootools.snowball.xyz"
}, },
{ {
@ -55,8 +55,8 @@
"framework": "test-4", "framework": "test-4",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
"recordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"subDomain": "iglootools-2.snowball.xyz" "subDomain": "iglootools-2.snowball.xyz"
}, },
{ {
@ -70,8 +70,8 @@
"framework": "test-5", "framework": "test-5",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
"recordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"recordData": {}, "registryRecordData": {},
"subDomain": "snowball-2.snowball.xyz" "subDomain": "snowball-2.snowball.xyz"
} }
] ]

View File

@ -3,7 +3,7 @@ import { UseFormRegister } from 'react-hook-form';
import { Typography, Input, IconButton } from '@material-tailwind/react'; import { Typography, Input, IconButton } from '@material-tailwind/react';
import { EnvironmentVariablesFormValues } from './EnvironmentVariablesTabPanel'; import { EnvironmentVariablesFormValues } from '../../../../types/project';
interface AddEnvironmentVariableRowProps { interface AddEnvironmentVariableRowProps {
onDelete: () => void; onDelete: () => void;

View File

@ -0,0 +1,85 @@
import React, { useMemo } from 'react';
import { Link, Outlet, useLocation, useOutletContext } from 'react-router-dom';
import { Tabs, TabsHeader, TabsBody, Tab } from '@material-tailwind/react';
import { OutletContextType } from '../../../../types/project';
const tabsData = [
{
label: 'General',
icon: '^',
value: 'general',
},
{
label: 'Domains',
icon: '^',
value: 'domains',
},
{
label: 'Git',
icon: '^',
value: 'git',
},
{
label: 'Environment variables',
icon: '^',
value: 'environment-variables',
},
{
label: 'Members',
icon: '^',
value: 'members',
},
];
const SettingsTabPanel = () => {
const { project, onUpdate } = useOutletContext<OutletContextType>();
const location = useLocation();
const currentTab = useMemo(() => {
if (project) {
const currTabArr = location.pathname.split('settings');
return currTabArr[currTabArr.length - 1];
} else {
return;
}
}, [location, project]);
return (
<>
<Tabs
value={currentTab}
orientation="vertical"
className="grid grid-cols-4"
>
<TabsHeader
className="bg-transparent col-span-1"
indicatorProps={{
className: 'bg-gray-900/10 shadow-none !text-gray-900',
}}
>
{tabsData.map(({ label, value, icon }) => (
<Link key={value} to={value === 'general' ? '' : value}>
<Tab
value={value === 'general' ? '' : `/${value}`}
className="flex justify-start"
>
<div className="flex gap-2">
<div>{icon}</div>
<div>{label}</div>
</div>
</Tab>
</Link>
))}
</TabsHeader>
<TabsBody className="col-span-2">
<Outlet context={{ project, onUpdate }} />
</TabsBody>
</Tabs>
</>
);
};
export default SettingsTabPanel;

View File

@ -1,92 +0,0 @@
import React, { createElement } from 'react';
import { useOutletContext } from 'react-router-dom';
import {
Tabs,
TabsHeader,
TabsBody,
Tab,
TabPanel,
} from '@material-tailwind/react';
import Domains from '../../../../components/projects/project/settings/Domains';
import GeneralTabPanel from '../../../../components/projects/project/settings/GeneralTabPanel';
import { EnvironmentVariablesTabPanel } from '../../../../components/projects/project/settings/EnvironmentVariablesTabPanel';
import GitTabPanel from '../../../../components/projects/project/settings/GitTabPanel';
import MembersTabPanel from '../../../../components/projects/project/settings/MembersTabPanel';
import { OutletContextType } from '../../../../types/project';
const tabsData = [
{
label: 'General',
icon: '^',
value: 'general',
component: GeneralTabPanel,
},
{
label: 'Domains',
icon: '^',
value: 'domains',
component: Domains,
},
{
label: 'Git',
icon: '^',
value: 'git',
component: GitTabPanel,
},
{
label: 'Environment variables',
icon: '^',
value: 'environmentVariables',
component: EnvironmentVariablesTabPanel,
},
{
label: 'Members',
icon: '^',
value: 'members',
component: MembersTabPanel,
},
];
const SettingsTabPanel = () => {
const { project, onUpdate } = useOutletContext<OutletContextType>();
return (
<>
<Tabs
value={'general'}
orientation="vertical"
className="grid grid-cols-4"
>
<TabsHeader
className="bg-transparent col-span-1"
indicatorProps={{
className: 'bg-gray-900/10 shadow-none !text-gray-900',
}}
>
{tabsData.map(({ label, value, icon }) => (
<Tab key={value} value={value} className="flex justify-start">
<div className="flex gap-2">
<div>{icon}</div>
<div>{label}</div>
</div>
</Tab>
))}
</TabsHeader>
<TabsBody className="col-span-2">
{tabsData.map(({ value, component }) => (
<TabPanel key={value} value={value} className="p-2">
{createElement(component, {
project: project,
onUpdate: onUpdate,
})}
</TabPanel>
))}
</TabsBody>
</Tabs>
</>
);
};
export default SettingsTabPanel;

View File

@ -1,8 +1,13 @@
import React from 'react'; import React from 'react';
import OverviewTabPanel from './OverviewTabPanel'; import OverviewTabPanel from './Overview';
import DeploymentsTabPanel from './DeploymentsTabPanel'; import DeploymentsTabPanel from './Deployments';
import SettingsTabPanel from './SettingsTabPanel'; import SettingsTabPanel from './Settings';
import GeneralTabPanel from './settings/General';
import GitTabPanel from './settings/Git';
import { EnvironmentVariablesTabPanel } from './settings/EnvironmentVariables';
import MembersTabPanel from './settings/Members';
import Domains from './settings/Domains';
const Database = () => ( const Database = () => (
<div> <div>
@ -23,6 +28,29 @@ const Integrations = () => (
</div> </div>
); );
export const settingsTabRoutes = [
{
index: true,
element: <GeneralTabPanel />,
},
{
path: 'domains',
element: <Domains />,
},
{
path: 'git',
element: <GitTabPanel />,
},
{
path: 'environment-variables',
element: <EnvironmentVariablesTabPanel />,
},
{
path: 'members',
element: <MembersTabPanel />,
},
];
export const projectTabRoutes = [ export const projectTabRoutes = [
{ {
index: true, index: true,
@ -43,5 +71,6 @@ export const projectTabRoutes = [
{ {
path: 'settings', path: 'settings',
element: <SettingsTabPanel />, element: <SettingsTabPanel />,
children: settingsTabRoutes,
}, },
]; ];

View File

@ -1,15 +1,18 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link, useOutletContext } from 'react-router-dom';
import { Domain, Project } from 'gql-client'; import { Domain } from 'gql-client';
import { Button, Typography } from '@material-tailwind/react'; import { Button, Typography } from '@material-tailwind/react';
import DomainCard from './DomainCard'; import DomainCard from '../../../../../components/projects/project/settings/DomainCard';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../context/GQLClientContext';
import repositories from '../../../../assets/repositories.json'; import repositories from '../../../../../assets/repositories.json';
import { OutletContextType } from '../../../../../types/project';
const Domains = ({ project }: { project: Project }) => { const Domains = () => {
const client = useGQLClient(); const client = useGQLClient();
const { project } = useOutletContext<OutletContextType>();
const [domains, setDomains] = useState<Domain[]>([]); const [domains, setDomains] = useState<Domain[]>([]);
const fetchDomains = async () => { const fetchDomains = async () => {

View File

@ -13,22 +13,11 @@ import {
Chip, Chip,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import AddEnvironmentVariableRow from './AddEnvironmentVariableRow'; import AddEnvironmentVariableRow from '../../../../../components/projects/project/settings/AddEnvironmentVariableRow';
import DisplayEnvironmentVariables from './DisplayEnvironmentVariables'; import DisplayEnvironmentVariables from '../../../../../components/projects/project/settings/DisplayEnvironmentVariables';
import HorizontalLine from '../../../HorizontalLine'; import HorizontalLine from '../../../../../components/HorizontalLine';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../context/GQLClientContext';
import { EnvironmentVariablesFormValues } from '../../../../../types/project';
export type EnvironmentVariablesFormValues = {
variables: {
key: string;
value: string;
}[];
environment: {
development: boolean;
preview: boolean;
production: boolean;
};
};
export const EnvironmentVariablesTabPanel = () => { export const EnvironmentVariablesTabPanel = () => {
const { id } = useParams(); const { id } = useParams();

View File

@ -1,15 +1,16 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { Link } from 'react-router-dom'; import { Link, useOutletContext } 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 { Organization, Project } from 'gql-client'; import { Organization } from 'gql-client';
import { Button, Typography, Input, Option } from '@material-tailwind/react'; import { Button, Typography, Input, Option } from '@material-tailwind/react';
import DeleteProjectDialog from './DeleteProjectDialog'; import DeleteProjectDialog from '../../../../../components/projects/project/settings/DeleteProjectDialog';
import ConfirmDialog from '../../../shared/ConfirmDialog'; import ConfirmDialog from '../../../../../components/shared/ConfirmDialog';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../context/GQLClientContext';
import AsyncSelect from '../../../shared/AsyncSelect'; import AsyncSelect from '../../../../../components/shared/AsyncSelect';
import { OutletContextType } from '../../../../../types/project';
const CopyIcon = ({ value }: { value: string }) => { const CopyIcon = ({ value }: { value: string }) => {
return ( return (
@ -25,14 +26,10 @@ const CopyIcon = ({ value }: { value: string }) => {
); );
}; };
const GeneralTabPanel = ({ const GeneralTabPanel = () => {
project,
onUpdate,
}: {
project: Project;
onUpdate: () => Promise<void>;
}) => {
const client = useGQLClient(); const client = useGQLClient();
const { project, onUpdate } = useOutletContext<OutletContextType>();
const [transferOrganizations, setTransferOrganizations] = useState< const [transferOrganizations, setTransferOrganizations] = useState<
Organization[] Organization[]
>([]); >([]);

View File

@ -1,12 +1,13 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { useOutletContext } from 'react-router-dom';
import { SubmitHandler, useForm } from 'react-hook-form'; import { SubmitHandler, useForm } from 'react-hook-form';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { Project } from 'gql-client';
import { Button, Input, Switch, Typography } from '@material-tailwind/react'; import { Button, Input, Switch, Typography } from '@material-tailwind/react';
import WebhookCard from './WebhookCard'; import WebhookCard from '../../../../../components/projects/project/settings/WebhookCard';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../context/GQLClientContext';
import { OutletContextType } from '../../../../../types/project';
type UpdateProdBranchValues = { type UpdateProdBranchValues = {
prodBranch: string; prodBranch: string;
@ -16,14 +17,9 @@ type UpdateWebhooksValues = {
webhookUrl: string; webhookUrl: string;
}; };
const GitTabPanel = ({ const GitTabPanel = () => {
project,
onUpdate,
}: {
project: Project;
onUpdate: () => Promise<void>;
}) => {
const client = useGQLClient(); const client = useGQLClient();
const { project, onUpdate } = useOutletContext<OutletContextType>();
const { const {
register: registerProdBranch, register: registerProdBranch,

View File

@ -1,22 +1,20 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import toast, { Toaster } from 'react-hot-toast'; import { useOutletContext } from 'react-router-dom';
import { import toast from 'react-hot-toast';
Permission, import { Permission, AddProjectMemberInput, ProjectMember } from 'gql-client';
Project,
AddProjectMemberInput,
ProjectMember,
} from 'gql-client';
import { Chip, Button, Typography } from '@material-tailwind/react'; import { Chip, Button, Typography } from '@material-tailwind/react';
import MemberCard from './MemberCard'; import MemberCard from '../../../../../components/projects/project/settings/MemberCard';
import AddMemberDialog from './AddMemberDialog'; import AddMemberDialog from '../../../../../components/projects/project/settings/AddMemberDialog';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../context/GQLClientContext';
import { OutletContextType } from '../../../../../types/project';
const FIRST_MEMBER_CARD = 0; const FIRST_MEMBER_CARD = 0;
const MembersTabPanel = ({ project }: { project: Project }) => { const MembersTabPanel = () => {
const client = useGQLClient(); const client = useGQLClient();
const { project } = useOutletContext<OutletContextType>();
const [addmemberDialogOpen, setAddMemberDialogOpen] = useState(false); const [addmemberDialogOpen, setAddMemberDialogOpen] = useState(false);
@ -127,7 +125,6 @@ const MembersTabPanel = ({ project }: { project: Project }) => {
open={addmemberDialogOpen} open={addmemberDialogOpen}
handleAddMember={addMemberHandler} handleAddMember={addMemberHandler}
/> />
<Toaster />
</div> </div>
); );
}; };

View File

@ -65,3 +65,15 @@ export type OutletContextType = {
project: Project; project: Project;
onUpdate: () => Promise<void>; onUpdate: () => Promise<void>;
}; };
export type EnvironmentVariablesFormValues = {
variables: {
key: string;
value: string;
}[];
environment: {
development: boolean;
preview: boolean;
production: boolean;
};
};