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:
2024-02-07 18:41:54 +05:30
committed by GitHub
co-authored by neeraj
parent 6d1a48905a
commit 413ed03eb8
37 changed files with 293 additions and 246 deletions
+51 -15
View File
@@ -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<Organization[]>([]);
const fetchUserOrganizations = useCallback(async () => {
const { organizations } = await client.getOrganizations();
setOrganizations(organizations);
}, [orgSlug]);
useEffect(() => {
fetchUserOrganizations();
setSelectedOrgSlug(orgSlug);
}, [orgSlug]);
const Sidebar = ({ organization }: { organization: Organization }) => {
return (
<div className="flex flex-col h-full p-4">
<div className="grow">
<div>
<Link to="/">
<Link to={`/${orgSlug}`}>
<h3 className="text-black text-2xl">Snowball</h3>
</Link>
</div>
<Card className="-ml-1 my-2">
<CardBody className="p-1 py-2 flex gap-2">
<div>^</div>
<div>
<Typography>{organization.name}</Typography>
<Typography>Organization</Typography>
<AsyncSelect
className="bg-white py-2"
value={selectedOrgSlug}
onChange={(value) => {
setSelectedOrgSlug(value!);
navigate(`/${value}`);
}}
selected={(_, index) => (
<div className="flex gap-2">
<div>^</div>
<div>
<span>{organizations[index!]?.name}</span>
<Typography>Organization</Typography>
</div>
</div>
</CardBody>
</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>
<NavLink
to="/"
to={`/${orgSlug}`}
className={({ isActive }) => (isActive ? 'text-blue-500' : '')}
>
<Typography>Projects</Typography>
@@ -32,7 +68,7 @@ const Sidebar = ({ organization }: { organization: Organization }) => {
</div>
<div>
<NavLink
to="/settings"
to={`/${orgSlug}/settings`}
className={({ isActive }) => (isActive ? 'text-blue-500' : '')}
>
<Typography>Settings</Typography>
@@ -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 (
@@ -34,7 +34,7 @@ const TemplateCard: React.FC<TemplateCardProps> = ({
isGitAuth,
}) => {
return isGitAuth ? (
<Link to="/projects/create/template">
<Link to="template">
<CardDetails framework={framework} />
</Link>
) : (
@@ -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
</MenuItem>
{!(deployment.environment === Environment.Production) && (
<MenuItem
onClick={() => setChangeToProduction(!changeToProduction)}
>
^ Change to production
</MenuItem>
)}
<MenuItem
onClick={() => setChangeToProduction(!changeToProduction)}
disabled={!(deployment.environment !== Environment.Production)}
>
^ Change to production
</MenuItem>
<hr className="my-3" />
<MenuItem
onClick={() => setRedeployToProduction(!redeployToProduction)}
@@ -145,14 +143,15 @@ const DeploymentDetailsCard = ({
>
^ Redeploy to production
</MenuItem>
{deployment.environment === Environment.Production && (
<MenuItem
onClick={() => setRollbackDeployment(!rollbackDeployment)}
disabled={deployment.isCurrent}
>
^ Rollback to this version
</MenuItem>
)}
<MenuItem
onClick={() => setRollbackDeployment(!rollbackDeployment)}
disabled={
deployment.isCurrent ||
deployment.environment !== Environment.Production
}
>
^ Rollback to this version
</MenuItem>
</MenuList>
</Menu>
</div>
@@ -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');
}