Implement routes for project tabs (#63)
* Add routes to project tabs * remove react tabs and use material tailwind component instead * Refactor code to move project tab panels in pages directory * Remove unused function from database class * Refactor routes for project tabs
This commit is contained in:
parent
a58b9b255e
commit
559e0f8934
@ -83,24 +83,6 @@ export class Database {
|
||||
return userOrgs;
|
||||
}
|
||||
|
||||
async getProjectsByOrganizationId (organizationId: string): Promise<Project[]> {
|
||||
const projectRepository = this.dataSource.getRepository(Project);
|
||||
|
||||
const projects = await projectRepository.find({
|
||||
relations: {
|
||||
organization: true,
|
||||
owner: true
|
||||
},
|
||||
where: {
|
||||
organization: {
|
||||
id: organizationId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
async getProjectById (projectId: string): Promise<Project | null> {
|
||||
const projectRepository = this.dataSource.getRepository(Project);
|
||||
|
||||
|
@ -28,7 +28,6 @@
|
||||
"react-oauth-popup": "^1.0.5",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-tabs": "^6.0.2",
|
||||
"react-timer-hook": "^3.0.7",
|
||||
"typescript": "^4.9.5",
|
||||
"usehooks-ts": "^2.10.0",
|
||||
|
@ -1,65 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import { Project } from 'gql-client';
|
||||
|
||||
import OverviewTabPanel from './OverviewTabPanel';
|
||||
import DeploymentsTabPanel from './DeploymentsTabPanel';
|
||||
import SettingsTabPanel from './SettingsTabPanel';
|
||||
|
||||
interface ProjectTabsProps {
|
||||
project: Project;
|
||||
onUpdate: () => Promise<void>;
|
||||
}
|
||||
|
||||
const Database = () => (
|
||||
<div>
|
||||
Content of database tab
|
||||
<p className="block">
|
||||
It is a long established fact that a reader will be distracted by the
|
||||
readable content of a page when looking at its layout.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
const Integrations = () => (
|
||||
<div>
|
||||
Content of integrations tab
|
||||
<p className="block">
|
||||
There are many variations of passages of Lorem Ipsum available.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ProjectTabs = ({ project, onUpdate }: ProjectTabsProps) => {
|
||||
return (
|
||||
<Tabs
|
||||
selectedTabClassName={
|
||||
'border-b-2 border-gray-900 text-gray-900 focus:outline-none'
|
||||
}
|
||||
>
|
||||
<TabList className="flex border-b border-gray-300 text-gray-600">
|
||||
<Tab className={'p-2 cursor-pointer'}>Overview</Tab>
|
||||
<Tab className={'p-2 cursor-pointer'}>Deployments</Tab>
|
||||
<Tab className={'p-2 cursor-pointer'}>Database</Tab>
|
||||
<Tab className={'p-2 cursor-pointer'}>Integrations</Tab>
|
||||
<Tab className={'p-2 cursor-pointer'}>Settings</Tab>
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
<OverviewTabPanel project={project} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<DeploymentsTabPanel project={project} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Database />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Integrations />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<SettingsTabPanel project={project} onUpdate={onUpdate} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectTabs;
|
@ -1,17 +1,30 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Link,
|
||||
Outlet,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import { Project as ProjectType } from 'gql-client';
|
||||
|
||||
import { Button, Typography } from '@material-tailwind/react';
|
||||
import {
|
||||
Button,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabsBody,
|
||||
TabsHeader,
|
||||
Typography,
|
||||
} from '@material-tailwind/react';
|
||||
|
||||
import HorizontalLine from '../../../components/HorizontalLine';
|
||||
import ProjectTabs from '../../../components/projects/project/ProjectTabs';
|
||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||
|
||||
const Id = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const client = useGQLClient();
|
||||
const location = useLocation();
|
||||
|
||||
const [project, setProject] = useState<ProjectType | null>(null);
|
||||
|
||||
@ -22,6 +35,15 @@ const Id = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const currentTab = useMemo(() => {
|
||||
if (id) {
|
||||
const [, tabPath] = location.pathname.split(id);
|
||||
return tabPath;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, [location, id]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProject(id);
|
||||
}, [id]);
|
||||
@ -54,7 +76,44 @@ const Id = () => {
|
||||
</div>
|
||||
<HorizontalLine />
|
||||
<div className="p-4">
|
||||
<ProjectTabs project={project} onUpdate={onUpdate} />
|
||||
<Tabs value={currentTab}>
|
||||
<TabsHeader
|
||||
className="rounded-none border-b border-blue-gray-50 bg-transparent p-0"
|
||||
indicatorProps={{
|
||||
className:
|
||||
'bg-transparent border-b-2 border-gray-900 shadow-none rounded-none',
|
||||
}}
|
||||
>
|
||||
<Link to="">
|
||||
<Tab value="" className={'p-2 cursor-pointer'}>
|
||||
Overview
|
||||
</Tab>
|
||||
</Link>
|
||||
<Link to="deployments">
|
||||
<Tab value="/deployments" className={'p-2 cursor-pointer'}>
|
||||
Deployments
|
||||
</Tab>
|
||||
</Link>
|
||||
<Link to="database">
|
||||
<Tab value="/database" className={'p-2 cursor-pointer'}>
|
||||
Database
|
||||
</Tab>
|
||||
</Link>
|
||||
<Link to="integrations">
|
||||
<Tab value="/integrations" className={'p-2 cursor-pointer'}>
|
||||
Integrations
|
||||
</Tab>
|
||||
</Link>
|
||||
<Link to="settings">
|
||||
<Tab value="/settings" className={'p-2 cursor-pointer'}>
|
||||
Settings
|
||||
</Tab>
|
||||
</Link>
|
||||
</TabsHeader>
|
||||
<TabsBody>
|
||||
<Outlet context={{ project, onUpdate }} />
|
||||
</TabsBody>
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
@ -1,25 +1,31 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Project, Domain } from 'gql-client';
|
||||
import { Domain } from 'gql-client';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
|
||||
import { Button, Typography } from '@material-tailwind/react';
|
||||
|
||||
import DeploymentDetailsCard from './deployments/DeploymentDetailsCard';
|
||||
import DeploymentDetailsCard from '../../../../components/projects/project/deployments/DeploymentDetailsCard';
|
||||
import FilterForm, {
|
||||
FilterValue,
|
||||
StatusOptions,
|
||||
} from './deployments/FilterForm';
|
||||
import { DeploymentDetails } from '../../../types/project';
|
||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||
import { COMMIT_DETAILS } from '../../../constants';
|
||||
} from '../../../../components/projects/project/deployments/FilterForm';
|
||||
import {
|
||||
DeploymentDetails,
|
||||
OutletContextType,
|
||||
} from '../../../../types/project';
|
||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||
import { COMMIT_DETAILS } from '../../../../constants';
|
||||
|
||||
const DEFAULT_FILTER_VALUE: FilterValue = {
|
||||
searchedBranch: '',
|
||||
status: StatusOptions.ALL_STATUS,
|
||||
};
|
||||
|
||||
const DeploymentsTabPanel = ({ project }: { project: Project }) => {
|
||||
const DeploymentsTabPanel = () => {
|
||||
const client = useGQLClient();
|
||||
|
||||
const { project } = useOutletContext<OutletContextType>();
|
||||
|
||||
const [filterValue, setFilterValue] = useState(DEFAULT_FILTER_VALUE);
|
||||
const [deployments, setDeployments] = useState<DeploymentDetails[]>([]);
|
||||
const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]);
|
@ -1,29 +1,26 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Domain, DomainStatus, Project } from 'gql-client';
|
||||
import { Domain, DomainStatus } from 'gql-client';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
|
||||
import { Typography, Button, Chip } from '@material-tailwind/react';
|
||||
|
||||
import ActivityCard from './ActivityCard';
|
||||
import { relativeTimeMs } from '../../../utils/time';
|
||||
import { useOctokit } from '../../../context/OctokitContext';
|
||||
import { GitCommitDetails } from '../../../types/project';
|
||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||
import ActivityCard from '../../../../components/projects/project/ActivityCard';
|
||||
import { relativeTimeMs } from '../../../../utils/time';
|
||||
import { useOctokit } from '../../../../context/OctokitContext';
|
||||
import { GitCommitDetails, OutletContextType } from '../../../../types/project';
|
||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||
|
||||
const COMMITS_PER_PAGE = 4;
|
||||
|
||||
interface OverviewProps {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
// TODO: Check if any live domain is set for production branch
|
||||
|
||||
const OverviewTabPanel = ({ project }: OverviewProps) => {
|
||||
const OverviewTabPanel = () => {
|
||||
const { octokit } = useOctokit();
|
||||
const [activities, setActivities] = useState<GitCommitDetails[]>([]);
|
||||
const [liveDomain, setLiveDomain] = useState<Domain>();
|
||||
|
||||
const client = useGQLClient();
|
||||
|
||||
const { project } = useOutletContext<OutletContextType>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!octokit) {
|
||||
return;
|
@ -1,5 +1,5 @@
|
||||
import React, { createElement } from 'react';
|
||||
import { Project } from 'gql-client';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Tabs,
|
||||
@ -9,11 +9,12 @@ import {
|
||||
TabPanel,
|
||||
} from '@material-tailwind/react';
|
||||
|
||||
import Domains from './settings/Domains';
|
||||
import GeneralTabPanel from './settings/GeneralTabPanel';
|
||||
import { EnvironmentVariablesTabPanel } from './settings/EnvironmentVariablesTabPanel';
|
||||
import GitTabPanel from './settings/GitTabPanel';
|
||||
import MembersTabPanel from './settings/MembersTabPanel';
|
||||
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 = [
|
||||
{
|
||||
@ -48,13 +49,9 @@ const tabsData = [
|
||||
},
|
||||
];
|
||||
|
||||
const SettingsTabPanel = ({
|
||||
project,
|
||||
onUpdate,
|
||||
}: {
|
||||
project: Project;
|
||||
onUpdate: () => Promise<void>;
|
||||
}) => {
|
||||
const SettingsTabPanel = () => {
|
||||
const { project, onUpdate } = useOutletContext<OutletContextType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
47
packages/frontend/src/pages/org-slug/projects/id/routes.tsx
Normal file
47
packages/frontend/src/pages/org-slug/projects/id/routes.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
import OverviewTabPanel from './OverviewTabPanel';
|
||||
import DeploymentsTabPanel from './DeploymentsTabPanel';
|
||||
import SettingsTabPanel from './SettingsTabPanel';
|
||||
|
||||
const Database = () => (
|
||||
<div>
|
||||
Content of database tab
|
||||
<p className="block">
|
||||
It is a long established fact that a reader will be distracted by the
|
||||
readable content of a page when looking at its layout.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Integrations = () => (
|
||||
<div>
|
||||
Content of integrations tab
|
||||
<p className="block">
|
||||
There are many variations of passages of Lorem Ipsum available.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const projectTabRoutes = [
|
||||
{
|
||||
index: true,
|
||||
element: <OverviewTabPanel />,
|
||||
},
|
||||
{
|
||||
path: 'deployments',
|
||||
element: <DeploymentsTabPanel />,
|
||||
},
|
||||
{
|
||||
path: 'database',
|
||||
element: <Database />,
|
||||
},
|
||||
{
|
||||
path: 'integrations',
|
||||
element: <Integrations />,
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
element: <SettingsTabPanel />,
|
||||
},
|
||||
];
|
@ -5,6 +5,7 @@ import Id from './Id';
|
||||
import AddDomain from './id/domain/add';
|
||||
import { createProjectRoutes } from './create/routes';
|
||||
import { addDomainRoutes } from './id/domain/add/routes';
|
||||
import { projectTabRoutes } from './id/routes';
|
||||
|
||||
export const projectsRoutesWithoutSearch = [
|
||||
{
|
||||
@ -23,5 +24,6 @@ export const projectsRoutesWithSearch = [
|
||||
{
|
||||
path: ':id',
|
||||
element: <Id />,
|
||||
children: projectTabRoutes,
|
||||
},
|
||||
];
|
||||
|
@ -60,3 +60,8 @@ export interface Commit {
|
||||
createdAt: string;
|
||||
branch: string;
|
||||
}
|
||||
|
||||
export type OutletContextType = {
|
||||
project: Project;
|
||||
onUpdate: () => Promise<void>;
|
||||
};
|
||||
|
15
yarn.lock
15
yarn.lock
@ -6139,11 +6139,6 @@ clone@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
||||
|
||||
clsx@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
|
||||
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
|
||||
|
||||
cmd-shim@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d"
|
||||
@ -13281,7 +13276,7 @@ promzard@^1.0.0:
|
||||
dependencies:
|
||||
read "^2.0.0"
|
||||
|
||||
prop-types@15.8.1, prop-types@^15.5.0, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@15.8.1, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@ -13623,14 +13618,6 @@ react-syntax-highlighter@^15.5.0:
|
||||
prismjs "^1.27.0"
|
||||
refractor "^3.6.0"
|
||||
|
||||
react-tabs@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-6.0.2.tgz#bc1065c3828561fee285a8fd045f22e0fcdde1eb"
|
||||
integrity sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==
|
||||
dependencies:
|
||||
clsx "^2.0.0"
|
||||
prop-types "^15.5.0"
|
||||
|
||||
react-timer-hook@^3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-timer-hook/-/react-timer-hook-3.0.7.tgz#ac42c43d0034b873cbf97b44eb34ccb2b11fe5e0"
|
||||
|
Loading…
Reference in New Issue
Block a user