forked from cerc-io/snowballtools-base
		
	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