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:
Nabarun Gogoi 2024-02-12 12:17:47 +05:30 committed by GitHub
parent a58b9b255e
commit 559e0f8934
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 152 additions and 136 deletions

View File

@ -83,24 +83,6 @@ export class Database {
return userOrgs; 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> { async getProjectById (projectId: string): Promise<Project | null> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);

View File

@ -28,7 +28,6 @@
"react-oauth-popup": "^1.0.5", "react-oauth-popup": "^1.0.5",
"react-router-dom": "^6.20.1", "react-router-dom": "^6.20.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-tabs": "^6.0.2",
"react-timer-hook": "^3.0.7", "react-timer-hook": "^3.0.7",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"usehooks-ts": "^2.10.0", "usehooks-ts": "^2.10.0",

View File

@ -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;

View File

@ -1,17 +1,30 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import {
Link,
Outlet,
useLocation,
useNavigate,
useParams,
} from 'react-router-dom';
import { Project as ProjectType } from 'gql-client'; 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 HorizontalLine from '../../../components/HorizontalLine';
import ProjectTabs from '../../../components/projects/project/ProjectTabs';
import { useGQLClient } from '../../../context/GQLClientContext'; import { useGQLClient } from '../../../context/GQLClientContext';
const Id = () => { const Id = () => {
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const client = useGQLClient(); const client = useGQLClient();
const location = useLocation();
const [project, setProject] = useState<ProjectType | null>(null); 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(() => { useEffect(() => {
fetchProject(id); fetchProject(id);
}, [id]); }, [id]);
@ -54,7 +76,44 @@ const Id = () => {
</div> </div>
<HorizontalLine /> <HorizontalLine />
<div className="p-4"> <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> </div>
</> </>
) : ( ) : (

View File

@ -1,25 +1,31 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; 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 { Button, Typography } from '@material-tailwind/react';
import DeploymentDetailsCard from './deployments/DeploymentDetailsCard'; import DeploymentDetailsCard from '../../../../components/projects/project/deployments/DeploymentDetailsCard';
import FilterForm, { import FilterForm, {
FilterValue, FilterValue,
StatusOptions, StatusOptions,
} from './deployments/FilterForm'; } from '../../../../components/projects/project/deployments/FilterForm';
import { DeploymentDetails } from '../../../types/project'; import {
import { useGQLClient } from '../../../context/GQLClientContext'; DeploymentDetails,
import { COMMIT_DETAILS } from '../../../constants'; OutletContextType,
} from '../../../../types/project';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { COMMIT_DETAILS } from '../../../../constants';
const DEFAULT_FILTER_VALUE: FilterValue = { const DEFAULT_FILTER_VALUE: FilterValue = {
searchedBranch: '', searchedBranch: '',
status: StatusOptions.ALL_STATUS, status: StatusOptions.ALL_STATUS,
}; };
const DeploymentsTabPanel = ({ project }: { project: Project }) => { const DeploymentsTabPanel = () => {
const client = useGQLClient(); const client = useGQLClient();
const { project } = useOutletContext<OutletContextType>();
const [filterValue, setFilterValue] = useState(DEFAULT_FILTER_VALUE); const [filterValue, setFilterValue] = useState(DEFAULT_FILTER_VALUE);
const [deployments, setDeployments] = useState<DeploymentDetails[]>([]); const [deployments, setDeployments] = useState<DeploymentDetails[]>([]);
const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]); const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]);

View File

@ -1,29 +1,26 @@
import React, { useEffect, useState } from 'react'; 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 { Typography, Button, Chip } from '@material-tailwind/react';
import ActivityCard from './ActivityCard'; import ActivityCard from '../../../../components/projects/project/ActivityCard';
import { relativeTimeMs } from '../../../utils/time'; import { relativeTimeMs } from '../../../../utils/time';
import { useOctokit } from '../../../context/OctokitContext'; import { useOctokit } from '../../../../context/OctokitContext';
import { GitCommitDetails } from '../../../types/project'; import { GitCommitDetails, OutletContextType } from '../../../../types/project';
import { useGQLClient } from '../../../context/GQLClientContext'; import { useGQLClient } from '../../../../context/GQLClientContext';
const COMMITS_PER_PAGE = 4; const COMMITS_PER_PAGE = 4;
interface OverviewProps { const OverviewTabPanel = () => {
project: Project;
}
// TODO: Check if any live domain is set for production branch
const OverviewTabPanel = ({ project }: OverviewProps) => {
const { octokit } = useOctokit(); const { octokit } = useOctokit();
const [activities, setActivities] = useState<GitCommitDetails[]>([]); const [activities, setActivities] = useState<GitCommitDetails[]>([]);
const [liveDomain, setLiveDomain] = useState<Domain>(); const [liveDomain, setLiveDomain] = useState<Domain>();
const client = useGQLClient(); const client = useGQLClient();
const { project } = useOutletContext<OutletContextType>();
useEffect(() => { useEffect(() => {
if (!octokit) { if (!octokit) {
return; return;

View File

@ -1,5 +1,5 @@
import React, { createElement } from 'react'; import React, { createElement } from 'react';
import { Project } from 'gql-client'; import { useOutletContext } from 'react-router-dom';
import { import {
Tabs, Tabs,
@ -9,11 +9,12 @@ import {
TabPanel, TabPanel,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import Domains from './settings/Domains'; import Domains from '../../../../components/projects/project/settings/Domains';
import GeneralTabPanel from './settings/GeneralTabPanel'; import GeneralTabPanel from '../../../../components/projects/project/settings/GeneralTabPanel';
import { EnvironmentVariablesTabPanel } from './settings/EnvironmentVariablesTabPanel'; import { EnvironmentVariablesTabPanel } from '../../../../components/projects/project/settings/EnvironmentVariablesTabPanel';
import GitTabPanel from './settings/GitTabPanel'; import GitTabPanel from '../../../../components/projects/project/settings/GitTabPanel';
import MembersTabPanel from './settings/MembersTabPanel'; import MembersTabPanel from '../../../../components/projects/project/settings/MembersTabPanel';
import { OutletContextType } from '../../../../types/project';
const tabsData = [ const tabsData = [
{ {
@ -48,13 +49,9 @@ const tabsData = [
}, },
]; ];
const SettingsTabPanel = ({ const SettingsTabPanel = () => {
project, const { project, onUpdate } = useOutletContext<OutletContextType>();
onUpdate,
}: {
project: Project;
onUpdate: () => Promise<void>;
}) => {
return ( return (
<> <>
<Tabs <Tabs

View 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 />,
},
];

View File

@ -5,6 +5,7 @@ import Id from './Id';
import AddDomain from './id/domain/add'; import AddDomain from './id/domain/add';
import { createProjectRoutes } from './create/routes'; import { createProjectRoutes } from './create/routes';
import { addDomainRoutes } from './id/domain/add/routes'; import { addDomainRoutes } from './id/domain/add/routes';
import { projectTabRoutes } from './id/routes';
export const projectsRoutesWithoutSearch = [ export const projectsRoutesWithoutSearch = [
{ {
@ -23,5 +24,6 @@ export const projectsRoutesWithSearch = [
{ {
path: ':id', path: ':id',
element: <Id />, element: <Id />,
children: projectTabRoutes,
}, },
]; ];

View File

@ -60,3 +60,8 @@ export interface Commit {
createdAt: string; createdAt: string;
branch: string; branch: string;
} }
export type OutletContextType = {
project: Project;
onUpdate: () => Promise<void>;
};

View File

@ -6139,11 +6139,6 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== 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: cmd-shim@6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d"
@ -13281,7 +13276,7 @@ promzard@^1.0.0:
dependencies: dependencies:
read "^2.0.0" 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" version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -13623,14 +13618,6 @@ react-syntax-highlighter@^15.5.0:
prismjs "^1.27.0" prismjs "^1.27.0"
refractor "^3.6.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: react-timer-hook@^3.0.7:
version "3.0.7" version "3.0.7"
resolved "https://registry.yarnpkg.com/react-timer-hook/-/react-timer-hook-3.0.7.tgz#ac42c43d0034b873cbf97b44eb34ccb2b11fe5e0" resolved "https://registry.yarnpkg.com/react-timer-hook/-/react-timer-hook-3.0.7.tgz#ac42c43d0034b873cbf97b44eb34ccb2b11fe5e0"