Add dialog for editing domains in project settings tab (#38)

* Implement edit domain dialog

* Pass project and repo from domain component

* Fix dialog states in domain card

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-01-08 10:48:07 +05:30 committed by GitHub
parent 2cb1feedcb
commit 66aa8fed4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 236 additions and 33 deletions

View File

@ -4,7 +4,8 @@
"projectid": 1, "projectid": 1,
"name": "randomurl.snowballtools.xyz", "name": "randomurl.snowballtools.xyz",
"status": "live", "status": "live",
"record": null "record": null,
"isRedirectedto": false
}, },
{ {
"id": 2, "id": 2,
@ -15,7 +16,8 @@
"type": "A", "type": "A",
"name": "@", "name": "@",
"value": "56.49.19.21" "value": "56.49.19.21"
} },
"isRedirectedto": false
}, },
{ {
"id": 3, "id": 3,
@ -26,6 +28,7 @@
"type": "CNAME", "type": "CNAME",
"name": "www", "name": "www",
"value": "cname.snowballtools.xyz" "value": "cname.snowballtools.xyz"
} },
"isRedirectedto": true
} }
] ]

View File

@ -15,7 +15,8 @@
"message": "subscription added", "message": "subscription added",
"createdAt": "2023-12-11T04:20:00", "createdAt": "2023-12-11T04:20:00",
"branch": "main" "branch": "main"
} },
"repositoryId": 1
}, },
{ {
"id": 2, "id": 2,
@ -33,7 +34,8 @@
"message": "component updates", "message": "component updates",
"createdAt": "2023-12-11T04:20:00", "createdAt": "2023-12-11T04:20:00",
"branch": "staging" "branch": "staging"
} },
"repositoryId": 1
}, },
{ {
"id": 3, "id": 3,
@ -51,7 +53,8 @@
"message": "No repo connected", "message": "No repo connected",
"createdAt": "2023-12-01T04:20:00", "createdAt": "2023-12-01T04:20:00",
"branch": "main" "branch": "main"
} },
"repositoryId": 1
}, },
{ {
"id": 4, "id": 4,
@ -69,7 +72,8 @@
"message": "hello world", "message": "hello world",
"createdAt": "2023-12-01T04:20:00", "createdAt": "2023-12-01T04:20:00",
"branch": "main" "branch": "main"
} },
"repositoryId": 1
}, },
{ {
"id": 5, "id": 5,
@ -87,7 +91,8 @@
"message": "404 added", "message": "404 added",
"createdAt": "2023-12-09T04:20:00", "createdAt": "2023-12-09T04:20:00",
"branch": "main" "branch": "main"
} },
"repositoryId": 1
}, },
{ {
"id": 6, "id": 6,
@ -105,6 +110,7 @@
"message": "design system integrated", "message": "design system integrated",
"createdAt": "2023-12-09T04:20:00", "createdAt": "2023-12-09T04:20:00",
"branch": "prod" "branch": "prod"
} },
"repositoryId": 1
} }
] ]

View File

