From 413ed03eb8eaea95b800ea0cc9119eb3ab2a80e7 Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Wed, 7 Feb 2024 18:41:54 +0530 Subject: [PATCH] Implement organization switcher and use slug in URL (#59) * Implement dropdown for organizations switcher * Add dynamic route for organization id * Update routes for organization slug * Use organization slug for adding project * Refactor to fetch organizations at sidebar component * Update organization switcher based on searched project * Refactor types in frontend --------- Co-authored-by: neeraj --- packages/backend/src/database.ts | 18 +++-- packages/backend/src/entity/Organization.ts | 7 +- packages/backend/src/resolvers.ts | 8 +-- packages/backend/src/schema.gql | 6 +- packages/backend/src/service.ts | 17 +++-- .../backend/test/fixtures/organizations.json | 6 +- packages/frontend/src/App.tsx | 20 ++++-- packages/frontend/src/components/Sidebar.tsx | 66 +++++++++++++---- .../src/components/projects/create/Deploy.tsx | 5 +- .../projects/create/TemplateCard.tsx | 2 +- .../deployments/DeploymentDetailsCard.tsx | 45 ++++++------ .../project/settings/DeleteProjectDialog.tsx | 5 +- packages/frontend/src/layouts/Dashboard.tsx | 44 ------------ .../frontend/src/layouts/ProjectSearch.tsx | 11 +-- packages/frontend/src/pages/OrgSlug.tsx | 23 ++++++ packages/frontend/src/pages/index.tsx | 72 +++++-------------- .../src/pages/{ => org-slug}/Settings.tsx | 0 .../frontend/src/pages/org-slug/index.tsx | 67 +++++++++++++++++ .../pages/{ => org-slug}/projects/Create.tsx | 7 +- .../src/pages/{ => org-slug}/projects/Id.tsx | 6 +- .../{ => org-slug}/projects/create/Import.tsx | 17 +++-- .../projects/create/Template.tsx | 2 +- .../{ => org-slug}/projects/create/index.tsx | 10 +-- .../{ => org-slug}/projects/create/routes.tsx | 0 .../projects/create/success/Id.tsx | 4 +- .../projects/create/template/Deploy.tsx | 2 +- .../projects/create/template/index.tsx | 4 +- .../projects/create/template/routes.tsx | 0 .../projects/id/domain/add/Config.tsx | 7 +- .../projects/id/domain/add/index.tsx | 6 +- .../projects/id/domain/add/routes.tsx | 2 +- .../pages/{ => org-slug}/projects/routes.tsx | 0 packages/frontend/src/types/project.ts | 31 -------- packages/gql-client/src/client.ts | 7 +- packages/gql-client/src/mutations.ts | 4 +- packages/gql-client/src/queries.ts | 6 +- packages/gql-client/src/types.ts | 2 +- 37 files changed, 293 insertions(+), 246 deletions(-) delete mode 100644 packages/frontend/src/layouts/Dashboard.tsx create mode 100644 packages/frontend/src/pages/OrgSlug.tsx rename packages/frontend/src/pages/{ => org-slug}/Settings.tsx (100%) create mode 100644 packages/frontend/src/pages/org-slug/index.tsx rename packages/frontend/src/pages/{ => org-slug}/projects/Create.tsx (74%) rename packages/frontend/src/pages/{ => org-slug}/projects/Id.tsx (88%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/Import.tsx (73%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/Template.tsx (95%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/index.tsx (64%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/routes.tsx (100%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/success/Id.tsx (95%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/template/Deploy.tsx (57%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/template/index.tsx (96%) rename packages/frontend/src/pages/{ => org-slug}/projects/create/template/routes.tsx (100%) rename packages/frontend/src/pages/{ => org-slug}/projects/id/domain/add/Config.tsx (93%) rename packages/frontend/src/pages/{ => org-slug}/projects/id/domain/add/index.tsx (91%) rename packages/frontend/src/pages/{ => org-slug}/projects/id/domain/add/routes.tsx (68%) rename packages/frontend/src/pages/{ => org-slug}/projects/routes.tsx (100%) diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 517818fc..73fc358b 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -56,6 +56,13 @@ export class Database { return updateResult.affected > 0; } + async getOrganization (options: FindOneOptions): Promise { + const organizationRepository = this.dataSource.getRepository(Organization); + const organization = await organizationRepository.findOne(options); + + return organization; + } + async getOrganizationsByUserId (userId: string): Promise { const organizationRepository = this.dataSource.getRepository(Organization); @@ -108,7 +115,7 @@ export class Database { return project; } - async getProjectsInOrganization (userId: string, organizationId: string): Promise { + async getProjectsInOrganization (userId: string, organizationSlug: string): Promise { const projectRepository = this.dataSource.getRepository(Project); const projects = await projectRepository @@ -116,9 +123,10 @@ export class Database { .leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true') .leftJoinAndSelect('deployments.domain', 'domain') .leftJoin('project.projectMembers', 'projectMembers') - .where('(project.ownerId = :userId OR projectMembers.userId = :userId) AND project.organizationId = :organizationId', { + .leftJoin('project.organization', 'organization') + .where('(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug', { userId, - organizationId + organizationSlug }) .getMany(); @@ -297,7 +305,7 @@ export class Database { return Boolean(updateResult.affected); } - async addProject (userId: string, projectDetails: DeepPartial): Promise { + async addProject (userId: string, organizationId: string, projectDetails: DeepPartial): Promise { const projectRepository = this.dataSource.getRepository(Project); // TODO: Check if organization exists @@ -312,7 +320,7 @@ export class Database { }); newProject.organization = Object.assign(new Organization(), { - id: projectDetails.organizationId + id: organizationId }); newProject.subDomain = `${newProject.name}.${PROJECT_DOMAIN}`; diff --git a/packages/backend/src/entity/Organization.ts b/packages/backend/src/entity/Organization.ts index e1087422..15ff1011 100644 --- a/packages/backend/src/entity/Organization.ts +++ b/packages/backend/src/entity/Organization.ts @@ -4,11 +4,13 @@ import { Column, CreateDateColumn, UpdateDateColumn, - OneToMany + OneToMany, + Unique } from 'typeorm'; import { UserOrganization } from './UserOrganization'; @Entity() +@Unique(['slug']) export class Organization { @PrimaryGeneratedColumn('uuid') id!: string; @@ -16,6 +18,9 @@ export class Organization { @Column('varchar', { length: 255 }) name!: string; + @Column('varchar') + slug!: string; + @CreateDateColumn() createdAt!: Date; diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 4eff1e48..9f1dc4e8 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -29,8 +29,8 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv return service.getProjectById(projectId); }, - projectsInOrganization: async (_: any, { organizationId }: {organizationId: string }, context: any) => { - return service.getProjectsInOrganization(context.userId, organizationId); + projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => { + return service.getProjectsInOrganization(context.userId, organizationSlug); }, deployments: async (_: any, { projectId }: { projectId: string }) => { @@ -130,9 +130,9 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv } }, - addProject: async (_: any, { data }: { data: DeepPartial }, context: any) => { + addProject: async (_: any, { organizationSlug, data }: { organizationSlug: string, data: DeepPartial }, context: any) => { try { - return service.addProject(context.userId, data); + return service.addProject(context.userId, organizationSlug, data); } catch (err) { log(err); } diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 93ff8df9..f0a33004 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -41,6 +41,7 @@ type User { type Organization { id: String! name: String! + slug: String! projects: [Project!] createdAt: String! updatedAt: String! @@ -129,7 +130,6 @@ input AddEnvironmentVariableInput { } input AddProjectInput { - organizationId: String! name: String! repository: String! prodBranch: String! @@ -176,7 +176,7 @@ type Query { user: User! organizations: [Organization!] projects: [Project!] - projectsInOrganization(organizationId: String!): [Project!] + projectsInOrganization(organizationSlug: String!): [Project!] project(projectId: String!): Project deployments(projectId: String!): [Deployment!] environmentVariables(projectId: String!): [EnvironmentVariable!] @@ -193,7 +193,7 @@ type Mutation { updateEnvironmentVariable(environmentVariableId: String!, data: UpdateEnvironmentVariableInput!): Boolean! removeEnvironmentVariable(environmentVariableId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean! - addProject(data: AddProjectInput): Project! + addProject(organizationSlug: String!, data: AddProjectInput): Project! updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean! redeployToProd(deploymentId: String!): Boolean! deleteProject(projectId: String!): Boolean! diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 3c8f9615..3dc835c4 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -40,8 +40,8 @@ export class Service { return dbProject; } - async getProjectsInOrganization (userId:string, organizationId: string): Promise { - const dbProjects = await this.db.getProjectsInOrganization(userId, organizationId); + async getProjectsInOrganization (userId:string, organizationSlug: string): Promise { + const dbProjects = await this.db.getProjectsInOrganization(userId, organizationSlug); return dbProjects; } @@ -182,8 +182,17 @@ export class Service { return updateResult; } - async addProject (userId: string, data: DeepPartial): Promise { - return this.db.addProject(userId, data); + async addProject (userId: string, organizationSlug: string, data: DeepPartial): Promise { + const organization = await this.db.getOrganization({ + where: { + slug: organizationSlug + } + }); + if (!organization) { + throw new Error('Organization does not exist'); + } + + return this.db.addProject(userId, organization.id, data); } async updateProject (projectId: string, data: DeepPartial): Promise { diff --git a/packages/backend/test/fixtures/organizations.json b/packages/backend/test/fixtures/organizations.json index 32e78b66..bd0d121b 100644 --- a/packages/backend/test/fixtures/organizations.json +++ b/packages/backend/test/fixtures/organizations.json @@ -1,8 +1,10 @@ [ { - "name": "Snowball Tools" + "name": "Snowball Tools", + "slug": "snowball-tools" }, { - "name": "AirFoil" + "name": "AirFoil", + "slug": "airfoil" } ] diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index e140b823..16fbb8f7 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -1,26 +1,28 @@ import React from 'react'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import DashboardLayout from './layouts/Dashboard'; -import Home from './pages/index'; -import Settings from './pages/Settings'; +import OrgSlug from './pages/OrgSlug'; +import Projects from './pages/org-slug'; +import Settings from './pages/org-slug/Settings'; import { projectsRoutesWithSearch, projectsRoutesWithoutSearch, -} from './pages/projects/routes'; +} from './pages/org-slug/projects/routes'; import ProjectSearchLayout from './layouts/ProjectSearch'; import { OctokitProvider } from './context/OctokitContext'; +import Index from './pages'; const router = createBrowserRouter([ { - element: , + path: ':orgSlug', + element: , children: [ { element: , children: [ { - path: '/', - element: , + path: '', + element: , }, { path: 'projects', @@ -38,6 +40,10 @@ const router = createBrowserRouter([ }, ], }, + { + path: '/', + element: , + }, ]); function App() { diff --git a/packages/frontend/src/components/Sidebar.tsx b/packages/frontend/src/components/Sidebar.tsx index 9e7abd86..c928da51 100644 --- a/packages/frontend/src/components/Sidebar.tsx +++ b/packages/frontend/src/components/Sidebar.tsx @@ -1,30 +1,66 @@ -import React from 'react'; -import { Link, NavLink } from 'react-router-dom'; +import React, { useCallback, useEffect, useState } from 'react'; +import { Link, NavLink, useNavigate, useParams } from 'react-router-dom'; import { Organization } from 'gql-client'; -import { Card, CardBody, Typography } from '@material-tailwind/react'; +import { Typography, Option } from '@material-tailwind/react'; + +import { useGQLClient } from '../context/GQLClientContext'; +import AsyncSelect from './shared/AsyncSelect'; + +const Sidebar = () => { + const { orgSlug } = useParams(); + const navigate = useNavigate(); + const client = useGQLClient(); + + const [selectedOrgSlug, setSelectedOrgSlug] = useState(orgSlug); + const [organizations, setOrganizations] = useState([]); + + const fetchUserOrganizations = useCallback(async () => { + const { organizations } = await client.getOrganizations(); + setOrganizations(organizations); + }, [orgSlug]); + + useEffect(() => { + fetchUserOrganizations(); + setSelectedOrgSlug(orgSlug); + }, [orgSlug]); -const Sidebar = ({ organization }: { organization: Organization }) => { return (
- +

Snowball

- - -
^
-
- {organization.name} - Organization + { + setSelectedOrgSlug(value!); + navigate(`/${value}`); + }} + selected={(_, index) => ( +
+
^
+
+ {organizations[index!]?.name} + Organization +
- - + )} + > + {/* TODO: Show label organization and manage in option */} + {organizations.map((org) => ( + + ))} +
(isActive ? 'text-blue-500' : '')} > Projects @@ -32,7 +68,7 @@ const Sidebar = ({ organization }: { organization: Organization }) => {
(isActive ? 'text-blue-500' : '')} > Settings diff --git a/packages/frontend/src/components/projects/create/Deploy.tsx b/packages/frontend/src/components/projects/create/Deploy.tsx index 6122328d..2021f6ed 100644 --- a/packages/frontend/src/components/projects/create/Deploy.tsx +++ b/packages/frontend/src/components/projects/create/Deploy.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { Button, Typography } from '@material-tailwind/react'; @@ -11,9 +11,10 @@ const Deploy = () => { const [open, setOpen] = React.useState(false); const handleOpen = () => setOpen(!open); const navigate = useNavigate(); + const { orgSlug } = useParams(); const handleCancel = useCallback(() => { - navigate('/projects/create'); + navigate(`/${orgSlug}/projects/create`); }, []); return ( diff --git a/packages/frontend/src/components/projects/create/TemplateCard.tsx b/packages/frontend/src/components/projects/create/TemplateCard.tsx index 46ce1bde..1a62975d 100644 --- a/packages/frontend/src/components/projects/create/TemplateCard.tsx +++ b/packages/frontend/src/components/projects/create/TemplateCard.tsx @@ -34,7 +34,7 @@ const TemplateCard: React.FC = ({ isGitAuth, }) => { return isGitAuth ? ( - + ) : ( diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx index b6f47a88..f089738d 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx @@ -1,4 +1,6 @@ import React, { useState } from 'react'; +import toast from 'react-hot-toast'; +import { Environment, Project, Domain, DeploymentStatus } from 'gql-client'; import { Menu, @@ -9,14 +11,12 @@ import { Chip, ChipProps, } from '@material-tailwind/react'; -import toast from 'react-hot-toast'; -import { Environment, Project, Domain } from 'gql-client'; import { relativeTimeMs } from '../../../../utils/time'; import ConfirmDialog from '../../../shared/ConfirmDialog'; import DeploymentDialogBodyCard from './DeploymentDialogBodyCard'; import AssignDomainDialog from './AssignDomainDialog'; -import { DeploymentDetails, Status } from '../../../../types/project'; +import { DeploymentDetails } from '../../../../types/project'; import { useGQLClient } from '../../../../context/GQLClientContext'; interface DeployDetailsCardProps { @@ -27,10 +27,10 @@ interface DeployDetailsCardProps { prodBranchDomains: Domain[]; } -const STATUS_COLORS: { [key in Status]: ChipProps['color'] } = { - [Status.BUILDING]: 'blue', - [Status.READY]: 'green', - [Status.ERROR]: 'red', +const STATUS_COLORS: { [key in DeploymentStatus]: ChipProps['color'] } = { + [DeploymentStatus.Building]: 'blue', + [DeploymentStatus.Ready]: 'green', + [DeploymentStatus.Error]: 'red', }; const DeploymentDetailsCard = ({ @@ -125,14 +125,12 @@ const DeploymentDetailsCard = ({ > ^ Assign domain - {!(deployment.environment === Environment.Production) && ( - setChangeToProduction(!changeToProduction)} - > - ^ Change to production - - )} - + setChangeToProduction(!changeToProduction)} + disabled={!(deployment.environment !== Environment.Production)} + > + ^ Change to production +
setRedeployToProduction(!redeployToProduction)} @@ -145,14 +143,15 @@ const DeploymentDetailsCard = ({ > ^ Redeploy to production - {deployment.environment === Environment.Production && ( - setRollbackDeployment(!rollbackDeployment)} - disabled={deployment.isCurrent} - > - ^ Rollback to this version - - )} + setRollbackDeployment(!rollbackDeployment)} + disabled={ + deployment.isCurrent || + deployment.environment !== Environment.Production + } + > + ^ Rollback to this version +
diff --git a/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx b/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx index 99958282..5f603d4b 100644 --- a/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx +++ b/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { useForm } from 'react-hook-form'; import toast from 'react-hot-toast'; import { Project } from 'gql-client'; @@ -26,6 +26,7 @@ const DeleteProjectDialog = ({ handleOpen, project, }: DeleteProjectDialogProp) => { + const { orgSlug } = useParams(); const navigate = useNavigate(); const client = useGQLClient(); @@ -43,7 +44,7 @@ const DeleteProjectDialog = ({ const { deleteProject } = await client.deleteProject(project.id); if (deleteProject) { - navigate('/'); + navigate(`/${orgSlug}`); } else { toast.error('Project not deleted'); } diff --git a/packages/frontend/src/layouts/Dashboard.tsx b/packages/frontend/src/layouts/Dashboard.tsx deleted file mode 100644 index b637210d..00000000 --- a/packages/frontend/src/layouts/Dashboard.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { Organization } from 'gql-client'; - -import { Outlet } from 'react-router-dom'; - -import Sidebar from '../components/Sidebar'; -import { useGQLClient } from '../context/GQLClientContext'; - -// TODO: Implement organization switcher -// TODO: Projects get organization details through routes instead of context -const USER_ORGANIZATION_INDEX = 0; - -const Dashboard = () => { - const client = useGQLClient(); - const [organizations, setOrganizations] = useState([]); - - const fetchUserOrganizations = useCallback(async () => { - const { organizations } = await client.getOrganizations(); - setOrganizations(organizations); - }, []); - - useEffect(() => { - fetchUserOrganizations(); - }, []); - - return ( -
- {organizations.length > 0 && ( - <> -
- -
-
-
- -
-
- - )} -
- ); -}; - -export default Dashboard; diff --git a/packages/frontend/src/layouts/ProjectSearch.tsx b/packages/frontend/src/layouts/ProjectSearch.tsx index 32be1155..0f1a3fea 100644 --- a/packages/frontend/src/layouts/ProjectSearch.tsx +++ b/packages/frontend/src/layouts/ProjectSearch.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Outlet, useNavigate, useOutletContext } from 'react-router-dom'; -import { Organization } from 'gql-client'; +import { Outlet, useNavigate } from 'react-router-dom'; import { IconButton, Typography } from '@material-tailwind/react'; @@ -9,7 +8,7 @@ import ProjectSearchBar from '../components/projects/ProjectSearchBar'; const ProjectSearch = () => { const navigate = useNavigate(); - const organization = useOutletContext(); + return (
@@ -17,7 +16,9 @@ const ProjectSearch = () => {
{ - navigate(`/projects/${project.id}`); + navigate( + `/${project.organization.slug}/projects/${project.id}`, + ); }} />
@@ -34,7 +35,7 @@ const ProjectSearch = () => {
- +
); diff --git a/packages/frontend/src/pages/OrgSlug.tsx b/packages/frontend/src/pages/OrgSlug.tsx new file mode 100644 index 00000000..0d30e750 --- /dev/null +++ b/packages/frontend/src/pages/OrgSlug.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Outlet } from 'react-router-dom'; + +import Sidebar from '../components/Sidebar'; + +const OrgSlug = () => { + return ( +
+ <> +
+ +
+
+
+ +
+
+ +
+ ); +}; + +export default OrgSlug; diff --git a/packages/frontend/src/pages/index.tsx b/packages/frontend/src/pages/index.tsx index cb0524e6..376cc518 100644 --- a/packages/frontend/src/pages/index.tsx +++ b/packages/frontend/src/pages/index.tsx @@ -1,69 +1,31 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Link, useOutletContext } from 'react-router-dom'; +import { Navigate } from 'react-router-dom'; +import { useGQLClient } from '../context/GQLClientContext'; import { Organization } from 'gql-client'; -import { Button, Typography, Chip } from '@material-tailwind/react'; - -import ProjectCard from '../components/projects/ProjectCard'; -import { useGQLClient } from '../context/GQLClientContext'; -import { ProjectDetails } from '../types/project'; -import { COMMIT_DETAILS } from '../constants'; - -const Projects = () => { +const Index = () => { const client = useGQLClient(); - const organization = useOutletContext(); + const [organization, setOrganization] = useState(); - const [projects, setProjects] = useState([]); - - const fetchProjects = useCallback(async () => { - const { projectsInOrganization } = await client.getProjectsInOrganization( - organization.id, - ); - - const updatedProjects = projectsInOrganization.map((project) => { - return { - ...project, - // TODO: Populate from github API - latestCommit: COMMIT_DETAILS, - }; - }); - - setProjects(updatedProjects); + const fetchUserOrganizations = useCallback(async () => { + const { organizations } = await client.getOrganizations(); + // By default information of first organization displayed + setOrganization(organizations[0]); }, []); useEffect(() => { - fetchProjects(); + fetchUserOrganizations(); }, []); return ( -
-
-
-
- Projects - -
-
-
- - - -
-
-
- {projects.length !== 0 && - projects.map((project, key) => { - return ; - })} -
-
+ <> + {Boolean(organization) ? ( + + ) : ( + <>Loading + )} + ); }; -export default Projects; +export default Index; diff --git a/packages/frontend/src/pages/Settings.tsx b/packages/frontend/src/pages/org-slug/Settings.tsx similarity index 100% rename from packages/frontend/src/pages/Settings.tsx rename to packages/frontend/src/pages/org-slug/Settings.tsx diff --git a/packages/frontend/src/pages/org-slug/index.tsx b/packages/frontend/src/pages/org-slug/index.tsx new file mode 100644 index 00000000..68da2db3 --- /dev/null +++ b/packages/frontend/src/pages/org-slug/index.tsx @@ -0,0 +1,67 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; + +import { Button, Typography, Chip } from '@material-tailwind/react'; + +import ProjectCard from '../../components/projects/ProjectCard'; +import { useGQLClient } from '../../context/GQLClientContext'; +import { ProjectDetails } from '../../types/project'; +import { COMMIT_DETAILS } from '../../constants'; + +const Projects = () => { + const client = useGQLClient(); + const { orgSlug } = useParams(); + const [projects, setProjects] = useState([]); + + const fetchProjects = useCallback(async () => { + const { projectsInOrganization } = await client.getProjectsInOrganization( + orgSlug!, + ); + + const updatedProjects = projectsInOrganization.map((project) => { + return { + ...project, + // TODO: Populate from github API + latestCommit: COMMIT_DETAILS, + }; + }); + + setProjects(updatedProjects); + }, [orgSlug]); + + useEffect(() => { + fetchProjects(); + }, [orgSlug]); + + return ( +
+
+
+
+ Projects + +
+
+
+ + + +
+
+
+ {projects.length !== 0 && + projects.map((project, key) => { + return ; + })} +
+
+ ); +}; + +export default Projects; diff --git a/packages/frontend/src/pages/projects/Create.tsx b/packages/frontend/src/pages/org-slug/projects/Create.tsx similarity index 74% rename from packages/frontend/src/pages/projects/Create.tsx rename to packages/frontend/src/pages/org-slug/projects/Create.tsx index 1f383105..e6b4d930 100644 --- a/packages/frontend/src/pages/projects/Create.tsx +++ b/packages/frontend/src/pages/org-slug/projects/Create.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { Outlet, Link } from 'react-router-dom'; +import { Outlet, Link, useParams } from 'react-router-dom'; import { IconButton } from '@material-tailwind/react'; -import HorizontalLine from '../../components/HorizontalLine'; +import HorizontalLine from '../../../components/HorizontalLine'; const CreateProject = () => { + const { orgSlug } = useParams(); return (
@@ -13,7 +14,7 @@ const CreateProject = () => {

Create new project

- + X diff --git a/packages/frontend/src/pages/projects/Id.tsx b/packages/frontend/src/pages/org-slug/projects/Id.tsx similarity index 88% rename from packages/frontend/src/pages/projects/Id.tsx rename to packages/frontend/src/pages/org-slug/projects/Id.tsx index 035d0dc5..40586b4d 100644 --- a/packages/frontend/src/pages/projects/Id.tsx +++ b/packages/frontend/src/pages/org-slug/projects/Id.tsx @@ -4,9 +4,9 @@ import { Project as ProjectType } from 'gql-client'; import { Button, Typography } from '@material-tailwind/react'; -import HorizontalLine from '../../components/HorizontalLine'; -import ProjectTabs from '../../components/projects/project/ProjectTabs'; -import { useGQLClient } from '../../context/GQLClientContext'; +import HorizontalLine from '../../../components/HorizontalLine'; +import ProjectTabs from '../../../components/projects/project/ProjectTabs'; +import { useGQLClient } from '../../../context/GQLClientContext'; const Id = () => { const { id } = useParams(); diff --git a/packages/frontend/src/pages/projects/create/Import.tsx b/packages/frontend/src/pages/org-slug/projects/create/Import.tsx similarity index 73% rename from packages/frontend/src/pages/projects/create/Import.tsx rename to packages/frontend/src/pages/org-slug/projects/create/Import.tsx index e7030b30..8ee3145e 100644 --- a/packages/frontend/src/pages/projects/create/Import.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/Import.tsx @@ -1,15 +1,16 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { Button } from '@material-tailwind/react'; -import { useOctokit } from '../../../context/OctokitContext'; -import { GitRepositoryDetails } from '../../../types/project'; -import Deploy from '../../../components/projects/create/Deploy'; -import { useGQLClient } from '../../../context/GQLClientContext'; +import { useOctokit } from '../../../../context/OctokitContext'; +import { GitRepositoryDetails } from '../../../../types/project'; +import Deploy from '../../../../components/projects/create/Deploy'; +import { useGQLClient } from '../../../../context/GQLClientContext'; const Import = () => { const [searchParams] = useSearchParams(); + const { orgSlug } = useParams(); const navigate = useNavigate(); const { octokit } = useOctokit(); const client = useGQLClient(); @@ -37,16 +38,14 @@ const Import = () => { return; } - const { addProject } = await client.addProject({ + const { addProject } = await client.addProject(orgSlug!, { // TODO: Implement form for setting project name name: `${gitRepo.owner!.login}-${gitRepo.name}`, - // TODO: Get organization id from context or URL - organizationId: String(1), prodBranch: gitRepo.default_branch ?? 'main', repository: gitRepo.full_name, }); - navigate(`/projects/create/success/${addProject.id}`); + navigate(`/${orgSlug}/projects/create/success/${addProject.id}`); }, [client, gitRepo]); return ( diff --git a/packages/frontend/src/pages/projects/create/Template.tsx b/packages/frontend/src/pages/org-slug/projects/create/Template.tsx similarity index 95% rename from packages/frontend/src/pages/projects/create/Template.tsx rename to packages/frontend/src/pages/org-slug/projects/create/Template.tsx index 04f5bbc5..87636f58 100644 --- a/packages/frontend/src/pages/projects/create/Template.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/Template.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; -import Stepper from '../../../components/Stepper'; +import Stepper from '../../../../components/Stepper'; const STEPPER_VALUES = [ { step: 1, route: '/projects/create/template', label: 'Create repository' }, diff --git a/packages/frontend/src/pages/projects/create/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/index.tsx similarity index 64% rename from packages/frontend/src/pages/projects/create/index.tsx rename to packages/frontend/src/pages/org-slug/projects/create/index.tsx index ff0d93e1..5c6aaf51 100644 --- a/packages/frontend/src/pages/projects/create/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/index.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import templateDetails from '../../../assets/templates.json'; -import TemplateCard from '../../../components/projects/create/TemplateCard'; -import RepositoryList from '../../../components/projects/create/RepositoryList'; -import ConnectAccount from '../../../components/projects/create/ConnectAccount'; -import { useOctokit } from '../../../context/OctokitContext'; +import templateDetails from '../../../../assets/templates.json'; +import TemplateCard from '../../../../components/projects/create/TemplateCard'; +import RepositoryList from '../../../../components/projects/create/RepositoryList'; +import ConnectAccount from '../../../../components/projects/create/ConnectAccount'; +import { useOctokit } from '../../../../context/OctokitContext'; const NewProject = () => { const { octokit, updateAuth } = useOctokit(); diff --git a/packages/frontend/src/pages/projects/create/routes.tsx b/packages/frontend/src/pages/org-slug/projects/create/routes.tsx similarity index 100% rename from packages/frontend/src/pages/projects/create/routes.tsx rename to packages/frontend/src/pages/org-slug/projects/create/routes.tsx diff --git a/packages/frontend/src/pages/projects/create/success/Id.tsx b/packages/frontend/src/pages/org-slug/projects/create/success/Id.tsx similarity index 95% rename from packages/frontend/src/pages/projects/create/success/Id.tsx rename to packages/frontend/src/pages/org-slug/projects/create/success/Id.tsx index 64d633b5..d37bb6f4 100644 --- a/packages/frontend/src/pages/projects/create/success/Id.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/success/Id.tsx @@ -4,7 +4,7 @@ import { Link, useParams } from 'react-router-dom'; import { Button } from '@material-tailwind/react'; const Id = () => { - const { id } = useParams(); + const { id, orgSlug } = useParams(); return (
@@ -57,7 +57,7 @@ const Id = () => {
- + diff --git a/packages/frontend/src/pages/projects/create/template/Deploy.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/Deploy.tsx similarity index 57% rename from packages/frontend/src/pages/projects/create/template/Deploy.tsx rename to packages/frontend/src/pages/org-slug/projects/create/template/Deploy.tsx index d8a21c5c..e767df0e 100644 --- a/packages/frontend/src/pages/projects/create/template/Deploy.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/template/Deploy.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import DeployComponent from '../../../../components/projects/create/Deploy'; +import DeployComponent from '../../../../../components/projects/create/Deploy'; const Deploy = () => { return ; diff --git a/packages/frontend/src/pages/projects/create/template/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx similarity index 96% rename from packages/frontend/src/pages/projects/create/template/index.tsx rename to packages/frontend/src/pages/org-slug/projects/create/template/index.tsx index 795e208d..1d0e0f5c 100644 --- a/packages/frontend/src/pages/projects/create/template/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import { Typography } from '@material-tailwind/react'; -import Dropdown from '../../../../components/Dropdown'; +import Dropdown from '../../../../../components/Dropdown'; const USER_OPTIONS = [ { value: 'saugatyadav1', label: 'saugatyadav1' }, @@ -93,7 +93,7 @@ const CreateRepo = () => {
- + diff --git a/packages/frontend/src/pages/projects/create/template/routes.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/routes.tsx similarity index 100% rename from packages/frontend/src/pages/projects/create/template/routes.tsx rename to packages/frontend/src/pages/org-slug/projects/create/template/routes.tsx diff --git a/packages/frontend/src/pages/projects/id/domain/add/Config.tsx b/packages/frontend/src/pages/org-slug/projects/id/domain/add/Config.tsx similarity index 93% rename from packages/frontend/src/pages/projects/id/domain/add/Config.tsx rename to packages/frontend/src/pages/org-slug/projects/id/domain/add/Config.tsx index ac9db74a..0e27ac03 100644 --- a/packages/frontend/src/pages/projects/id/domain/add/Config.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/domain/add/Config.tsx @@ -4,10 +4,10 @@ import toast from 'react-hot-toast'; import { Link, useParams, useSearchParams } from 'react-router-dom'; import { Typography, Alert, Button } from '@material-tailwind/react'; -import { useGQLClient } from '../../../../../context/GQLClientContext'; +import { useGQLClient } from '../../../../../../context/GQLClientContext'; const Config = () => { - const { id } = useParams(); + const { id, orgSlug } = useParams(); const client = useGQLClient(); const [searchParams] = useSearchParams(); const primaryDomainName = searchParams.get('name'); @@ -70,8 +70,7 @@ const Config = () => { ^It can take up to 48 hours for these updates to reflect globally. - - +