forked from cerc-io/snowballtools-base
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 <neeraj.rtly@gmail.com>
This commit is contained in:
parent
6d1a48905a
commit
413ed03eb8
@ -56,6 +56,13 @@ export class Database {
|
|||||||
return updateResult.affected > 0;
|
return updateResult.affected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getOrganization (options: FindOneOptions<Organization>): Promise<Organization | null> {
|
||||||
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
|
const organization = await organizationRepository.findOne(options);
|
||||||
|
|
||||||
|
return organization;
|
||||||
|
}
|
||||||
|
|
||||||
async getOrganizationsByUserId (userId: string): Promise<Organization[]> {
|
async getOrganizationsByUserId (userId: string): Promise<Organization[]> {
|
||||||
const organizationRepository = this.dataSource.getRepository(Organization);
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
|
|
||||||
@ -108,7 +115,7 @@ export class Database {
|
|||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsInOrganization (userId: string, organizationId: string): Promise<Project[]> {
|
async getProjectsInOrganization (userId: string, organizationSlug: string): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
const projects = await projectRepository
|
const projects = await projectRepository
|
||||||
@ -116,9 +123,10 @@ export class Database {
|
|||||||
.leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true')
|
.leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true')
|
||||||
.leftJoinAndSelect('deployments.domain', 'domain')
|
.leftJoinAndSelect('deployments.domain', 'domain')
|
||||||
.leftJoin('project.projectMembers', 'projectMembers')
|
.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,
|
userId,
|
||||||
organizationId
|
organizationSlug
|
||||||
})
|
})
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
@ -297,7 +305,7 @@ export class Database {
|
|||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProject (userId: string, projectDetails: DeepPartial<Project>): Promise<Project> {
|
async addProject (userId: string, organizationId: string, projectDetails: DeepPartial<Project>): Promise<Project> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
// TODO: Check if organization exists
|
// TODO: Check if organization exists
|
||||||
@ -312,7 +320,7 @@ export class Database {
|
|||||||
});
|
});
|
||||||
|
|
||||||
newProject.organization = Object.assign(new Organization(), {
|
newProject.organization = Object.assign(new Organization(), {
|
||||||
id: projectDetails.organizationId
|
id: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
newProject.subDomain = `${newProject.name}.${PROJECT_DOMAIN}`;
|
newProject.subDomain = `${newProject.name}.${PROJECT_DOMAIN}`;
|
||||||
|
@ -4,11 +4,13 @@ import {
|
|||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
OneToMany
|
OneToMany,
|
||||||
|
Unique
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { UserOrganization } from './UserOrganization';
|
import { UserOrganization } from './UserOrganization';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@Unique(['slug'])
|
||||||
export class Organization {
|
export class Organization {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
@ -16,6 +18,9 @@ export class Organization {
|
|||||||
@Column('varchar', { length: 255 })
|
@Column('varchar', { length: 255 })
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
|
@Column('varchar')
|
||||||
|
slug!: string;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv
|
|||||||
return service.getProjectById(projectId);
|
return service.getProjectById(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
projectsInOrganization: async (_: any, { organizationId }: {organizationId: string }, context: any) => {
|
projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => {
|
||||||
return service.getProjectsInOrganization(context.userId, organizationId);
|
return service.getProjectsInOrganization(context.userId, organizationSlug);
|
||||||
},
|
},
|
||||||
|
|
||||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
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<Project> }, context: any) => {
|
addProject: async (_: any, { organizationSlug, data }: { organizationSlug: string, data: DeepPartial<Project> }, context: any) => {
|
||||||
try {
|
try {
|
||||||
return service.addProject(context.userId, data);
|
return service.addProject(context.userId, organizationSlug, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ type User {
|
|||||||
type Organization {
|
type Organization {
|
||||||
id: String!
|
id: String!
|
||||||
name: String!
|
name: String!
|
||||||
|
slug: String!
|
||||||
projects: [Project!]
|
projects: [Project!]
|
||||||
createdAt: String!
|
createdAt: String!
|
||||||
updatedAt: String!
|
updatedAt: String!
|
||||||
@ -129,7 +130,6 @@ input AddEnvironmentVariableInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input AddProjectInput {
|
input AddProjectInput {
|
||||||
organizationId: String!
|
|
||||||
name: String!
|
name: String!
|
||||||
repository: String!
|
repository: String!
|
||||||
prodBranch: String!
|
prodBranch: String!
|
||||||
@ -176,7 +176,7 @@ type Query {
|
|||||||
user: User!
|
user: User!
|
||||||
organizations: [Organization!]
|
organizations: [Organization!]
|
||||||
projects: [Project!]
|
projects: [Project!]
|
||||||
projectsInOrganization(organizationId: String!): [Project!]
|
projectsInOrganization(organizationSlug: String!): [Project!]
|
||||||
project(projectId: String!): Project
|
project(projectId: String!): Project
|
||||||
deployments(projectId: String!): [Deployment!]
|
deployments(projectId: String!): [Deployment!]
|
||||||
environmentVariables(projectId: String!): [EnvironmentVariable!]
|
environmentVariables(projectId: String!): [EnvironmentVariable!]
|
||||||
@ -193,7 +193,7 @@ type Mutation {
|
|||||||
updateEnvironmentVariable(environmentVariableId: String!, data: UpdateEnvironmentVariableInput!): Boolean!
|
updateEnvironmentVariable(environmentVariableId: String!, data: UpdateEnvironmentVariableInput!): Boolean!
|
||||||
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
||||||
updateDeploymentToProd(deploymentId: String!): Boolean!
|
updateDeploymentToProd(deploymentId: String!): Boolean!
|
||||||
addProject(data: AddProjectInput): Project!
|
addProject(organizationSlug: String!, data: AddProjectInput): Project!
|
||||||
updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean!
|
updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean!
|
||||||
redeployToProd(deploymentId: String!): Boolean!
|
redeployToProd(deploymentId: String!): Boolean!
|
||||||
deleteProject(projectId: String!): Boolean!
|
deleteProject(projectId: String!): Boolean!
|
||||||
|
@ -40,8 +40,8 @@ export class Service {
|
|||||||
return dbProject;
|
return dbProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsInOrganization (userId:string, organizationId: string): Promise<Project[]> {
|
async getProjectsInOrganization (userId:string, organizationSlug: string): Promise<Project[]> {
|
||||||
const dbProjects = await this.db.getProjectsInOrganization(userId, organizationId);
|
const dbProjects = await this.db.getProjectsInOrganization(userId, organizationSlug);
|
||||||
return dbProjects;
|
return dbProjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,8 +182,17 @@ export class Service {
|
|||||||
return updateResult;
|
return updateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProject (userId: string, data: DeepPartial<Project>): Promise<Project | undefined> {
|
async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> {
|
||||||
return this.db.addProject(userId, data);
|
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<Project>): Promise<boolean> {
|
async updateProject (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "Snowball Tools"
|
"name": "Snowball Tools",
|
||||||
|
"slug": "snowball-tools"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AirFoil"
|
"name": "AirFoil",
|
||||||
|
"slug": "airfoil"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
import DashboardLayout from './layouts/Dashboard';
|
import OrgSlug from './pages/OrgSlug';
|
||||||
import Home from './pages/index';
|
import Projects from './pages/org-slug';
|
||||||
import Settings from './pages/Settings';
|
import Settings from './pages/org-slug/Settings';
|
||||||
import {
|
import {
|
||||||
projectsRoutesWithSearch,
|
projectsRoutesWithSearch,
|
||||||
projectsRoutesWithoutSearch,
|
projectsRoutesWithoutSearch,
|
||||||
} from './pages/projects/routes';
|
} from './pages/org-slug/projects/routes';
|
||||||
import ProjectSearchLayout from './layouts/ProjectSearch';
|
import ProjectSearchLayout from './layouts/ProjectSearch';
|
||||||
import { OctokitProvider } from './context/OctokitContext';
|
import { OctokitProvider } from './context/OctokitContext';
|
||||||
|
import Index from './pages';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
element: <DashboardLayout />,
|
path: ':orgSlug',
|
||||||
|
element: <OrgSlug />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
element: <ProjectSearchLayout />,
|
element: <ProjectSearchLayout />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '',
|
||||||
element: <Home />,
|
element: <Projects />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'projects',
|
path: 'projects',
|
||||||
@ -38,6 +40,10 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Index />,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
@ -1,30 +1,66 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Link, NavLink } from 'react-router-dom';
|
import { Link, NavLink, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Organization } from 'gql-client';
|
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<Organization[]>([]);
|
||||||
|
|
||||||
|
const fetchUserOrganizations = useCallback(async () => {
|
||||||
|
const { organizations } = await client.getOrganizations();
|
||||||
|
setOrganizations(organizations);
|
||||||
|
}, [orgSlug]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserOrganizations();
|
||||||
|
setSelectedOrgSlug(orgSlug);
|
||||||
|
}, [orgSlug]);
|
||||||
|
|
||||||
const Sidebar = ({ organization }: { organization: Organization }) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full p-4">
|
<div className="flex flex-col h-full p-4">
|
||||||
<div className="grow">
|
<div className="grow">
|
||||||
<div>
|
<div>
|
||||||
<Link to="/">
|
<Link to={`/${orgSlug}`}>
|
||||||
<h3 className="text-black text-2xl">Snowball</h3>
|
<h3 className="text-black text-2xl">Snowball</h3>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Card className="-ml-1 my-2">
|
<AsyncSelect
|
||||||
<CardBody className="p-1 py-2 flex gap-2">
|
className="bg-white py-2"
|
||||||
|
value={selectedOrgSlug}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSelectedOrgSlug(value!);
|
||||||
|
navigate(`/${value}`);
|
||||||
|
}}
|
||||||
|
selected={(_, index) => (
|
||||||
|
<div className="flex gap-2">
|
||||||
<div>^</div>
|
<div>^</div>
|
||||||
<div>
|
<div>
|
||||||
<Typography>{organization.name}</Typography>
|
<span>{organizations[index!]?.name}</span>
|
||||||
<Typography>Organization</Typography>
|
<Typography>Organization</Typography>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</div>
|
||||||
</Card>
|
)}
|
||||||
|
>
|
||||||
|
{/* TODO: Show label organization and manage in option */}
|
||||||
|
{organizations.map((org) => (
|
||||||
|
<Option key={org.id} value={org.slug}>
|
||||||
|
^ {org.name}
|
||||||
|
{org.slug === selectedOrgSlug && <p className="float-right">^</p>}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</AsyncSelect>
|
||||||
<div>
|
<div>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/"
|
to={`/${orgSlug}`}
|
||||||
className={({ isActive }) => (isActive ? 'text-blue-500' : '')}
|
className={({ isActive }) => (isActive ? 'text-blue-500' : '')}
|
||||||
>
|
>
|
||||||
<Typography>Projects</Typography>
|
<Typography>Projects</Typography>
|
||||||
@ -32,7 +68,7 @@ const Sidebar = ({ organization }: { organization: Organization }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/settings"
|
to={`/${orgSlug}/settings`}
|
||||||
className={({ isActive }) => (isActive ? 'text-blue-500' : '')}
|
className={({ isActive }) => (isActive ? 'text-blue-500' : '')}
|
||||||
>
|
>
|
||||||
<Typography>Settings</Typography>
|
<Typography>Settings</Typography>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback } from 'react';
|
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';
|
import { Button, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
@ -11,9 +11,10 @@ const Deploy = () => {
|
|||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const handleOpen = () => setOpen(!open);
|
const handleOpen = () => setOpen(!open);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { orgSlug } = useParams();
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
navigate('/projects/create');
|
navigate(`/${orgSlug}/projects/create`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -34,7 +34,7 @@ const TemplateCard: React.FC<TemplateCardProps> = ({
|
|||||||
isGitAuth,
|
isGitAuth,
|
||||||
}) => {
|
}) => {
|
||||||
return isGitAuth ? (
|
return isGitAuth ? (
|
||||||
<Link to="/projects/create/template">
|
<Link to="template">
|
||||||
<CardDetails framework={framework} />
|
<CardDetails framework={framework} />
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { Environment, Project, Domain, DeploymentStatus } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Menu,
|
Menu,
|
||||||
@ -9,14 +11,12 @@ import {
|
|||||||
Chip,
|
Chip,
|
||||||
ChipProps,
|
ChipProps,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { Environment, Project, Domain } from 'gql-client';
|
|
||||||
|
|
||||||
import { relativeTimeMs } from '../../../../utils/time';
|
import { relativeTimeMs } from '../../../../utils/time';
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
import DeploymentDialogBodyCard from './DeploymentDialogBodyCard';
|
import DeploymentDialogBodyCard from './DeploymentDialogBodyCard';
|
||||||
import AssignDomainDialog from './AssignDomainDialog';
|
import AssignDomainDialog from './AssignDomainDialog';
|
||||||
import { DeploymentDetails, Status } from '../../../../types/project';
|
import { DeploymentDetails } from '../../../../types/project';
|
||||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
|
||||||
interface DeployDetailsCardProps {
|
interface DeployDetailsCardProps {
|
||||||
@ -27,10 +27,10 @@ interface DeployDetailsCardProps {
|
|||||||
prodBranchDomains: Domain[];
|
prodBranchDomains: Domain[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_COLORS: { [key in Status]: ChipProps['color'] } = {
|
const STATUS_COLORS: { [key in DeploymentStatus]: ChipProps['color'] } = {
|
||||||
[Status.BUILDING]: 'blue',
|
[DeploymentStatus.Building]: 'blue',
|
||||||
[Status.READY]: 'green',
|
[DeploymentStatus.Ready]: 'green',
|
||||||
[Status.ERROR]: 'red',
|
[DeploymentStatus.Error]: 'red',
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeploymentDetailsCard = ({
|
const DeploymentDetailsCard = ({
|
||||||
@ -125,14 +125,12 @@ const DeploymentDetailsCard = ({
|
|||||||
>
|
>
|
||||||
^ Assign domain
|
^ Assign domain
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{!(deployment.environment === Environment.Production) && (
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setChangeToProduction(!changeToProduction)}
|
onClick={() => setChangeToProduction(!changeToProduction)}
|
||||||
|
disabled={!(deployment.environment !== Environment.Production)}
|
||||||
>
|
>
|
||||||
^ Change to production
|
^ Change to production
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
|
||||||
|
|
||||||
<hr className="my-3" />
|
<hr className="my-3" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setRedeployToProduction(!redeployToProduction)}
|
onClick={() => setRedeployToProduction(!redeployToProduction)}
|
||||||
@ -145,14 +143,15 @@ const DeploymentDetailsCard = ({
|
|||||||
>
|
>
|
||||||
^ Redeploy to production
|
^ Redeploy to production
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{deployment.environment === Environment.Production && (
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setRollbackDeployment(!rollbackDeployment)}
|
onClick={() => setRollbackDeployment(!rollbackDeployment)}
|
||||||
disabled={deployment.isCurrent}
|
disabled={
|
||||||
|
deployment.isCurrent ||
|
||||||
|
deployment.environment !== Environment.Production
|
||||||
|
}
|
||||||
>
|
>
|
||||||
^ Rollback to this version
|
^ Rollback to this version
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback } from 'react';
|
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 { useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { Project } from 'gql-client';
|
import { Project } from 'gql-client';
|
||||||
@ -26,6 +26,7 @@ const DeleteProjectDialog = ({
|
|||||||
handleOpen,
|
handleOpen,
|
||||||
project,
|
project,
|
||||||
}: DeleteProjectDialogProp) => {
|
}: DeleteProjectDialogProp) => {
|
||||||
|
const { orgSlug } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ const DeleteProjectDialog = ({
|
|||||||
const { deleteProject } = await client.deleteProject(project.id);
|
const { deleteProject } = await client.deleteProject(project.id);
|
||||||
|
|
||||||
if (deleteProject) {
|
if (deleteProject) {
|
||||||
navigate('/');
|
navigate(`/${orgSlug}`);
|
||||||
} else {
|
} else {
|
||||||
toast.error('Project not deleted');
|
toast.error('Project not deleted');
|
||||||
}
|
}
|
||||||
|
@ -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<Organization[]>([]);
|
|
||||||
|
|
||||||
const fetchUserOrganizations = useCallback(async () => {
|
|
||||||
const { organizations } = await client.getOrganizations();
|
|
||||||
setOrganizations(organizations);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchUserOrganizations();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-5 h-screen bg-light-blue-50">
|
|
||||||
{organizations.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div className="h-full">
|
|
||||||
<Sidebar organization={organizations[USER_ORGANIZATION_INDEX]} />
|
|
||||||
</div>
|
|
||||||
<div className="col-span-4 h-full p-3 overflow-y-hidden">
|
|
||||||
<div className="bg-white rounded-3xl h-full overflow-y-auto">
|
|
||||||
<Outlet context={organizations[USER_ORGANIZATION_INDEX]} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Dashboard;
|
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Outlet, useNavigate, useOutletContext } from 'react-router-dom';
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
import { Organization } from 'gql-client';
|
|
||||||
|
|
||||||
import { IconButton, Typography } from '@material-tailwind/react';
|
import { IconButton, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
|||||||
|
|
||||||
const ProjectSearch = () => {
|
const ProjectSearch = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const organization = useOutletContext<Organization>();
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="sticky top-0 bg-white z-30">
|
<div className="sticky top-0 bg-white z-30">
|
||||||
@ -17,7 +16,9 @@ const ProjectSearch = () => {
|
|||||||
<div className="grow mr-2">
|
<div className="grow mr-2">
|
||||||
<ProjectSearchBar
|
<ProjectSearchBar
|
||||||
onChange={(project) => {
|
onChange={(project) => {
|
||||||
navigate(`/projects/${project.id}`);
|
navigate(
|
||||||
|
`/${project.organization.slug}/projects/${project.id}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +35,7 @@ const ProjectSearch = () => {
|
|||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
</div>
|
</div>
|
||||||
<div className="z-0">
|
<div className="z-0">
|
||||||
<Outlet context={organization} />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
23
packages/frontend/src/pages/OrgSlug.tsx
Normal file
23
packages/frontend/src/pages/OrgSlug.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Sidebar from '../components/Sidebar';
|
||||||
|
|
||||||
|
const OrgSlug = () => {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-5 h-screen bg-light-blue-50">
|
||||||
|
<>
|
||||||
|
<div className="h-full">
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-4 h-full p-3 overflow-y-hidden">
|
||||||
|
<div className="bg-white rounded-3xl h-full overflow-y-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrgSlug;
|
@ -1,69 +1,31 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
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 { Organization } from 'gql-client';
|
||||||
|
|
||||||
import { Button, Typography, Chip } from '@material-tailwind/react';
|
const Index = () => {
|
||||||
|
|
||||||
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 client = useGQLClient();
|
||||||
const organization = useOutletContext<Organization>();
|
const [organization, setOrganization] = useState<Organization>();
|
||||||
|
|
||||||
const [projects, setProjects] = useState<ProjectDetails[]>([]);
|
const fetchUserOrganizations = useCallback(async () => {
|
||||||
|
const { organizations } = await client.getOrganizations();
|
||||||
const fetchProjects = useCallback(async () => {
|
// By default information of first organization displayed
|
||||||
const { projectsInOrganization } = await client.getProjectsInOrganization(
|
setOrganization(organizations[0]);
|
||||||
organization.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedProjects = projectsInOrganization.map((project) => {
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
// TODO: Populate from github API
|
|
||||||
latestCommit: COMMIT_DETAILS,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
setProjects(updatedProjects);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProjects();
|
fetchUserOrganizations();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<div className="flex p-5">
|
{Boolean(organization) ? (
|
||||||
<div className="grow">
|
<Navigate to={organization!.slug} />
|
||||||
<div className="flex gap-2 items-center">
|
) : (
|
||||||
<Typography variant="h4">Projects</Typography>
|
<>Loading</>
|
||||||
<Chip
|
)}
|
||||||
className="bg-gray-300 rounded-full static"
|
</>
|
||||||
value={projects.length}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Link to="/projects/create">
|
|
||||||
<Button className="rounded-full" color="blue">
|
|
||||||
Create project
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-3 gap-5 p-5">
|
|
||||||
{projects.length !== 0 &&
|
|
||||||
projects.map((project, key) => {
|
|
||||||
return <ProjectCard project={project} key={key} />;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Projects;
|
export default Index;
|
||||||
|
67
packages/frontend/src/pages/org-slug/index.tsx
Normal file
67
packages/frontend/src/pages/org-slug/index.tsx
Normal file
@ -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<ProjectDetails[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<div className="flex p-5">
|
||||||
|
<div className="grow">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<Typography variant="h4">Projects</Typography>
|
||||||
|
<Chip
|
||||||
|
className="bg-gray-300 rounded-full static"
|
||||||
|
value={projects.length}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Link to="projects/create">
|
||||||
|
<Button className="rounded-full" color="blue">
|
||||||
|
Create project
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-5 p-5">
|
||||||
|
{projects.length !== 0 &&
|
||||||
|
projects.map((project, key) => {
|
||||||
|
return <ProjectCard project={project} key={key} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Projects;
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
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 { IconButton } from '@material-tailwind/react';
|
||||||
|
|
||||||
import HorizontalLine from '../../components/HorizontalLine';
|
import HorizontalLine from '../../../components/HorizontalLine';
|
||||||
|
|
||||||
const CreateProject = () => {
|
const CreateProject = () => {
|
||||||
|
const { orgSlug } = useParams();
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<div className="flex p-4 items-center">
|
<div className="flex p-4 items-center">
|
||||||
@ -13,7 +14,7 @@ const CreateProject = () => {
|
|||||||
<h3 className="text-gray-750 text-2xl">Create new project</h3>
|
<h3 className="text-gray-750 text-2xl">Create new project</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Link to="/">
|
<Link to={`/${orgSlug}`}>
|
||||||
<IconButton className="rounded-full" variant="outlined">
|
<IconButton className="rounded-full" variant="outlined">
|
||||||
X
|
X
|
||||||
</IconButton>
|
</IconButton>
|
@ -4,9 +4,9 @@ import { Project as ProjectType } from 'gql-client';
|
|||||||
|
|
||||||
import { Button, Typography } from '@material-tailwind/react';
|
import { Button, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import HorizontalLine from '../../components/HorizontalLine';
|
import HorizontalLine from '../../../components/HorizontalLine';
|
||||||
import ProjectTabs from '../../components/projects/project/ProjectTabs';
|
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();
|
@ -1,15 +1,16 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
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 { Button } from '@material-tailwind/react';
|
||||||
|
|
||||||
import { useOctokit } from '../../../context/OctokitContext';
|
import { useOctokit } from '../../../../context/OctokitContext';
|
||||||
import { GitRepositoryDetails } from '../../../types/project';
|
import { GitRepositoryDetails } from '../../../../types/project';
|
||||||
import Deploy from '../../../components/projects/create/Deploy';
|
import Deploy from '../../../../components/projects/create/Deploy';
|
||||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
|
||||||
const Import = () => {
|
const Import = () => {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
const { orgSlug } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { octokit } = useOctokit();
|
const { octokit } = useOctokit();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
@ -37,16 +38,14 @@ const Import = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { addProject } = await client.addProject({
|
const { addProject } = await client.addProject(orgSlug!, {
|
||||||
// TODO: Implement form for setting project name
|
// TODO: Implement form for setting project name
|
||||||
name: `${gitRepo.owner!.login}-${gitRepo.name}`,
|
name: `${gitRepo.owner!.login}-${gitRepo.name}`,
|
||||||
// TODO: Get organization id from context or URL
|
|
||||||
organizationId: String(1),
|
|
||||||
prodBranch: gitRepo.default_branch ?? 'main',
|
prodBranch: gitRepo.default_branch ?? 'main',
|
||||||
repository: gitRepo.full_name,
|
repository: gitRepo.full_name,
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate(`/projects/create/success/${addProject.id}`);
|
navigate(`/${orgSlug}/projects/create/success/${addProject.id}`);
|
||||||
}, [client, gitRepo]);
|
}, [client, gitRepo]);
|
||||||
|
|
||||||
return (
|
return (
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import Stepper from '../../../components/Stepper';
|
import Stepper from '../../../../components/Stepper';
|
||||||
|
|
||||||
const STEPPER_VALUES = [
|
const STEPPER_VALUES = [
|
||||||
{ step: 1, route: '/projects/create/template', label: 'Create repository' },
|
{ step: 1, route: '/projects/create/template', label: 'Create repository' },
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import templateDetails from '../../../assets/templates.json';
|
import templateDetails from '../../../../assets/templates.json';
|
||||||
import TemplateCard from '../../../components/projects/create/TemplateCard';
|
import TemplateCard from '../../../../components/projects/create/TemplateCard';
|
||||||
import RepositoryList from '../../../components/projects/create/RepositoryList';
|
import RepositoryList from '../../../../components/projects/create/RepositoryList';
|
||||||
import ConnectAccount from '../../../components/projects/create/ConnectAccount';
|
import ConnectAccount from '../../../../components/projects/create/ConnectAccount';
|
||||||
import { useOctokit } from '../../../context/OctokitContext';
|
import { useOctokit } from '../../../../context/OctokitContext';
|
||||||
|
|
||||||
const NewProject = () => {
|
const NewProject = () => {
|
||||||
const { octokit, updateAuth } = useOctokit();
|
const { octokit, updateAuth } = useOctokit();
|
@ -4,7 +4,7 @@ import { Link, useParams } from 'react-router-dom';
|
|||||||
import { Button } from '@material-tailwind/react';
|
import { Button } from '@material-tailwind/react';
|
||||||
|
|
||||||
const Id = () => {
|
const Id = () => {
|
||||||
const { id } = useParams();
|
const { id, orgSlug } = useParams();
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
@ -57,7 +57,7 @@ const Id = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Link to={`/projects/${id}`}>
|
<Link to={`/${orgSlug}/projects/${id}`}>
|
||||||
<Button className="rounded-full" variant="gradient" color="blue">
|
<Button className="rounded-full" variant="gradient" color="blue">
|
||||||
View project
|
View project
|
||||||
</Button>
|
</Button>
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DeployComponent from '../../../../components/projects/create/Deploy';
|
import DeployComponent from '../../../../../components/projects/create/Deploy';
|
||||||
|
|
||||||
const Deploy = () => {
|
const Deploy = () => {
|
||||||
return <DeployComponent />;
|
return <DeployComponent />;
|
@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Typography } from '@material-tailwind/react';
|
import { Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import Dropdown from '../../../../components/Dropdown';
|
import Dropdown from '../../../../../components/Dropdown';
|
||||||
|
|
||||||
const USER_OPTIONS = [
|
const USER_OPTIONS = [
|
||||||
{ value: 'saugatyadav1', label: 'saugatyadav1' },
|
{ value: 'saugatyadav1', label: 'saugatyadav1' },
|
||||||
@ -93,7 +93,7 @@ const CreateRepo = () => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<Link to={'/projects/create/template/deploy'}>
|
<Link to="deploy">
|
||||||
<button className="bg-blue-500 rounded-xl p-2" type="submit">
|
<button className="bg-blue-500 rounded-xl p-2" type="submit">
|
||||||
Deploy ^
|
Deploy ^
|
||||||
</button>
|
</button>
|
@ -4,10 +4,10 @@ import toast from 'react-hot-toast';
|
|||||||
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { Typography, Alert, Button } from '@material-tailwind/react';
|
import { Typography, Alert, Button } from '@material-tailwind/react';
|
||||||
|
|
||||||
import { useGQLClient } from '../../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../../../context/GQLClientContext';
|
||||||
|
|
||||||
const Config = () => {
|
const Config = () => {
|
||||||
const { id } = useParams();
|
const { id, orgSlug } = useParams();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const primaryDomainName = searchParams.get('name');
|
const primaryDomainName = searchParams.get('name');
|
||||||
@ -70,8 +70,7 @@ const Config = () => {
|
|||||||
<i>^</i>It can take up to 48 hours for these updates to reflect
|
<i>^</i>It can take up to 48 hours for these updates to reflect
|
||||||
globally.
|
globally.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
<Link to={`/${orgSlug}/projects/${id}`}>
|
||||||
<Link to={`/projects/${id}`}>
|
|
||||||
<Button
|
<Button
|
||||||
className="w-fit"
|
className="w-fit"
|
||||||
color="blue"
|
color="blue"
|
@ -2,10 +2,10 @@ import React, { useMemo } from 'react';
|
|||||||
import { useParams, useLocation, Outlet, Link } from 'react-router-dom';
|
import { useParams, useLocation, Outlet, Link } from 'react-router-dom';
|
||||||
import { Typography, IconButton } from '@material-tailwind/react';
|
import { Typography, IconButton } from '@material-tailwind/react';
|
||||||
|
|
||||||
import Stepper from '../../../../../components/Stepper';
|
import Stepper from '../../../../../../components/Stepper';
|
||||||
|
|
||||||
const AddDomain = () => {
|
const AddDomain = () => {
|
||||||
const { id } = useParams();
|
const { id, orgSlug } = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const stepperValues = [
|
const stepperValues = [
|
||||||
@ -31,7 +31,7 @@ const AddDomain = () => {
|
|||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Typography variant="h3">Add Domain</Typography>
|
<Typography variant="h3">Add Domain</Typography>
|
||||||
<Link to={`/projects/${id}`}>
|
<Link to={`/${orgSlug}/projects/${id}`}>
|
||||||
<IconButton className="rounded-full" variant="outlined">
|
<IconButton className="rounded-full" variant="outlined">
|
||||||
X
|
X
|
||||||
</IconButton>
|
</IconButton>
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Config from './Config';
|
import Config from './Config';
|
||||||
import SetupDomain from '../../../../../components/projects/project/settings/SetupDomain';
|
import SetupDomain from '../../../../../../components/projects/project/settings/SetupDomain';
|
||||||
|
|
||||||
export const addDomainRoutes = [
|
export const addDomainRoutes = [
|
||||||
{
|
{
|
@ -2,10 +2,6 @@ import { Project, Deployment } from 'gql-client';
|
|||||||
|
|
||||||
export interface ProjectDetails extends Project {
|
export interface ProjectDetails extends Project {
|
||||||
latestCommit: Commit;
|
latestCommit: Commit;
|
||||||
|
|
||||||
// TODO: Move out of project
|
|
||||||
repositories?: RepositoryDetails[];
|
|
||||||
repositoryId?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeploymentDetails extends Deployment {
|
export interface DeploymentDetails extends Deployment {
|
||||||
@ -13,12 +9,6 @@ export interface DeploymentDetails extends Deployment {
|
|||||||
author: string;
|
author: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Status {
|
|
||||||
BUILDING = 'Building',
|
|
||||||
READY = 'Ready',
|
|
||||||
ERROR = 'Error',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GitOrgDetails {
|
export interface GitOrgDetails {
|
||||||
id: number;
|
id: number;
|
||||||
login: string;
|
login: string;
|
||||||
@ -65,27 +55,6 @@ export enum GitSelect {
|
|||||||
NONE = 'none',
|
NONE = 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DomainStatus {
|
|
||||||
LIVE = 'Live',
|
|
||||||
PENDING = 'Pending',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DomainDetails {
|
|
||||||
id: string;
|
|
||||||
projectid: string;
|
|
||||||
name: string;
|
|
||||||
status: DomainStatus;
|
|
||||||
record: {
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectSearchOutletContext {
|
|
||||||
projects: ProjectDetails[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Commit {
|
export interface Commit {
|
||||||
message: string;
|
message: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
@ -50,11 +50,11 @@ export class GQLClient {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsInOrganization (organizationId: string) : Promise<GetProjectsInOrganizationResponse> {
|
async getProjectsInOrganization (organizationSlug: string) : Promise<GetProjectsInOrganizationResponse> {
|
||||||
const { data } = await this.client.query({
|
const { data } = await this.client.query({
|
||||||
query: getProjectsInOrganization,
|
query: getProjectsInOrganization,
|
||||||
variables: {
|
variables: {
|
||||||
organizationId
|
organizationSlug
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -194,10 +194,11 @@ export class GQLClient {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProject (data: AddProjectInput): Promise<AddProjectResponse> {
|
async addProject (organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse> {
|
||||||
const result = await this.client.mutate({
|
const result = await this.client.mutate({
|
||||||
mutation: addProject,
|
mutation: addProject,
|
||||||
variables: {
|
variables: {
|
||||||
|
organizationSlug,
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -43,8 +43,8 @@ mutation ($deploymentId: String!) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const addProject = gql`
|
export const addProject = gql`
|
||||||
mutation ($data: AddProjectInput) {
|
mutation ($organizationSlug: String!, $data: AddProjectInput) {
|
||||||
addProject(data: $data) {
|
addProject(organizationSlug: $organizationSlug, data: $data) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
@ -65,8 +65,8 @@ query ($projectId: String!) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const getProjectsInOrganization = gql`
|
export const getProjectsInOrganization = gql`
|
||||||
query ($organizationId: String!) {
|
query ($organizationSlug: String!) {
|
||||||
projectsInOrganization(organizationId: $organizationId) {
|
projectsInOrganization(organizationSlug: $organizationSlug) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
createdAt
|
createdAt
|
||||||
@ -106,6 +106,7 @@ query {
|
|||||||
organizations {
|
organizations {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
slug
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
@ -191,6 +192,7 @@ query ($searchText: String!) {
|
|||||||
organization {
|
organization {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
slug
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ export type OrganizationProject = {
|
|||||||
export type Organization = {
|
export type Organization = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
slug: string
|
||||||
projects: OrganizationProject[]
|
projects: OrganizationProject[]
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
@ -240,7 +241,6 @@ export type DeleteDomainResponse = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AddProjectInput = {
|
export type AddProjectInput = {
|
||||||
organizationId: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
repository: string;
|
repository: string;
|
||||||
prodBranch: string;
|
prodBranch: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user