@ -4,34 +4,39 @@
"title": "project-101", "title": "project-101",
"updatedAt": "2023-12-21T08:30:00", "updatedAt": "2023-12-21T08:30:00",
"user": "bob", "user": "bob",
"private": false "private": false,
"branch": ["main", "prod", "dev"]
}, },
{ {
"id": 2, "id": 2,
"title": "project-102", "title": "project-102",
"updatedAt": "2023-12-21T08:30:00", "updatedAt": "2023-12-21T08:30:00",
"user": "alice", "user": "alice",
"private": true "private": true,
"branch": ["main", "prod", "dev"]
}, },
{ {
"id": 3, "id": 3,
"title": "project-103", "title": "project-103",
"updatedAt": "2023-12-21T04:20:00", "updatedAt": "2023-12-21T04:20:00",
"user": "charlie", "user": "charlie",
"private": false "private": false,
"branch": ["main", "prod", "dev"]
}, },
{ {
"id": 4, "id": 4,
"title": "project-104", "title": "project-104",
"updatedAt": "2023-12-21T04:27:00", "updatedAt": "2023-12-21T04:27:00",
"user": "alice", "user": "alice",
"private": false "private": false,
"branch": ["main", "prod", "dev"]
}, },
{ {
"id": 5, "id": 5,
"title": "project-105", "title": "project-105",
"updatedAt": "2023-12-21T04:41:00", "updatedAt": "2023-12-21T04:41:00",
"user": "ivan", "user": "ivan",
"private": false "private": false,
"branch": ["main", "prod", "dev"]
} }
] ]

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { import {
Chip, Chip,
Typography, Typography,
@ -11,9 +11,14 @@ import {
Card, Card,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import { DomainDetails, DomainStatus } from '../../../../types/project'; import {
DomainDetails,
DomainStatus,
ProjectDetails,
RepositoryDetails,
} from '../../../../types/project';
import ConfirmDialog from '../../../shared/ConfirmDialog'; import ConfirmDialog from '../../../shared/ConfirmDialog';
import projectData from '../../../../assets/projects.json'; import EditDomainDialog from './EditDomainDialog';
enum RefreshStatus { enum RefreshStatus {
IDLE, IDLE,
@ -24,16 +29,16 @@ enum RefreshStatus {
interface DomainCardProps { interface DomainCardProps {
domain: DomainDetails; domain: DomainDetails;
repo: RepositoryDetails;
project: ProjectDetails;
} }
const CHECK_FAIL_TIMEOUT = 5000; // In milliseconds const CHECK_FAIL_TIMEOUT = 5000; // In milliseconds
const DomainCard = ({ domain }: DomainCardProps) => { const DomainCard = ({ domain, repo, project }: DomainCardProps) => {
const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE); const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const currProject = projectData.filter( const [editDialogOpen, setEditDialogOpen] = useState(false);
(data) => data.id === domain.projectid,
);
return ( return (
<> <>
@ -69,10 +74,17 @@ const DomainCard = ({ domain }: DomainCardProps) => {
<button className="border-2 rounded-full w-8 h-8">...</button> <button className="border-2 rounded-full w-8 h-8">...</button>
</MenuHandler> </MenuHandler>
<MenuList> <MenuList>
<MenuItem className="text-black">^ Edit domain</MenuItem> <MenuItem
className="text-black"
onClick={() => {
setEditDialogOpen((preVal) => !preVal);
}}
>
^ Edit domain
</MenuItem>
<MenuItem <MenuItem
className="text-red-500" className="text-red-500"
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen((preVal) => !preVal)}
> >
^ Delete domain ^ Delete domain
</MenuItem> </MenuItem>
@ -94,7 +106,7 @@ const DomainCard = ({ domain }: DomainCardProps) => {
<Typography variant="small"> <Typography variant="small">
Once deleted, the project{' '} Once deleted, the project{' '}
<span className="bg-blue-100 rounded-sm p-0.5 text-blue-700"> <span className="bg-blue-100 rounded-sm p-0.5 text-blue-700">
{currProject[0].title} {project.title}
</span>{' '} </span>{' '}
will not be accessible from the domain{' '} will not be accessible from the domain{' '}
<span className="bg-blue-100 rounded-sm p-0.5 text-blue-700"> <span className="bg-blue-100 rounded-sm p-0.5 text-blue-700">
@ -143,6 +155,14 @@ const DomainCard = ({ domain }: DomainCardProps) => {
</table> </table>
</Card> </Card>
)} )}
<EditDomainDialog
handleOpen={() => {
setEditDialogOpen((preVal) => !preVal);
}}
open={editDialogOpen}
domain={domain}
repo={repo}
/>
</> </>
); );
}; };

View File

@ -1,15 +1,25 @@
import React from 'react'; import React, { useMemo } from 'react';
import { useParams, Link } from 'react-router-dom'; import { useParams, Link } from 'react-router-dom';
import { Button, Typography } from '@material-tailwind/react'; import { Button, Typography } from '@material-tailwind/react';
import DomainCard from './DomainCard'; import DomainCard from './DomainCard';
import domainsData from '../../../../assets/domains.json';
import { DomainDetails } from '../../../../types/project'; import { DomainDetails } from '../../../../types/project';
import domainsData from '../../../../assets/domains.json';
import repositories from '../../../../assets/repositories.json';
import projectData from '../../../../assets/projects.json';
const Domains = () => { const Domains = () => {
const { id } = useParams(); const { id } = useParams();
const currProject = useMemo(() => {
return projectData.find((data) => data.id === Number(id));
}, [id]);
const linkedRepo = useMemo(() => {
return repositories.find((repo) => repo.id === currProject?.repositoryId);
}, [currProject]);
return ( return (
<> <>
<div className="flex justify-between p-2"> <div className="flex justify-between p-2">
@ -21,13 +31,16 @@ const Domains = () => {
</Link> </Link>
</div> </div>
{(domainsData as DomainDetails[]) {(domainsData as DomainDetails[]).map((domain) => {
.filter((domain) => { return (
return Number(id) == domain.projectid; <DomainCard
}) domain={domain}
.map((domain) => { key={domain.id}
return <DomainCard domain={domain} key={domain.id} />; repo={linkedRepo!}
})} project={currProject!}
/>
);
})}
</> </>
); );
}; };

View File

@ -0,0 +1,153 @@
import React, { useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import {
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
Input,
Typography,
Select,
Option,
} from '@material-tailwind/react';
import { DomainDetails, RepositoryDetails } from '../../../../types/project';
import domains from '../../../../assets/domains.json';
const DEFAULT_REDIRECT_OPTIONS = ['none'];
interface EditDomainDialogProp {
open: boolean;
handleOpen: () => void;
domain: DomainDetails;
repo: RepositoryDetails;
}
const EditDomainDialog = ({
open,
handleOpen,
domain,
repo,
}: EditDomainDialogProp) => {
const getRedirectUrl = (domain: DomainDetails) => {
const domainArr = domain.name.split('www.');
let redirectUrl = '';
if (domain.name.startsWith('www.')) {
redirectUrl = domainArr[1];
} else {
redirectUrl = `www.${domainArr[0]}`;
}
return redirectUrl;
};
const redirectOptions = useMemo(() => {
const redirectUrl = getRedirectUrl(domain);
return [...DEFAULT_REDIRECT_OPTIONS, redirectUrl];
}, [domain]);
const isDisableDropdown = useMemo(() => {
const redirectUrl = getRedirectUrl(domain);
const domainRedirected = domains.find(
(domain) => domain.name === redirectUrl,
);
return domainRedirected?.isRedirectedto;
}, [domain]);
const {
handleSubmit,
register,
control,
watch,
formState: { isValid, isDirty },
} = useForm({
defaultValues: {
name: domain.name,
branch: repo.branch[0],
redirectedTo: !domain.isRedirectedto
? redirectOptions[0]
: redirectOptions[1],
},
});
return (
<Dialog open={open} handler={handleOpen}>
<DialogHeader className="flex justify-between">
<div>Edit domain</div>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1 rounded-3xl"
>
X
</Button>
</DialogHeader>
<form
onSubmit={handleSubmit(() => {
handleOpen();
toast.success(`Domain ${domain.name} has been updated`);
})}
>
<DialogBody className="flex flex-col gap-2 p-4">
<Typography variant="small">Domain name</Typography>
<Input crossOrigin={undefined} {...register('name')} />
<Typography variant="small">Redirect to</Typography>
<Controller
name="redirectedTo"
control={control}
render={({ field }) => (
<Select {...field} disabled={isDisableDropdown}>
{redirectOptions.map((option, key) => (
<Option key={key} value={option}>
^ {option}
</Option>
))}
</Select>
)}
/>
{isDisableDropdown && (
<div className="flex p-2 gap-2 text-black bg-gray-300 rounded-lg">
<div>^</div>
<Typography variant="small">
Domain {redirectOptions[1]} redirects to this domain so you
can not redirect this doman further.
</Typography>
</div>
)}
<Typography variant="small">Git branch</Typography>
<Input
crossOrigin={undefined}
{...register('branch', {
validate: (value) => repo.branch.includes(value),
})}
disabled={watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]}
/>
{!isValid && (
<Typography variant="small" className="text-red-500">
We couldn&apos;t find this branch in the connected Git repository.
</Typography>
)}
</DialogBody>
<DialogFooter className="flex justify-start">
<Button variant="outlined" onClick={handleOpen} className="mr-1">
Cancel
</Button>
<Button
variant="gradient"
color="blue"
type="submit"
disabled={!isDirty}
>
Save changes
</Button>
</DialogFooter>
</form>
</Dialog>
);
};
export default EditDomainDialog;

View File

@ -15,6 +15,7 @@ export interface ProjectDetails {
createdAt: string; createdAt: string;
branch: string; branch: string;
}; };
repositoryId: number;
} }
export interface DeploymentDetails { export interface DeploymentDetails {
@ -54,6 +55,7 @@ export interface RepositoryDetails {
updatedAt: string; updatedAt: string;
user: string; user: string;
private: boolean; private: boolean;
branch: string[];
} }
export enum GitSelect { export enum GitSelect {
@ -77,4 +79,5 @@ export interface DomainDetails {
name: string; name: string;
value: string; value: string;
}; };
isRedirectedto: boolean;
} }